Compare commits

...

305 commits

Author SHA1 Message Date
Christopher Faulet
4fd5cafe27 BUG/MEDIUM: htx: Fix htx_xfer() to consume more data than expected
Some checks failed
Contrib / build (push) Has been cancelled
alpine/musl / gcc (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
When an htx DATA block is partially transfer, we must take care to remove
exactly the copied size. To do so, we must save the size of the last block
value copied and not rely on the last data block after the copy. Indeed,
data can be merged with an existing DATA block, so the last block size can
be larger than the last part copied.

Because of this issue, it is possible to remove more data than
expected. Worse, this could lead to a crash by performing an integer
overflow on the block size.

No backport needed.
2026-03-27 17:19:12 +01:00
William Lallemand
d26bd9f978 BUG/MINOR: acme: fix task allocation leaked upon error
Fix a leak of the task object in acme_start_task() when one of the
condition in the function failed.

Fix issue #3308.

Must be backported to 3.2 and later.
2026-03-27 16:58:49 +01:00
Olivier Houchard
506cfcb5d4 MINOR: connections: Enhance tune.idle-pool.shared
There are two settings to control idle connection sharing across
threads.
tune.idle-pool.shared, that enables or disables it, and then
tune.takeover-other-tg-connections, which lets you or not get idle
connections from other thread groups.
Add a new keyword for tune.idle-pool.shared, "full", that lets you get
connections from other thread groups (equivalent to "full" keyword for
tune.takeover-other-tg-connections). The "on" keyword now will be
equivalent to the "restrict" one, which allowed getting connection from
other thread groups only when not doing it would result in a connection
failure (when reverse-http or when strict-macxonn are used).
tune.takeover-other-tg-connections will be deprecated.
2026-03-27 16:14:53 +01:00
Mia Kanashi
418f0c0bbe BUG/MEDIUM: acme: skip doing challenge if it is already valid
If server returns an auth with status valid it seems that client
needs to always skip it, CA can recycle authorizations, without
this change haproxy fails to obtain certificates in that case.
It is also something that is explicitly allowed and stated
in the dns-persist-01 draft RFC.

Note that it would be better to change how haproxy does status polling,
and implements the state machine, but that will take some thought
and time, this patch is a quick fix of the problem.

See:
https://github.com/letsencrypt/boulder/issues/2125
https://github.com/letsencrypt/pebble/issues/133

This must be backported to 3.2 and later.
2026-03-27 14:41:11 +01:00
Christopher Faulet
27d7c69e87 BUG/MINOR: http-ana: Only consider client abort for abortonclose
When abortonclose option is enabled (by default since 3.3), the HTTP rules
can no longer yield if the client aborts. However, stream aborts were also
considered. So it was possible to interrupt yielding rules, especially on
the response processing, while the client was still waiting for the
response.

So now, when abortonclose option is enabled, we now take care to only
consider client aborts to prevent HTTP rules to yield.

Many thanks to @DirkyJerky for his detailed analysis.

This patch should fix the issue #3306. It should be backported as far as
2.8.
2026-03-27 11:18:40 +01:00
Christopher Faulet
d1c7e56585 BUG/MINOR: config: Properly test warnif_misplaced_* return values
warnif_misplaced_* functions return 1 when a warning is reported and 0
otherwise. So the caller must properly handle the return value.

When parsing a proxy, ERR_WARN code must be added to the error code instead
of the return value. When a warning was reported, ERR_RETRYABLE (1) was
added instead of ERR_WARN.

And when tcp rules were parsed, warnings were ignored. Message were emitted
but the return values were ignored.

This patch should be backported to all stable versions.
2026-03-27 07:35:25 +01:00
Christopher Faulet
4e99cddde4 BUG/MINOR: config: Warn only if warnif_cond_conflicts report a conflict
When warnif_cond_conflicts() is called, we must take care to emit a warning
only when a conflict is reported. We cannot rely on the err_code variable
because some warnings may have been already reported. We now rely on the
errmsg variable. If it contains something, a warning is emitted. It is good
enough becasue warnif_cond_conflicts() only reports warnings.

This patch should fix the issue #3305. It is a 3.4-dev specific issue. No
backport needed.
2026-03-27 07:35:25 +01:00
Olivier Houchard
0e36267aac MEDIUM: server: remove a useless memset() in srv_update_check_addr_port.
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
Remove a memset that should not be there, and tries to zero a NULL pointer.
2026-03-26 16:43:48 +01:00
Olivier Houchard
1b0dfff552 MEDIUM: connections: Enforce mux protocol requirements
When picking a mux, pay attention to its MX_FL_FRAMED. If it is set,
then it means we explicitely want QUIC, so don't use that mux for any
protocol that is not QUIC.
2026-03-26 15:09:13 +01:00
Olivier Houchard
d3ad730d5f MINOR: protocols: Add a new proto_is_quic() function
Add a new function, proto_is_quic(), that returns true if the protocol
is QUIC (using a datagram socket but provides a stream transport).
2026-03-26 15:09:13 +01:00
Olivier Houchard
cca9245416 MINOR: checks: Store the protocol to be used in struct check
When parsing the check address, store the associated proto too.
That way we can use the notation like quic4@address, and the right
protocol will be used. It is possible for checks to use a different
protocol than the server, ie we can have a QUIC server but want to run
TCP checks, so we can't just reuse whatever the server uses.
WIP: store the protocol in checks
2026-03-26 15:09:13 +01:00
Olivier Houchard
07edaed191 BUG/MEDIUM: check: Don't reuse the server xprt if we should not
Don't assume the check will reuse the server's xprt. It may not be true
if some settings such as the ALPN has been set, and it differs from the
server's one. If the server is QUIC, and we want to use TCP for checks,
we certainly don't want to reuse its XPRT.
2026-03-26 15:09:13 +01:00
William Lallemand
1c1d9d2500 BUG/MINOR: acme: permission checks on the CLI
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
Permission checks on the CLI for ACME are missing.

This patch adds a check on the ACME commands
so they can only be run in admin mode.

ACME is stil a feature in experimental-mode.

Initial report by Cameron Brown.

Must be backported to 3.2 and later.
2026-03-25 18:37:47 +01:00
William Lallemand
47987ccbd9 BUG/MINOR: ech: permission checks on the CLI
Permission checks on the CLI for ECH are missing.

This patch adds a check for "(add|set|del|show) ssl ech" commands
so they can only be run in admin mode.

ECH is stil a feature in experimental-mode and is not compiled by
default.

Initial report by Cameron Brown.

Must be backported to 3.3.
2026-03-25 18:37:06 +01:00
William Lallemand
33041fe91f BUILD: tools: potential null pointer dereference in dl_collect_libs_cb
Some checks failed
Contrib / build (push) Has been cancelled
alpine/musl / gcc (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
This patch fixes a warning that can be reproduced with gcc-8.5 on RHEL8
(gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-28)).

This should fix issue #3303.

Must be backported everywhere 917e82f283 ("MINOR: debug: copy debug
symbols from /usr/lib/debug when present") was backported, which is
to branch 3.2 for now.
2026-03-23 21:52:56 +01:00
William Lallemand
8e250bba8f BUG/MINOR: acme/cli: fix argument check and error in 'acme challenge_ready'
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
Fix the check or arguments of the 'acme challenge_ready' command which
was checking if all arguments are NULL instead of one of the argument.

Must be backported to 3.2 and later.
2026-03-23 14:39:55 +01:00
William Lallemand
c7564c19a2 BUG/MINOR: acme: replace atol with len-bounded __strl2uic() for retry-after
Replace atol() by _strl2uic() in cases the input are ISTs when parsing
the retry-after header. There's no risk of an error since it will stop
at the first non-digit.

Must be backported to 3.2 and later.
2026-03-23 14:39:55 +01:00
William Lallemand
efbf0f8ed1 BUG/MINOR: acme: free() DER buffer on a2base64url error path
In acme_req_finalize() the data buffer is only freed when a2base64url
succeed. This patch moves the allocation so it free() the DER buffer in
every cases.

Must be backported to 3.2 and later.
2026-03-23 14:39:55 +01:00
William Lallemand
52d8ee85e7 BUG/MINOR: acme: NULL check on my_strndup()
Add a NULL check on my_strndup().

Must be backported to 3.2 and later.
2026-03-23 14:39:55 +01:00
Christopher Faulet
38a7d8599d DOC: config: Reorder params for 'tcp-check expect' directive
Order of parameters for the 'tcp-check expect' directive is changed to be
the same than 'http-check expect'.
2026-03-23 14:02:43 +01:00
Christopher Faulet
82afd36b6c DOC: config: Add missing 'status-code' param for 'http-check expect' directive
In the documentation of 'http-check expect' directive, the parameter
'status-code' was missing. Let's add it.

This patch could be backported to all stable versions.
2026-03-23 14:02:43 +01:00
Christopher Faulet
ada33006ef MINOR: proxy: Add use-small-buffers option to set where to use small buffers
Thanks to previous commits, it is possible to use small buffers at different
places: to store the request when a connection is queued or when L7 retries
are enabled, or for health-checks requests. However, there was no
configuration parameter to fine tune small buffer use.

It is now possible, thanks to the proxy option "use-small-buffers".
Documentation was updated accordingly.
2026-03-23 14:02:43 +01:00
Christopher Faulet
163eba5c8c DOC: config: Fix alphabetical ordering of external-check directives
external-check directives were not at the right place. Let's fix it.
2026-03-23 14:02:43 +01:00
Christopher Faulet
61d68f14b2 DOC: config: Fix alphabetical ordering of proxy options
external-check and idle-close-on-response options were not at the right
place. Let's fix it.
2026-03-23 14:02:43 +01:00
Christopher Faulet
125cbecfa9 MINOR: proxy: Review options flags used to configure healthchecks
When healthchecks were configured for a proxy, an enum-like was used to
sepcify the check's type. The idea was to reserve some values for futur
types of healthcheck. But it is overkill. I doubt we will ever have
something else than tcp and external checks. So corresponding PR_O2 flags
were slightly reviewed and a hole was filled.

Thanks to this change, some bits were released in options2 bitfield.
2026-03-23 14:02:43 +01:00
Christopher Faulet
a61ea0f414 MEDIUM: tcpcheck: Use small buffer if possible for healthchecks
If support for small buffers is enabled, we now try to use them for
healthcheck requests. First, we take care the tcpcheck ruleset may use small
buffers. Send rules using LF strings or too large data are excluded. The
ability to use small buffers or not are set on the ruleset. All send rules
of the ruleset must be compatible. This info is then transfer to server's
healthchecks relying on this ruleset.

Then, when a healthcheck is running, when a send rule is evaluated, if
possible, we try to use small buffers. On error, the ability to use small
buffers is removed and we retry with a regular buffer. It means on the first
error, the support is disabled for the healthcheck and all other runs will
use regular buffers.
2026-03-23 14:02:43 +01:00
Christopher Faulet
cd363e0246 MEDIUM: mux-h2: Stop dealing with HTX flags transfer in h2_rcv_buf()
In h2_rcv_buf(), HTX flags are transfer with data when htx_xfer() is
called. There is no reason to continue to deal with them in the H2 mux. In
addition, there is no reason to set SE_FL_EOI flag when a parsing error was
reported. This part was added before the stconn era. Nowadays, when an HTX
parsing error is reported, an error on the sedesc should also be reported.
2026-03-23 14:02:43 +01:00
Christopher Faulet
d257dd4563 Revert "BUG/MEDIUM: mux-h2: make sure to always report pending errors to the stream"
This reverts commit 44932b6c41.

The patch above was only necessary to handle partial headers or trailers
parsing. There was nothing to prevent the H2 multiplexer to start to add
headers or trailers in an HTX message and to stop the processing on error,
leaving the HTX message with no EOH/EOT block.

From the HTX API point of view, it is unexepected. And this was fixed thanks
to the commit ba7dc46a9 ("BUG/MINOR: h2/h3: Never insert partial
headers/trailers in an HTX message").

So this patch can be reverted. It is important to not report a parsign error
too early, when there are still data to transfer to the upper layer.

This patch must be backport where 44932b6c4 was backported but only after
backporting ba7dc46a9 first.
2026-03-23 14:02:43 +01:00
Christopher Faulet
39121ceca6 MEDIUM: tree-wide: Rely on htx_xfer() instead of htx_xfer_blks()
htx_xfer() function replaced htx_xfer_blks(). So let's use it.
2026-03-23 14:02:43 +01:00
Christopher Faulet
c9a9fa813b MEDIUM: stconn: Use a small buffer if possible for L7 retries
Whe L7 retries are enabled and the request is small enough, a small buffer
is used instead of a regular one.
2026-03-23 14:02:43 +01:00
Christopher Faulet
181cd8ba8a MEDIUM: stream: Try to use small buffer when TCP stream is queued
It was performed when an HTX stream was queued. Small requests were moved in
small buffers. Here we do the same but for TCP streams.
2026-03-23 14:02:42 +01:00
Christopher Faulet
5acdda4eed MEDIUM: stream: Try to use a small buffer for HTTP request on queuing
When a HTX stream is queued, if the request is small enough, it is moved
into a small buffer. This should save memory on instances intensively using
queues.

Applet and connection receive function were update to block receive when a
small buffer is in use.
2026-03-23 14:02:42 +01:00
Christopher Faulet
92a24a4e87 MEDIUM: chunk: Add support for small chunks
In the same way support for large chunks was added to properly work with
large buffers, we are now adding supports for small chunks because it is
possible to process small buffers.

So a dedicated memory pool is added to allocate small
chunks. alloc_small_trash_chunk() must be used to allocate a small
chunk. alloc_trash_chunk_sz() and free_trash_chunk() were uppdated to
support small chunks.

In addition, small trash buffers are also created, using the same mechanism
than for regular trash buffers. So three thread-local trash buffers are
created. get_small_trash_chunk() must be used to get a small trash buffer.
And get_trash_chunk_sz() was updated to also deal with small buffers.
2026-03-23 14:02:42 +01:00
Christopher Faulet
467f911cea MINOR: http-ana: Use HTX API to move to a large buffer
Use htx_move_to_large_buffer() to move a regular HTX message to a large
buffer when we are waiting for a huge payload.
2026-03-23 14:02:42 +01:00
Christopher Faulet
0213dd70c9 MINOR: htx: Add helper functions to xfer a message to smaller or larger one
htx_move_to_small_buffer()/htx_move_to_large_buffer() and
htx_copy_to_small_buffer()/htx_copy_to_large_buffer() functions can now be
used to move or copy blocks from a default buffer to a small or large
buffer. The destination buffer is allocated and then each blocks are
transferred into it.

These funtions relies in htx_xfer() function.
2026-03-23 14:02:42 +01:00
Christopher Faulet
5ead611cc2 MEDIUM: htx: Add htx_xfer function to replace htx_xfer_blks
htx_xfer() function should replace htx_xfer_blks(). It will be a bit easier to
maintain and to use. The behavior of htx_xfer() can be changed by calling it
with specific flags:

  * HTX_XFER_KEEP_SRC_BLKS: Blocks from the source message are just copied
  * HTX_XFER_PARTIAL_HDRS_COPY: It is allowed to partially xfer headers or trailers
  * HTX_XFER_HDRS_ONLY: only headers are xferred

By default (HTX_XFER_DEFAULT or 0), all blocks from the source message are moved
into to the destination mesage. So copied in the destination messageand removed
from the source message.

The caller must still define the maximum amount of data (including meta-data)
that can be xferred.

It is no longer necessary to specify a block type to stop the copy. Most of
time, with htx_xfer_blks(), this parameter was set to HTX_BLK_UNUSED. And
otherwise it was only specified to transfer headers.

It is important to not that the caller is responsible to verify the original
HTX message is well-formated. Especially, it must be sure headers part and
trailers part are complete (finished by EOH/EOT block).

For now, htx_xfer_blks() is not removed for compatiblity reason. But it is
deprecated.
2026-03-23 14:02:42 +01:00
Christopher Faulet
41c89e4fb6 MINOR: config: Report the warning when invalid large buffer size is set
When an invalid large buffer size was found in the configuration, a warning
was emitted but it was not reported via the error code. It is now fixed.
2026-03-23 14:02:42 +01:00
Christopher Faulet
b71f70d548 MINOR: config: Relax tests on the configured size of small buffers
When small buffer size was greater than the default buffer size, an error
was triggered. We now do the same than for large buffer. A warning is
emitted and the small buffer size is set to 0 do disable small buffer
allocation.
2026-03-23 14:02:42 +01:00
Christopher Faulet
01b9b67d5c MINOR: quic: Use b_alloc_small() to allocate a small buffer
Rely on b_alloc_small to allocate a small buffer.
2026-03-23 14:02:42 +01:00
Christopher Faulet
f8c96bf9cb MINOR: dynbuf: Add helper functions to alloc large and small buffers
b_alloc_small() and b_alloc_large() can now be used to alloc small or larger
buffers. For now, unlike default buffers, buffer_wait lists are not used.
2026-03-23 14:02:42 +01:00
Christopher Faulet
4d6cba03f2 MINOR: buffers: Move small buffers management from quic to dynbuf part
Because small buffers were only used by QUIC streams, the pool used to alloc
these buffers was located in the quic code. However, their usage will be
extended to other parts. So, the small buffers pool was moved into the
dynbuf part.
2026-03-23 14:02:42 +01:00
Amaury Denoyelle
1c379cad88 BUG/MINOR: http_htx: fix null deref in http-errors config check
http-errors parsing has been refactored in a recent serie of patches.
However, a null deref was introduced by the following patch in case a
non-existent http-errors section is referenced by an "errorfiles"
directive.

  commit 2ca7601c2d
  MINOR/OPTIM: http_htx: lookup once http_errors section on check/init

Fix this by delaying ha_free() so that it is called after ha_alert().

No need to backport.
2026-03-23 13:55:48 +01:00
William Lallemand
3d9865a12c BUG/MINOR: acme/cli: wrong argument check in 'acme renew'
Argument check should be args[2] instead of args[1] which is always
'renew'.

Must be backported to 3.2 and later.
2026-03-23 11:58:53 +01:00
William Lallemand
d72be950bd BUG/MINOR: acme: wrong error when checking for duplicate section
The cfg_parse_acme() function checks if an 'acme' section is already
existing in the configuration with cur_acme->linenum > 0. But the wrong
filename and line number are displayed in the commit message.

Must be backported to 3.2 and later.
2026-03-23 11:58:53 +01:00
William Lallemand
5a0fbbf1ca BUG/MINOR: acme: leak of ext_san upon insertion error
This patch fixes a leak of the ext_san structure when
sk_X509_EXTENSION_push() failed. sk_X509_EXTENSION_pop_free() is already
suppose to free it, so ext_san must be set to NULL upon success to avoid
a double-free.

Must be backported to 3.2 and later.
2026-03-23 11:58:53 +01:00
Amaury Denoyelle
c6fc53aa99 MEDIUM: proxy: remove http-errors limitation for dynamic backends
Use proxy_check_http_errors() on defaults proxy instances. This will
emit alert messages for errorfiles directives referencing a non-existing
http-errors section, or a warning if an explicitely listed status code
is not present in the target section.

This is a small behavior changes, as previouly this was only performed
for regular proxies. Thus, errorfile/errorfiles directives in an unused
defaults were never checked.

This may prevent startup of haproxy with a configuration file previously
considered as valid. However, this change is considered as necessary to
be able to use http-errors with dynamic backends. Any invalid defaults
will be detected on startup, rather than having to discover it at
runtime via "add backend" invokation.

Thus, any restriction on http-errors usage is now lifted for the
creation of dynamic backends.
2026-03-23 11:14:07 +01:00
Amaury Denoyelle
2ca7601c2d MINOR/OPTIM: http_htx: lookup once http_errors section on check/init
The previous patch has splitted the original proxy_check_errors()
function in two, so that check and init steps are performed separately.
However, this renders the code inefficient for "errorfiles" directive as
tree lookup on http-errors section is performed twice.

Optimize this by adding a reference to the section in conf_errors
structure. This is resolved during proxy_check_http_errors() and
proxy_finalize_http_errors() can reuse it.

No need to backport.
2026-03-23 10:51:33 +01:00
Amaury Denoyelle
d250b381dc MINOR: http_htx: split check/init of http_errors
Function proxy_check_errors() is used when configuration parsing is
over. This patch splits it in two newly named ones.

The first function is named proxy_check_http_errors(). It is responsible
to check for the validity of any "errorfiles" directive which could
reference non-existent http-errors section or code not defined in such
section. This function is now called via proxy_finalize().

The second function is named proxy_finalize_http_errors(). It converts
each conf_errors type used during parsing in a proper http_reply type
for runtime usage. This function is still called via post-proxy-check,
after proxy_finalize().

This patch does not bring any functional change. However, it will become
necessary to ensure http-errors can be used as expected with dynamic
backends.
2026-03-23 10:51:33 +01:00
Amaury Denoyelle
5b184e4178 MINOR: http_htx: rename fields in struct conf_errors
This patch is the second part of the refactoring for http-errors
parsing. It renames some fields in <conf_errors> structure to clarify
their usage. In particular, union variants are renamed "inl"/"section",
which better highlight the link with the newly defined enum
http_err_directive.
2026-03-23 10:51:33 +01:00
Amaury Denoyelle
fedaf054c4 MINOR: http_htx: use enum for arbitrary values in conf_errors
In conf_errors struct, arbitrary integer values were used for both
<type> field and <status> array. This renders the code difficult to
follow.

Replaces these values with proper enums type. Two new types are defined
for each of these fields. The first one represents the directive type,
derived from the keyword used (errorfile vs errorfiles). This directly
represents which part of <info> union should be manipulated.

The second enum is used for errorfiles directive with a reference on a
http-errors section. It indicates whether or not if a status code should
be imported from this section, and if this import is explicit or
implicit.
2026-03-23 10:51:33 +01:00
David Carlier
8e469ebf2e BUG/MEDIUM: acme: fix multiple resource leaks in acme_x509_req()
Several resources were leaked on both success and error paths:

- X509_NAME *nm was never freed. X509_REQ_set_subject_name() makes
  an internal copy, so nm must be freed separately by the caller.
- str_san allocated via my_strndup() was never freed on either path.
- On error paths after allocation, x (X509_REQ) and exts
  (STACK_OF(X509_EXTENSION)) were also leaked.

Fix this by adding proper cleanup of all allocated resources in both
the success and error paths. Also move sk_X509_EXTENSION_pop_free()
after X509_REQ_sign() so it is not skipped when sign fails, and
initialize nm to NULL to make early error paths safe.

Must be backported as far as 3.2.
2026-03-23 10:44:42 +01:00
Willy Tarreau
ff7b06badb BUILD: sched: fix leftover of debugging test in single-run changes
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
There was a leftover of "activity[tid].ctr1++" in commit 7d40b3134
("MEDIUM: sched: do not run a same task multiple times in series")
that unfortunately only builds in development mode :-(
2026-03-23 07:29:43 +01:00
Willy Tarreau
5d0f5f8168 MINOR: mux-h2: assign a limited frames processing budget
This introduces 3 new settings: tune.h2.be.max-frames-at-once and
tune.h2.fe.max-frames-at-once, which limit the number of frames that
will be processed at once for backend and frontend side respectively,
and tune.h2.fe.max-rst-at-once which limits the number of RST_STREAM
frames processed at once on the frontend.

We can now yield when reading too many frames at once, which allows to
limit the latency caused by processing too many frames in large buffers.
However if we stop due to the RST budget being depleted, it's most likely
the sign of a protocol abuse, so we make the tasklet go to BULK since
the goal is to punish it.

By limiting the number of RST per loop to 1, the SSL response time drops
from 95ms to 1.6ms during an H2 RST flood attack, and the maximum SSL
connection rate drops from 35.5k to 28.0k instead of 11.8k. A moderate
SSL load that shows 1ms response time and 23kcps increases to 2ms with
15kcps versus 95ms and 800cps before. The average loop time goes down
from 270-280us to 160us, while still doubling the attack absorption
rate with the same CPU capacity.

This patch may usefully be backported to 3.3 and 3.2. Note that to be
effective, this relies on the following patches:

  MEDIUM: sched: do not run a same task multiple times in series
  MINOR: sched: do not requeue a tasklet into the current queue
  MINOR: sched: do not punish self-waking tasklets anymore
  MEDIUM: sched: do not punish self-waking tasklets if TASK_WOKEN_ANY
  MEDIUM: sched: change scheduler budgets to lower TL_BULK
2026-03-23 07:14:22 +01:00
Willy Tarreau
ed6a4bc807 MEDIUM: sched: change scheduler budgets to lower TL_BULK
Having less yielding tasks in TL_BULK and more in TL_NORMAL, we need
to rebalance these queues' priorities. Tests have shown that raising
TL_NORMAL to 40% and lowering TL_BULK to 3% seems to give about the
best tradeoffs.
2026-03-23 06:58:37 +01:00
Willy Tarreau
282b9b7d16 MEDIUM: sched: do not punish self-waking tasklets if TASK_WOKEN_ANY
Self-waking tasklets are currently punished and go to the BULK list.
However it's a problem with muxes or the stick-table purge that just
yield and wake themselves up to limit the latency they cause to the
rest of the process, because by doing so to help others, they punish
themselves. Let's check if any TASK_WOKEN_ANY flag is present on
the tasklet and stop sending tasks presenting such a flag to TL_BULK.
Since tasklet_wakeup() by default passes TASK_WOKEN_OTHER, it means
that such tasklets will no longer be punished. However, tasks which
only want a best-effort wakeup can simply pass 0.

It's worth noting that a comparison was made between going into
TL_BULK at all and only setting the TASK_SELF_WAKING flag, and
it shows that the average latencies are ~10% better when entirely
avoiding TL_BULK in this case.
2026-03-23 06:57:12 +01:00
Willy Tarreau
6982c2539f MINOR: sched: do not punish self-waking tasklets anymore
Nowadays due to yield etc, it's counter-productive to permanently
punish self-waking tasklets, let's abandon this principle as it prevent
finer task priority handling.

We continue to check for the TASK_SELF_WAKING flag to place a task
into TL_BULK in case some code wants to make use of it in the future
(similarly to TASK_HEAVY), but no code sets it anymore. It could
possible make sense in the future to replace this flag with a one-shot
variant requesting low-priority.
2026-03-23 06:55:31 +01:00
Willy Tarreau
9852d5be26 MINOR: sched: do not requeue a tasklet into the current queue
As found by Christopher, the concept of waking a tasklet up into the
current queue is totally flawed, because if a task is in TL_BULK or
TL_HEAVY, all the tasklets it will wake up will end up in the same
queue. Not only this will clobber such queues, but it will also
reduce their quality of service, and this can contaminate other
tasklets due to the numerous wakeups there are now with the subsribe
mechanism between layers.
2026-03-23 06:54:42 +01:00
Willy Tarreau
7d40b3134a MEDIUM: sched: do not run a same task multiple times in series
There's always a risk that some tasks run multiple times if they wake
each other up. Now we include the loop counter in the task struct and
stop processing the queue it's in when meeting a task that has already
run. We only pick 16 bits since that's only what remains free in the
task common part, so from time to time (once every 65536) it will be
possible to wrongly match a task as having already run and stop evaluating
its queue, but it's rare enough that we don't care, because this will
be OK on the next iteration.
2026-03-23 06:52:24 +01:00
Frederic Lecaille
8f6cb8f452 BUG/MINOR: qpack: fix 62-bit overflow and 1-byte OOB reads in decoding
Some checks failed
Contrib / build (push) Has been cancelled
alpine/musl / gcc (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
This patch improves the robustness of the QPACK varint decoder and fixes
potential 1-byte out-of-bounds reads in qpack_decode_fs().

In qpack_decode_fs(), two 1-byte OOB reads were possible on truncated
streams between two varint decoding. These occurred when trying to read
the byte containing the Huffman bit <h> and the Value Length prefix
immediately following an Index or a Name Length.

Note that these OOB are limited to a single byte because
qpack_get_varint() already ensures that its input length is non-zero
before consuming any data.

The fixes in qpack_decode_fs() are:
- When decoding an index, we now verify that at least one byte remains
  to safely access the following <h> bit and value length.
- When decoding a literal, we now check len < name_len + 1 to ensure
  the byte starting the header value is reachable.

In qpack_get_varint(), the maximum value is now strictly capped at 2^62-1
as per RFC. This is enforced using a budget-based check:

   (v & 127) > (limit - ret) >> shift

This prevents values from  overflowing into the 63rd or 64th bits, which
would otherwise break subsequent signed comparisons (e.g., if (len < name_len))
by interpreting the length as a negative value, leading to false positive
tests.

Thank you to @jming912 for having reported this issue in GH #3302.

Must be backported as far as 2.6
2026-03-20 19:40:11 +01:00
Egor Shestakov
60c9e2975b BUG/MINOR: sock: adjust accept() error messages for ENFILE and ENOMEM
In the ENFILE and ENOMEM cases, when accept() fails, an irrelevant
global.maxsock value was printed that doesn't reflect system limits.
Now the actconn is printed that gives a hint about the failure reasons.

Should be backported in all stable branches.
2026-03-20 16:51:47 +01:00
Aurelien DARRAGON
5617e47f91 MINOR: log: support optional 'profile <log_profile_name>' argument to do-log action
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
We anticipated that the do-log action should be expanded with optional
arguments at some point. Now that we heard of multiple use-cases
that could be achieved with do-log action, but that are limitated by the
fact that all do-log statements inherit from the implicit log-profile
defined on the logger, we need to provide a way for the user to specify
that custom log-profile that could be used per do-log actions individually

This is what we try to achieve in this commit, by leveraging the
prerequisite work performed by the last 2 commits.
2026-03-20 11:42:48 +01:00
Aurelien DARRAGON
042b7ab763 MINOR: log: provide a way to override logger->profile from process_send_log_ctx
In process_send_log(), now also consider the ctx if ctx->profile != NULL

In that case, we do as if logger->prof was set, but we consider
ctx->profile in priority over the logger one. What this means is that
it will become possible to pass ctx.profile to a profile that will be
used no matter what to generate the log payload.

This is a pre-requisite to implement optional "profile" argument for
do-log action
2026-03-20 11:42:40 +01:00
Aurelien DARRAGON
7466f64c56 MINOR: log: split do_log() in do_log() + do_log_ctx()
do_log() is just a wrapper to use do_log_ctx() with pre-filled ctx, but
we now have the low-level do_log_ctx() variant which can be used to
pass specific ctx parameters instead.
2026-03-20 11:41:06 +01:00
Willy Tarreau
15b005fd1e [RELEASE] Released version 3.4-dev7
Some checks failed
Contrib / build (push) Has been cancelled
alpine/musl / gcc (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
Released version 3.4-dev7 with the following main changes :
    - BUG/MINOR: stconn: Increase SC bytes_out value in se_done_ff()
    - BUG/MINOR: ssl-sample: Fix sample_conv_sha2() by checking EVP_Digest* failures
    - BUG/MINOR: backend: Don't get proto to use for webscoket if there is no server
    - BUG/MINOR: jwt: Missing 'jwt_tokenize' return value check
    - MINOR: flt_http_comp: define and use proxy_get_comp() helper function
    - MEDIUM: flt_http_comp: split "compression" filter in 2 distinct filters
    - CLEANUP: flt_http_comp: comp_state doesn't bother about the direction anymore
    - BUG/MINOR: admin: haproxy-reload use explicit socat address type
    - MEDIUM: admin: haproxy-reload conversion to POSIX sh
    - BUG/MINOR: admin: haproxy-reload rename -vv long option
    - SCRIPTS: git-show-backports: hide the common ancestor warning in quiet mode
    - SCRIPTS: git-show-backports: add a restart-from-last option
    - MINOR: mworker: add a BUG_ON() on mproxy_li in _send_status
    - BUG/MINOR: mworker: don't set the PROC_O_LEAVING flag on master process
    - Revert "BUG/MINOR: jwt: Missing 'jwt_tokenize' return value check"
    - MINOR: jwt: Improve 'jwt_tokenize' function
    - MINOR: jwt: Convert EC JWK to EVP_PKEY
    - MINOR: jwt: Parse ec-specific fields in jose header
    - MINOR: jwt: Manage ECDH-ES algorithm in jwt_decrypt_jwk function
    - MINOR: jwt: Add ecdh-es+axxxkw support in jwt_decrypt_jwk converter
    - MINOR: jwt: Manage ec certificates in jwt_decrypt_cert
    - DOC: jwt: Add ECDH support in jwt_decrypt converters
    - MINOR: stconn: Call sc_conn_process from the I/O callback if TASK_WOKEN_MSG state was set
    - MINOR: mux-h2: Rely on h2s_notify_send() when resuming h2s for sending
    - MINOR: mux-spop: Rely on spop_strm_notify_send() when resuming streams for sending
    - MINOR: muxes: Wakup the data layer from a mux stream with TASK_WOKEN_IO state
    - MAJOR: muxes: No longer use app_ops .wake() callback function from muxes
    - MINOR: applet: Call sc_applet_process() instead of .wake() callback function
    - MINOR: connection: Call sc_conn_process() instead of .wake() callback function
    - MEDIUM: stconn: Remove .wake() callback function from app_ops
    - MINOR: check: Remove wake_srv_chk() function
    - MINOR: haterm: Remove hstream_wake() function
    - MINOR: stconn: Wakup the SC with TASK_WOKEN_IO state from opposite side
    - MEDIUM: stconn: Merge all .chk_rcv() callback functions in sc_chk_rcv()
    - MINOR: stconn: Remove .chk_rcv() callback functions
    - MEDIUM: stconn: Merge all .chk_snd() callback functions in sc_chk_snd()
    - MINOR: stconn: Remove .chk_snd() callback functions
    - MEDIUM: stconn: Merge all .abort() callback functions in sc_abort()
    - MINOR: stconn: Remove .abort() callback functions
    - MEDIUM: stconn: Merge all .shutdown() callback functions in sc_shutdown()
    - MINOR: stconn: Remove .shutdown() callback functions
    - MINOR: stconn: Totally app_ops from the stconns
    - MINOR: stconn: Simplify sc_abort/sc_shutdown by merging calls to se_shutdown
    - DEBUG: stconn: Add a CHECK_IF() when I/O are performed on a orphan SC
    - MEDIUM: mworker: exiting when couldn't find the master mworker_proc element
    - BUILD: ssl: use ASN1_STRING accessors for OpenSSL 4.0 compatibility
    - BUILD: ssl: make X509_NAME usage OpenSSL 4.0 ready
    - BUG/MINOR: tcpcheck: Fix typo in error error message for `http-check expect`
    - BUG/MINOR: jws: fix memory leak in jws_b64_signature
    - DOC: configuration: http-check expect example typo
    - DOC/CLEANUP: config: update mentions of the old "Global parameters" section
    - BUG/MEDIUM: ssl: Handle receiving early data with BoringSSL/AWS-LC
    - BUG/MINOR: mworker: always stop the receiving listener
    - BUG/MEDIUM: ssl: Don't report read data as early data with AWS-LC
    - BUILD: makefile: fix range build without test command
    - BUG/MINOR: memprof: avoid a small memory leak in "show profiling"
    - BUG/MINOR: proxy: do not forget to validate quic-initial rules
    - MINOR: activity: use dynamic allocation for "show profiling" entries
    - MINOR: tools: extend the pointer hashing code to ease manipulations
    - MINOR: tools: add a new pointer hash function that also takes an argument
    - MINOR: memprof: attempt different retry slots for different hashes on collision
    - MINOR: tinfo: start to add basic thread_exec_ctx
    - MINOR: memprof: prepare to consider exec_ctx in reporting
    - MINOR: memprof: also permit to sort output by calling context
    - MINOR: tools: add a function to write a thread execution context.
    - MINOR: debug: report the execution context on thread dumps
    - MINOR: memprof: report the execution context on profiling output
    - MINOR: initcall: record the file and line declaration of an INITCALL
    - MINOR: tools: decode execution context TH_EX_CTX_INITCALL
    - MINOR: tools: support decoding ha_caller type exec context
    - MINOR: sample: store location for fetch/conv via initcalls
    - MINOR: sample: also report contexts registered directly
    - MINOR: tools: support an execution context that is just a function
    - MINOR: actions: store the location of keywords registered via initcalls
    - MINOR: actions: also report execution contexts registered directly
    - MINOR: filters: set the exec context to the current filter config
    - MINOR: ssl: set the thread execution context during message callbacks
    - MINOR: connection: track mux calls to report their allocation context
    - MINOR: task: set execution context on task/tasklet calls
    - MINOR: applet: set execution context on applet calls
    - MINOR: cli: keep the info of the current keyword being processed in the appctx
    - MINOR: cli: keep track of the initcall context since kw registration
    - MINOR: cli: implement execution context for manually registered keywords
    - MINOR: activity: support aggregating by caller also for memprofile
    - MINOR: activity: raise the default number of memprofile buckets to 4k
    - DOC: internals: short explanation on how thread_exec_ctx works
    - BUG/MINOR: mworker: only match worker processes when looking for unspawned proc
    - MINOR: traces: defer processing of "-dt" options
    - BUG/MINOR: mworker: fix typo &= instead of & in proc list serialization
    - BUG/MINOR: mworker: set a timeout on the worker socketpair read at startup
    - BUG/MINOR: mworker: avoid passing NULL version in proc list serialization
    - BUG/MINOR: sockpair: set FD_CLOEXEC on fd received via SCM_RIGHTS
    - BUG/MEDIUM: stconn: Don't forget to wakeup applets on shutdown
    - BUG/MINOR: spoe: Properly switch SPOE filter to WAITING_ACK state
    - BUG/MEDIUM: spoe: Properly abort processing on client abort
    - BUG/MEDIUM: stconn: Fix abort on close when a large buffer is used
    - BUG/MEDIUM: stconn: Don't perform L7 retries with large buffer
    - BUG/MINOR: h2/h3: Only test number of trailers inserted in HTX message
    - MINOR: htx: Add function to truncate all blocks after a specific block
    - BUG/MINOR: h2/h3: Never insert partial headers/trailers in an HTX message
    - BUG/MINOR: http-ana: Swap L7 buffer with request buffer by hand
    - BUG/MINOR: stream: Fix crash in stream dump if the current rule has no keyword
    - BUG/MINOR: mjson: make mystrtod() length-aware to prevent out-of-bounds reads
    - MEDIUM: stats-file/clock: automatically update now_offset based on shared clock
    - MINOR: promex: export "haproxy_sticktable_local_updates" metric
    - BUG/MINOR: spoe: Fix condition to abort processing on client abort
    - BUILD: spoe: Remove unsused variable
    - MINOR: tools: add a function to create a tar file header
    - MINOR: tools: add a function to load a file into a tar archive
    - MINOR: config: support explicit "on" and "off" for "set-dumpable"
    - MINOR: debug: read all libs in memory when set-dumpable=libs
    - DEV: gdb: add a new utility to extract libs from a core dump: libs-from-core
    - MINOR: debug: copy debug symbols from /usr/lib/debug when present
    - MINOR: debug: opportunistically load libthread_db.so.1 with set-dumpable=libs
    - BUG/MINOR: mworker: don't try to access an initializing process
    - BUG/MEDIUM: peers: enforce check on incoming table key type
    - BUG/MINOR: mux-h2: properly ignore R bit in GOAWAY stream ID
    - BUG/MINOR: mux-h2: properly ignore R bit in WINDOW_UPDATE increments
    - OPTIM: haterm: use chunk builders for generated response headers
    - BUG/MAJOR: h3: check body size with content-length on empty FIN
    - BUG/MEDIUM: h3: reject unaligned frames except DATA
    - BUG/MINOR: mworker/cli: fix show proc pagination losing entries on resume
    - CI: github: treat vX.Y.Z release tags as stable like haproxy-* branches
    - MINOR: freq_ctr: add a function to add values with a peak
    - MINOR: task: maintain a per-thread indicator of the peak run-queue size
    - MINOR: mux-h2: store the concurrent streams hard limit in the h2c
    - MINOR: mux-h2: permit to moderate the advertised streams limit depending on load
    - MINOR: mux-h2: permit to fix a minimum value for the advertised streams limit
    - BUG/MINOR: mworker: fix sort order of mworker_proc in 'show proc'
    - CLEANUP: mworker: fix tab/space mess in mworker_env_to_proc_list()
2026-03-20 10:14:59 +01:00
William Lallemand
f1e8173a43 CLEANUP: mworker: fix tab/space mess in mworker_env_to_proc_list()
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
The previous patch messed up with the indentation in
mworker_env_to_proc_list()
2026-03-19 18:01:06 +01:00
William Lallemand
4c61e9028c BUG/MINOR: mworker: fix sort order of mworker_proc in 'show proc'
Since version 3.1, the display order of old workers in 'show proc' was
accidentally reversed. The oldest worker was shown first and the newest
last, which was not the intended behavior. This regression was introduced
during the master-worker rework.

Fix this by sorting the list during deserialization in
mworker_env_to_proc_list().

An alternative fix would have been to iterate the list in reverse order
in the show proc function, but that approach risks introducing
inconsistencies when backporting to older versions.

Must be backported to 3.1 and later.
2026-03-19 17:51:28 +01:00
Willy Tarreau
932d77e287 MINOR: mux-h2: permit to fix a minimum value for the advertised streams limit
When using rq-load on tune.h2.fe.max-concurrent-streams, it's easy to
reach a situation where only one stream is allowed. There's nothing
wrong with this but it turns out that slightly higher values do not
necessarily cause significantly higher loads and will improve the user
experience. For this reason the keyword now also supports "min" to
specify a value. Experimentation shows that values from 5 to 15 remain
very effective at protecting the run queue while allowing a great level
of parallelism that keeps a site fluid.
2026-03-19 16:24:32 +01:00
Willy Tarreau
c238965b27 MINOR: mux-h2: permit to moderate the advertised streams limit depending on load
Global setting tune.h2.fe.max-concurrent-streams now supports an optional
"rq-load" option to pass either a target load, or a keyword among "auto"
and "ignore". These are used to quadratically reduce the advertised streams
limit when the thread's run queue size goes beyong the configured value,
and automatically reduce the load on the process from new connections.
With "auto", instead of taking an explicit value, it uses as a target the
"tune.runqueue-depth" setting (which might be automatic). Tests have shown
that values between 50 and 100 are already very effective at reducing the
loads during attacks from 100000 to around 1500. By default, "ignore"
is in effect, which means that the dynamic tuning is not enabled.
2026-03-19 16:24:31 +01:00
Willy Tarreau
b63492e4f4 MINOR: mux-h2: store the concurrent streams hard limit in the h2c
The hard limit on the number of concurrent streams is currently
determined only by configuration and returned by
h2c_max_concurrent_streams(). However this doesn't permit to
change such settings on the fly without risking to break connections,
and it doesn't allow a connection to pick a different value, which
could be desirable for example to try to slow abuse down.

Let's store a copy of h2c_max_concurrent_streams() at connection
creation time into the h2c as streams_hard_limit. This inflates
the h2c size from 1324 to 1328 (0.3%) which is acceptable for the
expected benefits.
2026-03-19 16:24:31 +01:00
Willy Tarreau
b3a84800b4 MINOR: task: maintain a per-thread indicator of the peak run-queue size
The new field th_ctx->rq_tot_peak contains the computed peak run queue
length averaged over the last 512 calls. This is computed when entering
process_runnable_tasks. It will not take into account new tasks that are
created or woken up during this round nor those which are evicted, which
is the reason why we're using a peak measurement to increase chances to
observe transient high values. Tests have shown that 512 samples are good
to provide a relatively smooth average measurement while still fading
away in a matter of milliseconds at high loads. Since this value is
only updated once per round, it cannot be used as a statistic and
shouldn't be exposed, it's only for internal use (self-regulation).
2026-03-19 16:24:31 +01:00
Willy Tarreau
eec60f14dd MINOR: freq_ctr: add a function to add values with a peak
Sometimes it's desirable to observe fading away peak values, where a new
value that is higher than the historical one instantly replaces it,
otherwise contributes to it. It is convenient when trying to observe
certain phenomenons like peak queue sizes. The new function
swrate_add_peak_local() does that to a private variable (no atomic ops
involved as it's not worth the cost since such use cases are typically
local).
2026-03-19 16:24:31 +01:00
William Lallemand
fc38ebb079 CI: github: treat vX.Y.Z release tags as stable like haproxy-* branches
Add detection of release tags matching the vX.Y.Z pattern so they use
the same stable CI configuration as haproxy-* branches, rather than the
development one.

It prevents stable tag to trigger the CI with docker images and SSL
libraries only used for development.

Must be backported in stable releases.
2026-03-19 15:58:24 +01:00
Alexander Stephan
10e78d9246 BUG/MINOR: mworker/cli: fix show proc pagination losing entries on resume
After commit 594408cd61 ("BUG/MINOR: mworker/cli: fix show proc
pagination using reload counter"), the old-workers pagination stores
ctx->next_reload = child->reloads on flush failure, then skips entries
with child->reloads >= ctx->next_reload on resume.

The >= comparison is direction-dependent: it assumes the list is in
descending reload order (newest first). On current master, proc_list
is in ascending order (oldest first) because mworker_env_to_proc_list()
appends deserialized entries before mworker_prepare_master() appends
the new worker. This means the skip logic is inverted and can miss
entries or loop incorrectly depending on the version.

We fix this by renaming the context field to resume_reload and changing its
semantics: it now tracks the reload count of the last *successfully
flushed* row rather than the failed one. On flush failure, resume_reload
is left unchanged so the failed row is replayed on the next call. On
resume, entries are skipped by walking the list until the marker entry is
found (exact == match), which works regardless of list direction.

Additionally, we have to handle the unlikely case where the marker entry
is deleted from proc_list between handler calls (e.g. the process exits and
SIGCHLD processing removes it). Detect this by tracking the previous
LEAVING entry's reload count during the skip phase: if two consecutive
entries straddle the skip value (one > skip, the other < skip), the
deleted entry's former position has been crossed, so skipping stops and
the current entry is emitted.

This should be backported to all stable branches. On branches where
proc_list is in descending order (2.9, 3.0), the fix applies the
same way since the skip logic is now direction-agnostic.
2026-03-19 14:46:15 +01:00
Amaury Denoyelle
4e937e0391 BUG/MEDIUM: h3: reject unaligned frames except DATA
Some checks failed
Contrib / build (push) Has been cancelled
alpine/musl / gcc (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
HTTP/3 parser cannot deal with unaligned frames, except for DATA. As it
was expected that such case would not occur, a simple BUG_ON() was
written to protect HEADERS parsing.

First, this BUG_ON() was incorrectly written due an incorrect operator
'>=' vs '>' when checking if data wraps. Thus this patch correct it.

However this correction is not sufficient as it still possible to handle
a large unaligned HEADERS frame, which would trigger this BUG_ON(). This
is very unlikely as HEADERS is the first received frame on a request
stream, but not completely impossible. As HTTP/3 frame header (type +
length) is parsed first and removed, this leaves a small gap at the
buffer beginning. If this small gap is then filled with the remaining
frame payload, it would result in unaligned data. Also, trailers are
also sensitive here as in this case a HEADERS frame is handled after
other frames.

The objective of this patch is to ensure that an unaligned frame is now
handled in a safe way. This is extend to all HTTP/3 frames (except DATA)
and not only to HEADERS type. Parsing is interrupted if frame payload is
wrapping in the buffer. This should never happen except maybe with some
weird clients, so the connection is closed with H3_EXCESSIVE_LOAD error.

This approach is considered the safest one, in particular for backport
purpose. In the future, realign operation via copy may be implemented
instead if considered as useful.

This must be backported up to 2.6.
2026-03-19 10:40:25 +01:00
Amaury Denoyelle
05a295441c BUG/MAJOR: h3: check body size with content-length on empty FIN
In QUIC, a STREAM frame may be received with no data but with FIN bit
set. This situation is tedious to handle and haproxy parsing code has
changed several times to deal with this situation. Now, H3 and H09
layers parsing code are skipped in favor of the shared function
qcs_http_handle_standalone_fin() used to handle the HTX EOM emission.

However, this shortcut bypasses an important HTTP/3 validation check on
the received body size vs the announced content-length header. Under
some conditions, this could cause a desynchronization with the backend
server which could be exploited for request smuggling.

Fix HTTP/3 parsing code by adding a call to h3_check_body_size() prior
to qcs_http_handle_standalone_fin() if content-length header has been
found. If the body size is incorrect, the stream is immediately resetted
with H3_MESSAGE_ERROR code and the error is forwarded to the stream
layer.

Thanks to Martino Spagnuolo for his detailed report on this issue and
for having contacting us about it via the security mailing list.

This must be backported up to 2.6.
2026-03-19 10:38:46 +01:00
Aleksandar Lazic
4e57516c9a OPTIM: haterm: use chunk builders for generated response headers
hstream_build_http_resp() currently uses snprintf() to build the
status code and the generated X-req/X-rsp header values.

These strings are short and are fully derived from already parsed request
state, so they can be assembled directly in the HAProxy trash buffer using
`chunk_strcat()` and `ultoa_o()`.

This keeps the generated output unchanged while removing the remaining
`snprintf()` calls from the response-building path.

No functional change is expected.

Signed-off-by: Aleksandar Lazic <al-haproxy@none.at>
2026-03-19 07:42:33 +01:00
Willy Tarreau
e31640368a BUG/MINOR: mux-h2: properly ignore R bit in WINDOW_UPDATE increments
The window size increments are 31 bits and the topmost bit is reserved
and should be ignored, however it was not masked, so a peer sending it
set would emit a negative value which could actually reduce the current
window instead of increasing it. Note that the window cannot reach zero
as there's already a test for this, but transfers could slow down to
the same speed as if an initial window of just a few bytes had been
advertised. Let's just mask the reserved bit before processing.

This should be backported to all stable versions.
2026-03-19 07:21:47 +01:00
Willy Tarreau
0e231bbd7c BUG/MINOR: mux-h2: properly ignore R bit in GOAWAY stream ID
The stream ID indicated in GOAWAY frames must have its bit 31 (R) ignored
and this wasn't the case. The effect is that if this bit was present, the
GOAWAY frame would mark the last acceptable stream as negative, which is
the default situation (unlimited), thus would basically result in this
GOAWAY frame to be ignored since it would replace a negative last_sid
with another negative one. The impact is thus basically that if a peer
would emit anything non-zero in the R bit, the GOAWAY frame would be
ignored and new streams would still be initiated on the backend, before
being rejected by the server.

Thanks to Haruto Kimura (Stella) for finding and reporting this bug.

This fix needs to be backported to all stable versions.
2026-03-19 07:11:54 +01:00
Willy Tarreau
1696cfaa19 BUG/MEDIUM: peers: enforce check on incoming table key type
The key type received over the peers protocol is not checked for
validity and as a result can crash the process when passed through
peer_int_key_type[] in peer_treat_definemsg(). The risk remains
very low since only trusted peers may exchange tables, however it
represents a risk the day haproxy supports new key types, because
mixing old and new versions could then cause the old ones to crash.
Let's add the required check in peer_treat_definemsg().

It is also worth noting that in this function a few protocol identifiers
of type int read directly from a var_int via intdecode() and that some
protocol aliasing may occur (e.g. table_id, table_id_len etc). This is
not supposed to be a problem but it could hide implementation bugs and
cause interoperability issues once fixed, so these should be addressed
in a future commit that will not be marked for backporting.

Thanks to Haruto Kimura (Stella) for finding and reporting this bug.

This fix needs to be backported to all stable versions.
2026-03-19 07:03:10 +01:00
William Lallemand
c6221db375 BUG/MINOR: mworker: don't try to access an initializing process
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
In pcli_prefix_to_pid(), when resolving a worker by absolute pid
(@!<pid>) or by relative pid (@1), a worker that still has PROC_O_INIT
set (i.e. not yet ready, still initializing) could be returned as a
valid target.

During a reload, if a client connects to the master CLI and sends a
command targeting a worker (e.g. @@1 or @@!<pid>), the master resolves
the target pid and attempts to forward the command by transferring a fd
over the worker's sockpair. If the worker is still initializing and has
not yet sent its READY signal, its end of the sockpair is not usable,
causing send_fd_uxst() to fail with EPIPE. This results in the
following alert being repeated in a loop:

  [ALERT] (550032) : socketpair: Cannot transfer the fd 13 over sockpair@5. Giving up.

The situation is even worse if the initializing worker has already
exited (e.g. due to a bind failure) but has not yet been removed from
the process list: in that case the sockpair's remote end is already
closed, making the failure immediate and unrecoverable until the dead
worker is cleaned up.

This was not possible before 3.1 because the master's polling loop only
started once all workers were fully ready, making it impossible to
receive CLI connections while a worker was still initializing.

Fix this by skipping workers with PROC_O_INIT set in both the absolute
and relative pid resolution paths of pcli_prefix_to_pid(), so that
only fully initialized workers can be targeted.

Must be backported to 3.1 and later.
2026-03-18 17:08:30 +01:00
Willy Tarreau
b93137ce67 MINOR: debug: opportunistically load libthread_db.so.1 with set-dumpable=libs
When loading libs into the core dump, let's also try to load
libthread_db.so.1 that gdb usually requires. It can significantly help
decoding the threads for systems which require it, and the file is quite
small. It can appear at a few different locations and is generally next
to libpthread.so, or alternately libc, so we first look where we found
them, and fall back to a few other common places. The file is really
small, a few tens of kB usually.
2026-03-18 15:30:39 +01:00
Willy Tarreau
e07c9ee575 MINOR: debug: copy debug symbols from /usr/lib/debug when present
When set-dumpable=libs, let's also pick the debug symbols for the libs
we're loading. For now we only try /usr/lib/debug/<path>, which is quite
common and easy to guess. Build IDs could also be used but are more
complex to deal with, so let's stay simple for now.
2026-03-18 15:30:39 +01:00
Willy Tarreau
de4f7eaeed DEV: gdb: add a new utility to extract libs from a core dump: libs-from-core
This utility takes in argument the path to a core dump, and it looks
for the archive signature of libraries embedded with "set-dumpable libs",
and either emits the offset and size of stdout, or directly dumps the
contents so that the tar file can be extracted directly by piping the
output to tar xf.
2026-03-18 15:30:39 +01:00
Willy Tarreau
e1738b665d MINOR: debug: read all libs in memory when set-dumpable=libs
When "set-dumpable" is set to "libs", in addition to marking the process
dumpable, haproxy also reads the binary and shared objects into memory as
a tar archive in a page-aligned location so that these files are easily
extractable from a future core dump. The goal here is to always have
access to the exact same binary and libs as those which caused the core
to happen. It's indeed very frequent to miss some of these, or to get
mismatching files due to a local update that didn't experience a reload,
or to get those of a host system instead of the container.

The in-memory tar file presents everything under a directory called
"core-%d" where %d corresponds to the PID of the worker process. In
order to ease the finding of these data in the core dump, the memory
area is contiguous and surrounded by PROT_NONE pages so that it appears
in its own segment in the core file. The total size used by this is a
few tens of MB, which is not a problem on large systems.
2026-03-18 15:30:39 +01:00
Willy Tarreau
6152a4eef5 MINOR: config: support explicit "on" and "off" for "set-dumpable"
The global "set-dumpable" keyword currently is only positional. Let's
extend its syntax to support arguments. For now we support both "on"
and "off" to explicitly enable or disable it.
2026-03-18 15:30:39 +01:00
Willy Tarreau
94a4578ccf MINOR: tools: add a function to load a file into a tar archive
New function load_file_into_tar() concatenates a file into an in-memory
tar archive and grows its size. Only the base name and a provided prefix
are used to name the faile. If the file cannot be loaded, it's added as
size zero and permissions 0 to show that it failed to load. This will
be used to load post-mortem information so it needs to remain simple.
2026-03-18 15:30:39 +01:00
Willy Tarreau
c1dfea3ab3 MINOR: tools: add a function to create a tar file header
The purpose here is to create a tar file header in memory from a known
file name, prefix, size and mode. It will be used to prepare archives
of libs in use for improved debugging, but may probably be useful for
other purposes due to its simplicity.
2026-03-18 15:30:34 +01:00
Christopher Faulet
15cdcab1fc BUILD: spoe: Remove unsused variable
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
Since 7a1382da7 ("BUG/MINOR: spoe: Fix condition to abort processing on
client abort"), the chn variable is no longer used in
spoe_process_event(). Let's remove it

This patch must be backported with the commit above, as far as 3.1.
2026-03-18 11:28:33 +01:00
Christopher Faulet
7a1382da79 BUG/MINOR: spoe: Fix condition to abort processing on client abort
The test to detect client aborts in the SPOE, introduced by commit b3be3b94a
("BUG/MEDIUM: spoe: Properly abort processing on client abort"), was no
correct. Producer flags must not be tested. Only the frontend SC must be
tested when the abortonclose option is set.

Because of this bug, when a client aborted, the SPOE processing was aborted
too, regardless the abortonclose option.

This patch must be backpoeted with the commit above, so as far as 3.1.
2026-03-18 11:24:49 +01:00
Aurelien DARRAGON
8fe0950511 MINOR: promex: export "haproxy_sticktable_local_updates" metric
haproxy_sticktable_local_updates corresponds to the table->localupdate
counter, which is used internally by the peers protocol to identify
update messages in order to send and ack them among peers.

Here we decide to expose this information, as it is already the case in
"show peers" output, because it turns out that this value, which is
cumulative and grows in sync with the number of updates triggered on the
table due to changes initiated by the current process, can be used to
compute the update rate of the table. Computing the update rate of the
table (from the process point of view, ie: updates sent by the process and
not those received by the process), can be a great load indicator in order
to properly scale the infrastructure that is intended to handle the
table updates.

Note that there is a pitfall, which is that the value will eventually
wrap since it is stored using unsigned 32bits integer. Scripts or system
making use of this value must take wrapping into account between two
readings to properly compute the effective number of updates that were
performed between two readings. Also, they must ensure that the "polling"
rate between readings is small enough so that the value cannot wrap behind
their back.
2026-03-18 11:18:37 +01:00
Aurelien DARRAGON
4319c20363 MEDIUM: stats-file/clock: automatically update now_offset based on shared clock
We no longer rely on now_offset stored in the shm-stats-file. Instead
haproxy automatically computes the now_offset relative to the monotonic
clock and the shared global clock.

Indeed, the previous model based on static now_offset when monotonic
clock is available proved to be insufficient when used in
combination with shm-stats-file (that is when monotonic clock is shared
between multiple co-processes). In ideal situation co-processes would
correctly apply the offset to their local monotonic clock and end up
with consistent now_ns. But when restarting from an existing
shm-stats-file from a previous session (ie: prior to reboot), then the
local monotonic clock would no longer be consistent with the one used
to update the file previously, so applying a static offset would fail
to restore clock consistency.

For this specific issue, a workaround was brought by 09bf116
("BUG/MEDIUM: stats-file: detect and fix inconsistent shared clock when resuming from shm-stats-file")
but the solution implemented there was deemed too fragile, because there
is a 60sec window where the fix would fail to detect inconsistent clock
and would leave haproxy with a broken clock ranging from 0 to 60 seconds,
which can be huge..

By simply recomputing the now_offset each time we learn from another
process (through the shared map by reading global_now_ns), we simply
recompute our local offset (difference between OUR monotonic clock
and the SHARED one). Also, in clock_update_global_date(), we make
sure we always recompute the now_offset as now_ms may have been
updated from shared clock if shared clock was ahead of us.

Thanks to that new logic, interrupted processes, resumed processes,
processed started with shm-stats-file from previous session now
correctly recover from those various situations and multiple
co-processes with diverting clocks on startup end up converging to
the same values.

Since it is no longer relevant to save now_offset in the map, it was
removed but to prevent shm-stats-file incompatibility with previous
versions, 8-byte hole was forced, and we didn't bump the shm-stats-file
version on purpose.

This patch may be backported in 3.3 after a solid period of observation
to ensure we didn't break things.
2026-03-18 11:18:33 +01:00
William Lallemand
29592cb330 BUG/MINOR: mjson: make mystrtod() length-aware to prevent out-of-bounds reads
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
mystrtod() was not length-aware and relied on null-termination or a
non-numeric character to stop. The fix adds a length parameter as a
strict upper bound for all pointer accesses.

The practical impact in haproxy is essentially null: all callers embed
the JSON payload inside a large haproxy buffer, so the speculative read
past the last digit lands on memory that is still within the same
allocation. ASAN cannot detect it in a normal haproxy run for the same
reason — the overread never escapes the enclosing buffer. Triggering a
detectable fault requires placing the JSON payload at the exact end of
an allocation.

Note: the 'path' buffer was using a null-terminated string so the result
of strlen is passed to it, this part was not at risk.

Thanks to Kamil Frankowicz for the original bug report.

This patch must be backported to all maintained versions.
2026-03-17 17:08:28 +01:00
Christopher Faulet
8dae4f7c0b BUG/MINOR: stream: Fix crash in stream dump if the current rule has no keyword
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
The commit 9f1e9ee0e ("DEBUG: stream: Display the currently running rule in
stream dump") revealed a bug. When a stream is dumped, if it is blocked on a
rule, we must take care the rule has a keyword to display its name.

Indeed, some action parsings are inlined with the rule parser. In that case,
there is no keyword attached to the rule.

Because of this bug, crashes can be experienced when a stream is
dumped. Now, when there is no keyword, "?" is display instead.

This patch must be backported as far as 2.6.
2026-03-17 08:39:49 +01:00
Christopher Faulet
ef2a292585 BUG/MINOR: http-ana: Swap L7 buffer with request buffer by hand
When a L7 retry is performed, we should not rely on b_xfer() to swap the L7
buffer with the request buffer. When it is performed the request buffer is
not allocated. b_xfer() must not be called with an unallocated destination
buffer. The swap remains an optim. For instance, It is not performed on
buffers of different size. So the caller is responsible to provide an
allocated destination buffer with enough free space to transfer data.

However, when a L7 retry is performed, we cannot allocate a request buffer,
because we cannot yield. An error was reported, if we wait for a buffer, the
error will be handled by process_stream(). But we can swap the buffers by
hand. At this stage, we know there is no request buffer, so we can easily
swap it with the L7 buffer.

Note there is no real bug for now.

This patch could be backported to all stable versions.
2026-03-17 07:48:02 +01:00
Christopher Faulet
ba7dc46a92 BUG/MINOR: h2/h3: Never insert partial headers/trailers in an HTX message
In HTX, headers and trailers parts must always be complete. It is unexpected
to found header blocks without the EOH block or trailer blocks without the
EOT block. So, during H2/H3 message parsing, we must take care to remove any
HEADER/TRAILER block inserted when an error is encountered. It is mandatory
to be sure to properly report parsing error to upper layer.x

It is now performed by calling htx_truncat_blk() function on the error
path. The tail block is saved before converting any HEADERS/TRAILERS frame
to HTX. It is used to remove all inserted block on error.

This patch rely on the following one:

  "MINOR: htx: Add function to truncate all blocks after a specific block"

It should be backported with the commit above to all stable versions for
the H2 part and as far as 2.8 for h3 one.
2026-03-17 07:48:02 +01:00
Christopher Faulet
fbdb0a991a MINOR: htx: Add function to truncate all blocks after a specific block
htx_truncated_blk() function does the same than htx_trunctate(), except data
are truncated relatively to a block in the message instead of an offset.
2026-03-17 07:48:02 +01:00
Christopher Faulet
3250ec6e9c BUG/MINOR: h2/h3: Only test number of trailers inserted in HTX message
When H2 or H3 trailers are inserted in an HTX message, we must take care to
not exceed the maximum number of trailers allowed in a message (same than
the maximum number of headers, i.e tune.http.maxhdr). However, all HTX
blocks in the HTX message were considered. Only TRAILERS HTX blocks must be
considered.

To fix the issue, in h2_make_htx_trailers(), we rely on the "idx" variable
at the end of the for loop. In h3_trailers_to_htx(), we rely on the
"hdr_idx" variable.

This patch must be backported to all stables versions for the H2 part and as
far as 2.8 for the H3 one.

pouet
2026-03-17 07:48:02 +01:00
Christopher Faulet
9c0aeb3af4 BUG/MEDIUM: stconn: Don't perform L7 retries with large buffer
L7 retries are buggy when a large buffer is used on the request channel. A
memcpy is used to copy data from the request buffer into the L7 buffer. The
L7 buffer is for now always a standard buffer. So if a larger buffer is
used, this leads to a buffer overflow and crash the process.

The Best way to fix the issue is to disable L7 retries when a large buffer
was allocated for the request channel. In that case, we don't want to
allocate an extra large buffer.

No backport needed.
2026-03-17 07:48:02 +01:00
Christopher Faulet
cd91838042 BUG/MEDIUM: stconn: Fix abort on close when a large buffer is used
When a large buffer is used on a channel, once we've started to send data to
the opposite side, receives are blocked temporarily to be sure to flush the
large buffer ASAP to be able to fall back on regular buffers. This was
performed by skipping call to the endpoint (connection or applet). Howerver,
doing so, this broken the abortonclose and more generally this masked any
shut or error events reported by the lower layer.

To fix the issue, instead of skipping receives, we now try a receive but
with a requested size set to 0.

No backport needed
2026-03-17 07:48:01 +01:00
Christopher Faulet
b3be3b94a0 BUG/MEDIUM: spoe: Properly abort processing on client abort
Client abort when abortonclose is configured was ignored when messges were
sent on event while it works properly when messages are sent via an
"send-spoe-group" action.

To fix the issue, when the SPOE filter is waiting for the SPOE applet
response, it must check if a client abort was reported and if so, must
interrupt its processing.

This patch should be backported as far as 3.1.
2026-03-17 07:48:01 +01:00
Christopher Faulet
d10fc3d265 BUG/MINOR: spoe: Properly switch SPOE filter to WAITING_ACK state
When the SPOE applet is created, the SPOE filter is set in SENDING_MSGS
state. When the applet has transferred data, it should switch the filter to
WAITING_ACK state. Concretly, there is no bug. At best, it could save some
useless applet wakeups.

This patch should be backported as far as 3.1
2026-03-17 07:47:52 +01:00
Christopher Faulet
00bea05a14 BUG/MEDIUM: stconn: Don't forget to wakeup applets on shutdown
When SC's shudown callback functions were merged, a regression was
introduced. The applet was no longer woken up. Because of this bug, an
applet could remain blocked, waiting for an I/O event or a timeout.

This patch should fix the issue #3301.

No backport needed.
2026-03-17 07:38:57 +01:00
William Lallemand
ab7acdcc3a BUG/MINOR: sockpair: set FD_CLOEXEC on fd received via SCM_RIGHTS
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
FDs received through recv_fd_uxst() do not have FD_CLOEXEC set.
The equivalent sock_accept_conn() already handles this correctly:
any FD accepted or received in the master must be marked close-on-exec
to avoid leaking it across the execvp() performed on soft-reload.

This is currently triggering a leak in the master since 3.1: the worker
sends a socketpair fd to the master  to issue the _send_status CLI
command, and recv_fd_uxst() receive it without setting FD_CLOEXEC.  If a
re-exec is emitted before the master had the chance to close that fd, it
survives execvp() and appears as an untracked unnamed AF_UNIX socket in
the new master generation.

This must be backported to all maintained branches.
2026-03-16 16:31:58 +01:00
William Lallemand
a3bf0de651 BUG/MINOR: mworker: avoid passing NULL version in proc list serialization
Some checks failed
Contrib / build (push) Has been cancelled
alpine/musl / gcc (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
Add a NULL guard for the version field. This has no functional impact
since the master process never uses this field for its own mworker_proc
element, and should be the only one impacted. This avoid seeing "(null)"
in the version field when debugging.

Must be backported to 3.1 and later.
2026-03-13 20:26:53 +01:00
William Lallemand
51d6f1ca4f BUG/MINOR: mworker: set a timeout on the worker socketpair read at startup
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
During a soft reload, a starting worker sends sock_pair[0] to the master
via send_fd_uxst(), then reads on sock_pair[1] waiting for the master to
acknowledge receipt. Because of a documented macOS sendmsg(2) bug, the
worker must keep sock_pair[0] open until the master confirms the fd was
received by the CLI applet. This means the read() on sock_pair[1] will
never return 0 (EOF), since the worker itself still holds a reference to
sock_pair[0]. The worker can only unblock when the master actively sends
a byte back. If the master crashes before doing so, the worker blocks
indefinitely in read().

Fix this by setting a 2-second SO_RCVTIMEO on sock_pair[1] before the
read(), so the worker can unblock and continue regardless of the master's
state.

This was introduced by d7f6819161 ("BUG/MEDIUM: mworker: fix startup
and reload on macOS").

This should be backported to 3.1 and later.
2026-03-13 18:45:58 +01:00
William Lallemand
cb51c8729d BUG/MINOR: mworker: fix typo &= instead of & in proc list serialization
In mworker_proc_list_to_env(), a typo used '&=' instead of '&' when
checking PROC_O_TYPE_WORKER in child->options. This would corrupt the
options field by clearing all bits except PROC_O_TYPE_WORKER, but since
the function is called right before the master re-execs itself during a
reload, the corruption has no actual effect: the in-memory proc_list is
discarded by the exec, and the options field is not serialized to the
environment anyway.

This should be backported to all maintained versions.
2026-03-13 18:38:24 +01:00
Maxime Henrion
a390daaee4 MINOR: traces: defer processing of "-dt" options
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
We defer processing of the "-dt" options until after the configuration
file has been read. This will be useful if we ever allow trace sources
to be registered later, for instance with LUA.

No backport needed.
2026-03-13 09:13:24 +01:00
William Lallemand
d172f7b923 BUG/MINOR: mworker: only match worker processes when looking for unspawned proc
In master-worker mode, when a freshly forked worker looks up its own
entry in proc_list to send its "READY" status to the master, the loop
was breaking on the first process with pid == -1 regardless of its
type. If a non-worker process (e.g. a master or program) also had
pid == -1, the wrong entry could be selected, causing send_fd_uxst()
to use an invalid ipc_fd.

Fix this by adding a PROC_O_TYPE_WORKER check to the loop condition,
and add a BUG_ON() assertion to catch any case where the loop exits
without finding a valid worker entry.

Must be backported to 3.1.
2026-03-13 09:13:11 +01:00
Willy Tarreau
4e8cf26ab6 DOC: internals: short explanation on how thread_exec_ctx works
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
The goal is to have enough info to be able to automatically enable the
feature on future rulesets or subsystems.
2026-03-12 18:28:09 +01:00
Willy Tarreau
f7820bcbaa MINOR: activity: raise the default number of memprofile buckets to 4k
It was set to 1k by default but with the refinement of exec_ctx it's
becoming short, so let's raise it now.
2026-03-12 18:06:38 +01:00
Willy Tarreau
892adf3cc1 MINOR: activity: support aggregating by caller also for memprofile
"show profiling" supports "aggr" for tasks but it was ignored for
memory. Now that we're having many more entries, it makes sense to
have it to ignore the call path and merge similar operations.
2026-03-12 18:06:38 +01:00
Willy Tarreau
17cbec485a MINOR: cli: implement execution context for manually registered keywords
Keywords registered out of an initcall will have a TH_EX_CTX_CLI_KWL
execution context pointing to the keyword list. The report will indicate
the 5 first words of the first command of the list, e.g.:

     exec_ctx: cli kwl starting with 'debug counters   '

This should also work for CLI keywords registered in Lua.
2026-03-12 18:06:38 +01:00
Willy Tarreau
5cd71f69ba MINOR: cli: keep track of the initcall context since kw registration
Now CLI keywords registered via an initcall will be tracked during
execution, by keeping a link to their initcall location. "show threads"
now shows "exec_ctx: kw registered at @debug.c:3093" which indeed
corresponds to the initcall for the debugging commands.
2026-03-12 18:06:38 +01:00
Willy Tarreau
8139795c64 MINOR: cli: keep the info of the current keyword being processed in the appctx
Till now the CLI didn't know what keyword was being processed after it
was parsed. In order to report the execution context, we'll need to
store it. And this may even help for post-mortem analysis to know the
exact keyword being processed, so let's store the pointer in the cli_ctx
part of the appctx.
2026-03-12 18:06:38 +01:00
Willy Tarreau
9cb11d0859 MINOR: applet: set execution context on applet calls
It allows to know when a thread is currnetly running inside an applet.
For example now "show threads" will show "applet '<CLI>'" for the thread
issuing this command.
2026-03-12 18:06:38 +01:00
Willy Tarreau
c0bf395cde MINOR: task: set execution context on task/tasklet calls
It now appears almost everywhere due to callbacks (e.g. ssl_sock_io_cb).
Muxes also become visible now on memory profiling. A small test on h1+ssl
yields 838 lines of statistics. The number of buckets should definitely
be increased, and more grouping criteria should be added.

A performance test was conducted to observe the possible effect of
setting the execution context on each task switch, and it didn't change
at all, remaining at about 1.01 billion ctxsw/s on a 128-thread EPYC.
2026-03-12 18:06:38 +01:00
Willy Tarreau
ec7b07b650 MINOR: connection: track mux calls to report their allocation context
Most calls to mux ops were instrumented with a CALL_MUX_WITH_RET() or
CALL_MUX_NO_RET() macro in order to make the current thread's context
point to the called mux and be able to track its allocations. Only
a bunch of harmless mux_ctl() and ->subscribe/unsubscribe calls were
left untouched since useless. But destroy/detach/shut/init/snd_buf
and rcv_buf are now tracked.

It will not show allocations performed in IO callback via tasklet
wakeups however.

In order to ease reading of the output, cmp_memprof_ctx() knows about
muxes and sorts based on the .subscribe function address instead of
the mux_ops address so as to keep various callers grouped.
2026-03-12 18:06:38 +01:00
Willy Tarreau
e8e4449985 MINOR: ssl: set the thread execution context during message callbacks
In order to be able to track memory allocation performed from message
callbacks, let's set the thread execution context to a generic function
pointing to them during their call. This allows for example to observe
the share of SSL allocations caused by ssl_sock_parse_clienthello() when
SSL captures are enabled.

The release calls are automatic from the SSL library for these, and are
registered directly via SSL_get_ex_new_index(). Maybe we should improve
the internal API to wrap that function and systematically track free
calls as well. In this case, maybe even registering the message callback
registration could take both the callback and the release function.
There are few such users however, essentially capture and keylog.
2026-03-12 18:06:38 +01:00
Willy Tarreau
3fb8659d04 MINOR: filters: set the exec context to the current filter config
Doing this allows to report the allocations/releases performed by filters
when running with memory profiling enabled. The flt_conf pointer is kept
and the report shows the filter name.
2026-03-12 18:06:38 +01:00
Willy Tarreau
43b56c22c7 MINOR: actions: also report execution contexts registered directly
This now reports directly registered actions using new type
TH_EX_CTX_ACTION which will report the first keyword of the
list.
2026-03-12 18:06:38 +01:00
Willy Tarreau
861d1111c3 MINOR: actions: store the location of keywords registered via initcalls
A bit similar to what was done for sample fetch functions and converters,
we now store with each action keyword the location of the initcall when
they're registered this way. Since there are many functions only calling
a LIST_APPEND() (one per ruleset), we now implement a dedicated function
to store the context in all keywords before doing the append.

However that's not sufficient, because keywords are not mandatory for
actions, so we cannot safely rely on rule->kw. Thus we then set the
exec_ctx per rule when they are all scanned in check_action_rules(),
based on the keyword if it exists, otherwise we make a context from
the action_ptr function if it is set (it should).

Finally at all call points we now check rule->exec_ctx.
2026-03-12 18:06:38 +01:00
Willy Tarreau
261cae3b6d MINOR: tools: support an execution context that is just a function
The purpose here is to be able to spot certain callbacks, such as the
SSL message callbacks, which are difficult to associate to anything.
Thus we introduce a new context type, TH_EX_CTX_FUNC, for which the
context is just the function pointed to by the void *pointer. One
difficulty with callbacks is that the allocation and release contexts
will likely be different, so the code should be properly structured
to allow proper tracking, either by instrumenting all calls, or by
making sure that the free calls are easy to spot in a report.
2026-03-12 18:06:38 +01:00
Willy Tarreau
aa4d5dd217 MINOR: sample: also report contexts registered directly
With the two new context types TH_EX_CTX_SMPF/CONV, we can now also
report contexts corresponding to direct calls to sample_register_fetches()
and sample_register_convs(). In this case, the first word of the keyword
list is reported.
2026-03-12 18:06:38 +01:00
Willy Tarreau
6e819dc4fa MINOR: sample: store location for fetch/conv via initcalls
Now keywords are registered with an exec_ctx and this one is passed
when calling ->process. The ctx is of type INITCALL when passed via
an initcall where we know the file name and line number.

This was tested with and extra "malloc(15)" added in smp_fetch_path()
which shows that it works:

  $ socat /tmp/sock1 - <<< "show profiling memory"|grep via
           Calls         |         Tot Bytes           |       Caller and method  [via]
      1893399           0       60592592              0|         0x78b2ec task_run_applet+0x3339c malloc(32) [via initcall @http_fetch.c:2416]
2026-03-12 18:06:38 +01:00
Willy Tarreau
2cd0cd84c6 MINOR: tools: support decoding ha_caller type exec context
The TH_EX_CTX_CALLER type takes an ha_caller pointer which allows a
caller to mark its caller's location using MK_CALLER().
2026-03-12 18:06:38 +01:00
Willy Tarreau
6e75da7a91 MINOR: tools: decode execution context TH_EX_CTX_INITCALL
When the execution context is set to TH_EX_CTX_INITCALL, the pointer
points to a valid initcall, and the decoder will show "kw registered
at %s:%d" with file and line number of the initcall declaration. It's
up to the caller to make the initcall pointer point to the one that was
set during the initcall. The purpose here is to be able to preserve and
pass that knowledge of an initcall down the chain so that future calls
to functions registered via the initcall are still assigned to it.
2026-03-12 18:06:38 +01:00
Willy Tarreau
33c928c745 MINOR: initcall: record the file and line declaration of an INITCALL
The INITCALL macros will now store the file and line number where they
are declared into the initcall struct, and RUN_INITCALLS() will assign
them to the global caller_file and caller_line variables, and will even
set caller_initcall to the current initall so that at any instant such
functions know where their caller declared them. This will help with
error messages and traces where a bit of context will be welcome.
2026-03-12 18:06:38 +01:00
Willy Tarreau
3f3a0609e3 MINOR: memprof: report the execution context on profiling output
This leads to the context pointer being reported in "show profiling
memory" when known, as "[via other ctx XXX]" for example.
2026-03-12 18:06:38 +01:00
Willy Tarreau
998ed00729 MINOR: debug: report the execution context on thread dumps
Now we have one extra line saying "exec_ctx: something" in thread dumps
when it's known. It may help with warnings and panics to figure what
is ongoing.
2026-03-12 18:06:37 +01:00
Willy Tarreau
5d3246205b MINOR: tools: add a function to write a thread execution context.
The new function chunk_append_thread_ctx() appends to a buffer the given
execution context based on its type and pointer. The goal is to easily
use it in profiling output and thread dumps. For now it only handles
TH_EX_CTX_NONE (which prints nothing) and TH_EX_CTX_OTHER (which indicates
"other ctx" followed by the pointer). It will be extended by new types as
they arrive.
2026-03-12 18:06:37 +01:00
Willy Tarreau
13c89bf20d MINOR: memprof: also permit to sort output by calling context
By passing "byctx" to "show profiling memory", it's possible to sort by
the calling context first, which could help group certain calls by
subsystem and ease the interpretation of the output.
2026-03-12 18:06:37 +01:00
Willy Tarreau
2dfc8417cf MINOR: memprof: prepare to consider exec_ctx in reporting
This now allows to report the same function in multiple bins based on the
th_ctx's exec_ctx discriminant. It's also worth noting that the context is
not atomically committed, but this shouldn't be a problem since a single
entry can get it. In the worst case, a second thread trying to create the
same context in parallel would create a different bin just for this call,
which is harmless. The same situation already exists with the caller
pointer.
2026-03-12 18:06:37 +01:00
Willy Tarreau
b7c8fab507 MINOR: tinfo: start to add basic thread_exec_ctx
We have the struct made of a type and a pointer in the th_ctx and a
function to switch it for the current thread. Two macros are provided
to enclose a callee within a temporary context. For now only type OTHER
is supported (only a generic pointer).
2026-03-12 18:06:37 +01:00
Willy Tarreau
fb7e5e1696 MINOR: memprof: attempt different retry slots for different hashes on collision
When two pointer hash to the same memprofile bin, we currently try again
with the same bin until we find a spare one or we reach the limit of 16.
Olivier suggested to try with a different step for different pointers so
as to limit the number of bins to visit in such a case, so let's split
the pointer hash calculation so that we keep the raw hash before reduction
and use its lowest bits as the retry step. We force lowest bit to 1 to
avoid integral multiples that would oscillate between only a few positions.

Quick tests with h1+h2 requests show that for ~744 distinct entries, we
used to have 1.17 retries per lookup before and 0.6 now so we're halving
the cost of hash collisions. A heavier workload that used to produce 920
entries with 2.01 retries per lookup now reaches 966 entries (94.3% usage
vs 89.8% before) with only 1.44 retries per lookup.

This should be safe to backport, but depends on this previous commit:

    MINOR: tools: extend the pointer hashing code to ease manipulations
2026-03-12 18:06:37 +01:00
Willy Tarreau
3b4275b072 MINOR: tools: add a new pointer hash function that also takes an argument
The purpose here is to combine two pointers and a long argument instead
of having the caller perform the mixing. Also it's cleaner and more
efficient this was as the arg is mixed after the multiplications, and
modern processors are efficient at multiplying then adding.
2026-03-12 18:06:37 +01:00
Willy Tarreau
825e5611ba MINOR: tools: extend the pointer hashing code to ease manipulations
We'll need to further extend the pointer hashing code to pass extra
parameters and to retrieve the dropped bits, so let's first split the
part that hashes the pointer from the part that reduces the hash to
the desired size.
2026-03-12 18:06:37 +01:00
Willy Tarreau
01457979b6 MINOR: activity: use dynamic allocation for "show profiling" entries
Historically, the data manipulated by "show profiling" were copied
onto the stack for sorting and aggregating, but not only this limits
the number of entries we can keep, but it also has an impact on CPU
usage (having to redo the whole copy+sort upon each resume) and the
output accuracy (if sorting changes lines, resume may happen from an
incorrect one).

Instead, let's dynamically allocate the work buffer and place it into
the service context. We only allocate it immediately before needing it
and release it immediately afterwards so that it doesn't stay long. It
also requires a release handler to release those allocates by interrupted
dumps, but that's all. The overall result is now much cleaner, more
accurate, faster and safer.

This patch may be backported to older LTS releases.
2026-03-12 18:06:37 +01:00
Willy Tarreau
07655da068 BUG/MINOR: proxy: do not forget to validate quic-initial rules
In check_config_validity() and proxy_finalize() we check the consistency
of all rule sets, but the quic_initial rules were not placed there. This
currently has little to no impact, however we're going to use that to
also finalize certain debugging info so better call the function. This
can be backported to 3.1 (proxy_finalize is 3.4-only).
2026-03-12 18:06:37 +01:00
Willy Tarreau
ed44adc3ca BUG/MINOR: memprof: avoid a small memory leak in "show profiling"
In 3.1, per-DSO statistics were added to the memprofile output by
commit 401fb0e87a ("MINOR: activity/memprofile: show per-DSO stats").
However an strdup() is performed there on the .info field, that is
never freed when leaving the function. Let's do it each time we leave
it. Ironically, this was found thanks to "show profiling" showing
itself as an unbalanced caller of strdup().

This needs to be backported to 3.0 since that commit was backported
there.
2026-03-12 18:06:37 +01:00
Willy Tarreau
4d5a91b8af BUILD: makefile: fix range build without test command
In 3.3, the "make range" target adopted a test command via the TEST_CMD
variable, with commit 90b70b61b1 ("BUILD: makefile: implement support
for running a command in range"). However now it breaks the script when
TEST_CMD is not set due to the shell expansion leaving two '||' operators
side by side. Let's fix this by passing the contents of the makefile
variable in positional arguments before executing them.
2026-03-12 18:06:37 +01:00
Olivier Houchard
4102461dd6 BUG/MEDIUM: ssl: Don't report read data as early data with AWS-LC
To read early data with AWS-LC (and BoringSSL), we have to use
SSL_read(). But SSL_read() will also try to do the handshake if it
hasn't been done yet, and at some point will do the handshake and will
return data that are actually not early data. So use SSL_in_early_data()
to make sure that the data we received are actually early data, and only
if so add the CO_FL_EARLY_DATA flag. Otherwise any data first received will be
considered early, and a Early-data header will be added.
As this bug was introduced by 76ba026548,
it should be backported with it.
2026-03-12 17:31:12 +01:00
William Lallemand
13d13691b5 BUG/MINOR: mworker: always stop the receiving listener
Upon _send_status, always stop the listener from which the request
was received, rather than looking it up from the proc_list entry via
fdtab[proc->ipc_fd[0]].owner.

A BUG_ON is added to verify that the listener which received the
request is the one expected for the reported PID.

This means it is no longer possible to send "_send_status READY XXX"
manually through the master CLI for testing, as that would trigger
the BUG_ON.

Must be backported as far as 3.1.
2026-03-12 17:29:50 +01:00
Olivier Houchard
76ba026548 BUG/MEDIUM: ssl: Handle receiving early data with BoringSSL/AWS-LC
The API for early data is a bit different with BoringSSL and AWS-LC than
it is for OpenSSL. As it was implemented, early data would be accepted,
but would not be processed until the handshake is done. Change that by
doing something similar to what OpenSSL does, and, if 0RTT has been
enabled on the listener, use SSL_read() to try to get early data before
starting the handshake, and if there's any, provide them to the mux the
same way it is done for OpenSSL.
That replaces a bunch of #ifdef SSL_READ_EARLY_DATA_SUCCESS by
something specific to OpenSSL has to be done.
This should be backported to 3.3.
2026-03-12 14:14:51 +01:00
Egor Shestakov
f24ed2a5d1 DOC/CLEANUP: config: update mentions of the old "Global parameters" section
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
The name of "Global section" was changed only in the summary, not in the
text itself. The names of some related refs were also updated.

Should be backported as far as 3.2.
2026-03-12 09:25:01 +01:00
Tom Braarup
b837b2b86c DOC: configuration: http-check expect example typo
On the http-check expect example
(https://docs.haproxy.org/dev/configuration.html#4.2-http-check%20expect)
there is a typo

-http-check expect header name "set-cookie" value -m beg "sessid="
+http-check expect hdr name "set-cookie" value -m beg "sessid="
2026-03-12 09:20:32 +01:00
Mia Kanashi
b6e28bb4d7 BUG/MINOR: jws: fix memory leak in jws_b64_signature
EVP_MD_CTX is allocated using EVP_MD_CTX_new() but was never freed.
ctx should be initialized to NULL otherwise EVP_MD_CTX_free(ctx) could
segfault.

Must be backported as far as 3.2.
2026-03-12 09:18:42 +01:00
Tim Duesterhus
760fef1fc0 BUG/MINOR: tcpcheck: Fix typo in error error message for http-check expect
With a config:

    backend bk_app
    	http-check expect status 200 string "status: ok"

This now correctly emits the error:

    config : parsing [./patch.cfg:2] : 'http-check expect' : only one pattern expected.

This line containing the typo is unchanged since at least HAProxy 2.2, the
patch should be backported into all supported branches.
2026-03-12 09:10:45 +01:00
William Lallemand
73732abfb2 BUILD: ssl: make X509_NAME usage OpenSSL 4.0 ready
Starting with OpenSSL 4.0, X509_get_subject_name(), X509_get_issuer_name(),
and X509_CRL_get_issuer() return a const-qualified X509_NAME pointer.
Similarly, X509_NAME_get_entry() returns a const X509_NAME_ENTRY *, and
X509_NAME_ENTRY_get_data() returns a const ASN1_STRING *.

Introduce the __X509_NAME_CONST__ macro (defined to 'const' for OpenSSL
>= 4.0.0, empty for WolfSSL and older OpenSSL version which lacks const
on these APIs) and use it to qualify X509_NAME * variables and the
parameters of the three DN helper functions ssl_sock_get_dn_entry(),
ssl_sock_get_dn_formatted(), and ssl_sock_get_dn_oneline(). This avoids
both const-qualifier warnings on OpenSSL 4.0 and discarded-qualifier
warnings on WolfSSL, without needing explicit casts at call sites.

In ssl_sock.c (ssl_get_client_ca_file) and ssl_gencert.c
(ssl_sock_do_create_cert), a __X509_NAME_CONST__ X509_NAME * variable was
being reused to store the result of X509_NAME_dup() and then passed to
mutating functions (X509_NAME_add_entry_by_txt, X509_NAME_free). Introduce
separate X509_NAME * variables (xn_dup, subject) to hold the mutable
duplicate.

Original patch from Alexandr Nedvedicky <sashan@openssl.org>:
https://www.mail-archive.com/haproxy@formilux.org/msg46696.html
2026-03-11 17:00:59 +01:00
William Lallemand
e82f03dd88 BUILD: ssl: use ASN1_STRING accessors for OpenSSL 4.0 compatibility
In OpenSSL 4.0, the ASN1_STRING struct was made opaque and direct access
to its members (->data, ->length, ->type) no longer compiles. Replace
these accesses in ssl_sock_get_serial(), ssl_sock_get_time(), and
asn1_generalizedtime_to_epoch() with the proper accessor functions
ASN1_STRING_get0_data(), ASN1_STRING_length(), and ASN1_STRING_type().

The old direct access is preserved under USE_OPENSSL_WOLFSSL since
WolfSSL does not provide these accessor functions.

Original patch from Alexandr Nedvedicky <sashan@openssl.org>:
https://www.mail-archive.com/haproxy@formilux.org/msg46696.html
2026-03-11 16:59:54 +01:00
William Lallemand
6d14fd0b29 MEDIUM: mworker: exiting when couldn't find the master mworker_proc element
Some checks failed
Contrib / build (push) Has been cancelled
alpine/musl / gcc (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
When a master process is reloading, the HAPROXY_PROCESSES variable is
deserialized. In older version of the master-worker (< 1.9), no master
element was existing in this variable.

This is not suppose to happen anymore, and could have provoked problems
in the master anyway.

This patch changes the behavior by exiting the master with an alert if
mp master element was found in this variable.
2026-03-10 15:57:21 +01:00
Christopher Faulet
00563233b7 DEBUG: stconn: Add a CHECK_IF() when I/O are performed on a orphan SC
When no endpoint is attached to a SC, it is unexpected to have I/O (receive
or send). But we honestly don't know if it happens or not. So a CHECK_IF()
is added to be able to track such calls.
2026-03-10 15:10:34 +01:00
Christopher Faulet
b2b0d1a8be MINOR: stconn: Simplify sc_abort/sc_shutdown by merging calls to se_shutdown
Calls to se_shutdown were no the same between applets and mux endpoints.
Only the SHUTW flag was not the same. However, on the multiplexers are
sensitive to the true SHUTW flag. The applets handle all of them the same
way. So calls to se_shutdown() from sc_abort() and sc_shutdown() can be
merged to always use the multiplexer version.
2026-03-10 15:10:34 +01:00
Christopher Faulet
fb1bc592f5 MINOR: stconn: Totally app_ops from the stconns
The stconn app_ops structure is now empty and can be safely removed. So let's do
so.
2026-03-10 15:10:34 +01:00
Christopher Faulet
990456462f MINOR: stconn: Remove .shutdown() callback functions
These callback functions are no longer used, so they can safely be
removed. In addition, the field was removed from the app_ops structure.
2026-03-10 15:10:34 +01:00
Christopher Faulet
c65526ad57 MEDIUM: stconn: Merge all .shutdown() callback functions in sc_shutdown()
sc_shutdown() is no longer relying on .shutdown() callback functions.
Everything was merged in sc_shutdown() with a test on the app type.
2026-03-10 15:10:34 +01:00
Christopher Faulet
9dfff87b69 MINOR: stconn: Remove .abort() callback functions
These callback functions are no longer used, so they can safely be
removed. In addition, the field was removed from the app_ops structure.
2026-03-10 15:10:34 +01:00
Christopher Faulet
0fc6884bc7 MEDIUM: stconn: Merge all .abort() callback functions in sc_abort()
sc_abort() is no longer relying on .abort() callback functions.  Everything
was merged in abort() with a test on the app type.
2026-03-10 15:10:34 +01:00
Christopher Faulet
0c9741b70a MINOR: stconn: Remove .chk_snd() callback functions
These callback functions are no longer used, so they can safely be
removed. In addition, the field was removed from the app_ops structure.
2026-03-10 15:10:34 +01:00
Christopher Faulet
e33dfc4f26 MEDIUM: stconn: Merge all .chk_snd() callback functions in sc_chk_snd()
sc_chk_snd() is no longer relying on .chk_snd() callback functions.
Everything was merged in sc_chk_snd() with a test on the app type.
2026-03-10 15:10:34 +01:00
Christopher Faulet
5aa67f0587 MINOR: stconn: Remove .chk_rcv() callback functions
These callback functions are no longer used, so they can safely be
removed. In addition, the field was removed from the app_ops structure.
2026-03-10 15:10:34 +01:00
Christopher Faulet
aef7afbe65 MEDIUM: stconn: Merge all .chk_rcv() callback functions in sc_chk_rcv()
sc_chk_rcv() is no longer relying on .chk_rcv() callback functions.
Everything was merged in sc_chk_rcv() with a test on the app type.
2026-03-10 15:10:34 +01:00
Christopher Faulet
7c895092a7 MINOR: stconn: Wakup the SC with TASK_WOKEN_IO state from opposite side
When a SC is woken up by the opposite side, in inter stream-connector calls,
TASK_WOKEN_IO state is now used.
2026-03-10 15:10:34 +01:00
Christopher Faulet
aaa97c4441 MINOR: haterm: Remove hstream_wake() function
This function is no longer used, so it can be safely removed.
2026-03-10 15:10:34 +01:00
Christopher Faulet
d491329de9 MINOR: check: Remove wake_srv_chk() function
wake_srv_chk() function is now only used by srv_chk_io_cb(), the
health-checl I/O callback function. So let's remove it. The code of the
function was moved in srv_chk_io_cb().
2026-03-10 15:10:34 +01:00
Christopher Faulet
9c7c669d7a MEDIUM: stconn: Remove .wake() callback function from app_ops
.wake() callback function is no longer used by endpoints. So it can be
removed from the app_ops structure.
2026-03-10 15:10:34 +01:00
Christopher Faulet
a33b42035b MINOR: connection: Call sc_conn_process() instead of .wake() callback function
At we fail to create a mux, in conn_create_mux(), instead of calling the
app_ops .wake() callback function, we can directly call sc_conn_process().
At this stage, we know we are using an connection, so it is safe to do so.
2026-03-10 15:10:34 +01:00
Christopher Faulet
7be95eb892 MINOR: applet: Call sc_applet_process() instead of .wake() callback function
At the end of task_run_applet() and task_process_applet(), instead of
calling the app_ops .wake() callback function, we can directly call
sc_applet_process(). At this stage, we know we are using an applet, so it is
safe to do so.
2026-03-10 15:10:34 +01:00
Christopher Faulet
64d997ebfc MAJOR: muxes: No longer use app_ops .wake() callback function from muxes
Thanks to previous commits, it is now possible to wake the data layer up,
via a tasklet_wakeup, instead of using the app_ops .wake() callback
function.

When a data layer must be notified of a mux event (an error for instance),
we now always perform a tasklet_wakeup(). TASK_WOKEN_MSG state is used by
default. TASK_WOKEN_IO is eventually added if the data layer was subscribed
to receives or sends.

Changes are not trivial at all. We replaced a synchronous call to the
sc_conn_process() function by a tasklet_wakeup().
2026-03-10 15:10:34 +01:00
Christopher Faulet
26a0817c1a MINOR: muxes: Wakup the data layer from a mux stream with TASK_WOKEN_IO state
Now, when a mux stream is waking its data layer up for receives or sends, it
uses the TASK_WOKEN_IO state. The state is not used by the stconn I/O
callback function for now.
2026-03-10 15:10:34 +01:00
Christopher Faulet
376487cca9 MINOR: mux-spop: Rely on spop_strm_notify_send() when resuming streams for sending
In spop_resume_each_sending_spop_strm(), there was exactly the same code
than spop_strm_notify_send(). So let's use spop_strm_notify_send() instead
of duplicating code.
2026-03-10 15:10:34 +01:00
Christopher Faulet
aea0d38fdd MINOR: mux-h2: Rely on h2s_notify_send() when resuming h2s for sending
In h2_resume_each_sending_h2s(), there was exactly the same code than
h2s_notify_send(). So let's use h2s_notify_send() instead of duplicating
code.
2026-03-10 15:10:34 +01:00
Christopher Faulet
7abb7c4c79 MINOR: stconn: Call sc_conn_process from the I/O callback if TASK_WOKEN_MSG state was set
It is the first commit of a series to refactor the SC app_ops. The first
step is to remove the .wake() callback function from the app_ops to replace
all uses by a wakeup of the SC tasklet.

Here, when the SC is woken up, the state is now tested and if TASK_WOKEN_MSG
is set, sc_conn_process() is called.
2026-03-10 15:10:34 +01:00
Remi Tricot-Le Breton
924a92200f DOC: jwt: Add ECDH support in jwt_decrypt converters
The jwt_decrypt_jwk and jwt_decrypt_cert converters now manage
algorithms in the ECDH family.
2026-03-10 14:58:48 +01:00
Remi Tricot-Le Breton
31bbc1f0f1 MINOR: jwt: Manage ec certificates in jwt_decrypt_cert
This patch adds the support of algorithms in the ECDH family in the
jwt_decrypt_cert converter.
2026-03-10 14:58:47 +01:00
Remi Tricot-Le Breton
3925bb8efc MINOR: jwt: Add ecdh-es+axxxkw support in jwt_decrypt_jwk converter
This builds on the ECDH-ES processing and simply requires an extra AES
Key Wrap operation between the built key and the token's CEK.
2026-03-10 14:58:47 +01:00
Remi Tricot-Le Breton
32d9af559f MINOR: jwt: Manage ECDH-ES algorithm in jwt_decrypt_jwk function
When ECDH-ES algorithm is used in a JWE token, no cek is provided and
one must be built in order to decrypt the contents of the token. The
decrypting key is built by deriving a temporary key out of a public key
provided in the token and the private key provided by the user and
performing a concatKDF operation.
2026-03-10 14:58:47 +01:00
Remi Tricot-Le Breton
026652a7eb MINOR: jwt: Parse ec-specific fields in jose header
When the encoding is of the ECDH family, the optional "apu" and "apv"
fields of the JOSE header must be parsed, as well as the mandatory "epk"
field that contains an EC public key used to derive a key that allows
either to decrypt the contents of the token (in case of ECDH-ES) or to
decrypt the content encoding key (cek) when using ECDH-ES+AES Key Wrap.
2026-03-10 14:58:46 +01:00
Remi Tricot-Le Breton
3d9764f4c3 MINOR: jwt: Convert EC JWK to EVP_PKEY
Convert a JWK with the "EC" key type ("kty") into an EVP_PKEY. The JWK
can either represent a public key if it only contains the "x" and "y"
fields, or a private key if it also contains the "d" field.
2026-03-10 14:58:46 +01:00
Remi Tricot-Le Breton
e34b633be3 MINOR: jwt: Improve 'jwt_tokenize' function
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
The 'jwt_tokenize' function that can be used to split a JWT token into
its subparts can either fully process the token (from beginning to end)
when we need to check its signature, or only partially when using the
jwt_header_query or jwt_member_query converters. In this case we relied
on the fact that the return value of the 'jwt_tokenize' function was not
checked because a '-1' was returned (which was not actually an error).

In order to make this logic more explicit, the 'jwt_tokenize' function
now has a way to warn the caller that the token was invalid (less
subparts than the specified 'item_num') or that the token was not
processed in full (enough subparts found without parsing the token all
the way).
The function will now only return 0 if we found strictly the same number
of subparts as 'item_num'.
2026-03-10 14:20:42 +01:00
William Lallemand
1babe8cb1b Revert "BUG/MINOR: jwt: Missing 'jwt_tokenize' return value check"
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
This reverts commit 5e14904fef.

The patch is broken, a better implementation is needed.
2026-03-09 16:53:06 +01:00
William Lallemand
1cbd1163f0 BUG/MINOR: mworker: don't set the PROC_O_LEAVING flag on master process
The master process in the proc_list mustn't set the PROC_O_LEAVING flag
since the reload doesn't mean the master will leave.

Could be backported as far as 3.1.
2026-03-09 16:51:56 +01:00
William Lallemand
bd3983b595 MINOR: mworker: add a BUG_ON() on mproxy_li in _send_status
mproxy_li is supposed to be used in _send_status to stop the sockpair FD
between the master and the new worker, being a listener.

This can only work if the listener has been stored in the fdtab owner,
and there's no reason it shouldn't be here.
2026-03-09 16:51:56 +01:00
Willy Tarreau
520faedda0 SCRIPTS: git-show-backports: add a restart-from-last option
It's always a bit tricky to avoid already backported patches when they
just got a different ID (e.g. a critical fix in a topic branch). Most
often with stable topic branches we just want to pick all stable commits
since the last backported one. New option -L instead of -m does exactly
this: it enumerates only commits that were added to the reference branch
after its most recent backport.
2026-03-09 15:36:05 +01:00
Willy Tarreau
459835d535 SCRIPTS: git-show-backports: hide the common ancestor warning in quiet mode
It's annoying to always see that warning in quiet mode when backporting
upstream to topic branches, let's hide it.
2026-03-09 15:36:02 +01:00
William Lallemand
9b3345237a BUG/MINOR: admin: haproxy-reload rename -vv long option
Some checks failed
Contrib / build (push) Has been cancelled
alpine/musl / gcc (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
The -vv option used --verbose as its long form, which was identical to
the long form of -v. Since the case statement matches top-to-bottom,
--verbose would always trigger -v (VERBOSE=2), making -vv unreachable
via its long option. The long form is renamed to --verbose=all to avoid
the conflict, and the usage string is updated accordingly.

Must be backported to 3.3.
2026-03-08 01:37:56 +01:00
William Lallemand
2a0cf52cfc MEDIUM: admin: haproxy-reload conversion to POSIX sh
The script relied on a bash-specific process substitution (< <(...)) to
feed socat's output into the read loop. This is replaced with a standard
POSIX pipe into a command group.

The response parsing is also simplified: instead of iterating over each
line with a while loop and echoing them individually, the status line is
read first, the "--" separator consumed, and the remaining output is
streamed to stderr or discarded as a whole depending on the verbosity
level.

Could be backported to 3.3 as it makes it more portable, but introduce a
slight change in the error format.
2026-03-08 01:37:52 +01:00
William Lallemand
551e5f5fd4 BUG/MINOR: admin: haproxy-reload use explicit socat address type
socat was used with the ${MASTER_SOCKET} variable directly, letting it
auto-detect the network protocol. However, when given a plain filename
that does not point to a UNIX socket, socat would create a file at that
path instead of reporting an error.

To fix this, the address type is now determined explicitly: if
MASTER_SOCKET points to an existing UNIX socket file (checked with -S),
UNIX-CONNECT: is used; if it matches a <host>:<port> pattern, TCP: is
used; otherwise an error is reported. The socat_addr variable is also
properly scoped as local to the reload() function.

Could be backported in 3.3.
2026-03-08 01:33:29 +01:00
Aurelien DARRAGON
2a2989bb23 CLEANUP: flt_http_comp: comp_state doesn't bother about the direction anymore
Some checks failed
Contrib / build (push) Has been cancelled
alpine/musl / gcc (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
no need to have duplicated comp_ctx and comp_algo for request vs response
in comp_state struct, because thanks to previous commit compression filter
is either oriented on the request or the response, and 2 distinct filters
are instanciated when we need to handle both requests and responses
compression.

Thus we can save us from duplicated struct members and related operations.
2026-03-06 13:55:41 +01:00
Aurelien DARRAGON
cbebdb4ba8 MEDIUM: flt_http_comp: split "compression" filter in 2 distinct filters
Existing "compression" filter is a multi-purpose filter that will try
to compress both requests and responses according to "compression"
settings, such as "compression direction".

One of the pre-requisite work identified to implement decompression
filter is that we needed a way to manually define the sequence of
enabled filters to chain them in the proper order to make
compression and decompression chains work as expected in regard
to the intended use-case.

Due to the current nature of the "compression" filter this was not
possible, because the filter has a combined action as it will try
to compress both requests and responses, and as we are about to
implement "filter-sequence" directive, we will not be able to
change the order of execution of the compression filter between
requests and responses.

A possible solution we identified to solve this issue is to split the
existing "compression" filter into 2 distinct filters, one which is
request-oriented, "comp-req", and another one which is response-oriented
"comp-res". This is what we are doing in this commit. Compression logic
in itself is unchanged, "comp-req" will only aim to compress the request
while "comp-res" will try to compress the response. Both filters will
still be invoked on request and responses hooks, but they only do their
part of the job.

From now on, to compress both requests and responses, both filters have
to be enabled on the proxy. To preserve original behavior, the "compression"
filter is still supported, what it does is that it instantiates both
"comp-req" and "comp-res" filters implicitly, as the compression filter is
now effectively split into 2 separate filters under the hood.

When using "comp-res" and "comp-req" filters explicitly, the use of the
"compression direction" setting is not relevant anymore. Indeed, the
compression direction is assumed as soon as one or both filters are
enabled. Thus "compression direction" is kept as a legacy option in
order to configure the "compression" generic filter.

Documentation was updated.
2026-03-06 13:55:31 +01:00
Aurelien DARRAGON
9549b05b94 MINOR: flt_http_comp: define and use proxy_get_comp() helper function
proxy_get_comp() function can be used to retrieve proxy->comp options or
allocate and initialize it if missing

For now, it is solely used by parse_compression_options(), but the goal is
to be able to use this helper from multiple origins.
2026-03-06 13:55:24 +01:00
Remi Tricot-Le Breton
5e14904fef BUG/MINOR: jwt: Missing 'jwt_tokenize' return value check
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
There was a "jwt_tokenize" call whose return value was not checked.

This was found by coverity and raised in GitHub #3277.
This patch can be backported to all stable branches.
2026-03-06 09:52:19 +01:00
Christopher Faulet
af6b9a0967 BUG/MINOR: backend: Don't get proto to use for webscoket if there is no server
In connect_server(), it is possible to have no server defined (dispatch mode
or transparent backend). In that case, we must be carefull to check the srv
variable in all calls involving the server. It was not perform at one place,
when the protocol to use for websocket is retrieved. This must not be done
when there is no server.

This patch should fix the first report in #3144. It must be backported to
all stable version.
2026-03-06 09:24:32 +01:00
Christopher Faulet
bfe5a2c3d7 BUG/MINOR: ssl-sample: Fix sample_conv_sha2() by checking EVP_Digest* failures
In sample_conv_sha2(), calls to EVP_Digest* can fail. So we must check
return value of each call and report a error on failure and release the
digest context.

This patch should fix the issue #3274. It should be backported as far as
2.6.
2026-03-06 09:07:16 +01:00
Christopher Faulet
b48c9a1465 BUG/MINOR: stconn: Increase SC bytes_out value in se_done_ff()
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
When data are sent via the zero-copy data forwarding, we must not forget to
increase the stconn bytes_out value.

This patch must be backport to 3.3.
2026-03-05 16:17:33 +01:00
Willy Tarreau
fcfabd0d90 [RELEASE] Released version 3.4-dev6
Some checks failed
Contrib / build (push) Has been cancelled
alpine/musl / gcc (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
Released version 3.4-dev6 with the following main changes :
    - CLEANUP: acme: remove duplicate includes
    - BUG/MINOR: proxy: detect strdup error on server auto SNI
    - BUG/MINOR: server: set auto SNI for dynamic servers
    - BUG/MINOR: server: enable no-check-sni-auto for dynamic servers
    - MINOR: haterm: provide -b and -c options (RSA key size, ECDSA curves)
    - MINOR: haterm: add long options for QUIC and TCP "bind" settings
    - BUG/MINOR: haterm: missing allocation check in copy_argv()
    - BUG/MINOR: quic: fix counters used on BE side
    - MINOR: quic: add BUG_ON() on half_open_conn counter access from BE
    - BUG/MINOR: quic/h3: display QUIC/H3 backend module on HTML stats
    - BUG/MINOR: acme: acme_ctx_destroy() leaks auth->dns
    - BUG/MINOR: acme: wrong labels logic always memprintf errmsg
    - MINOR: ssl: clarify error reporting for unsupported keywords
    - BUG/MINOR: acme: fix incorrect number of arguments allowed in config
    - CLEANUP: haterm: remove unreachable labels hstream_add_data()
    - CLEANUP: haterm: avoid static analyzer warnings about rand() use
    - CLEANUP: ssl: Remove a useless variable from ssl_gen_x509()
    - CI: use the latest docker for QUIC Interop
    - CI: remove redundant "halog" compilation
    - CLENAUP: cfgparse: accept-invalid-http-* does not support "no"/"defaults"
    - BUG/MEDIUM: spoe: Acquire context buffer in applet before consuming a frame
    - MINOR: traces: always mark trace_source as thread-aligned
    - MINOR: ncbmbuf: improve itbmap_next() code
    - MINOR: proxy: improve code when checking server name conflicts
    - MINOR: quic: add a new metric for ncbuf failures
    - BUG/MINOR: haterm: cannot reset default "haterm" mode
    - BUG/MEDIUM: cpu-topo: Distribute CPUs fairly across groups
    - BUG/MINOR: quic: missing app ops init during backend 0-RTT sessions
    - CLEANUP: ssl: remove outdated comments
    - MINOR: mux-h2: also count glitches on invalid trailers
    - MINOR: mux-h2: add a new setting, "tune.h2.log-errors" to tweak error logging
    - BUG/MEDIUM: mux-h2: make sure to always report pending errors to the stream
    - BUG/MINOR: server: adjust initialization order for dynamic servers
    - CLEANUP: tree-wide: drop a few useless null-checks before free()
    - CLEANUP: quic-stats: include counters from quic_stats
    - REORG: stats/counters: move extra_counters to counters not stats
    - CLEANUP: stats: drop stats.h / stats-t.h where not needed
    - MEDIUM: counters: change the fill_stats() API to pass the module and extra_counters
    - CLEANUP: counters: only retrieve zeroes for unallocated extra_counters
    - MEDIUM: counters: add a dedicated storage for extra_counters in various structs
    - MINOR: counters: store a tgroup step for extra_counters to access multiple tgroups
    - MEDIUM: counters: store the number of thread groups accessing extra_counters
    - MINOR: counters: add EXTRA_COUNTERS_BASE() to retrieve extra_counters base storage
    - MEDIUM: counters: return aggregate extra counters in ->fill_stats()
    - MEDIUM: counters: make EXTRA_COUNTERS_GET() consider tgid
    - BUG/MINOR: call EXTRA_COUNTERS_FREE() before srv_free_params() in srv_drop()
    - MINOR: promex: test applet resume in stress mode
    - BUG/MINOR: promex: fix server iteration when last server is deleted
    - BUG/MINOR: proxy: add dynamic backend into ID tree
    - MINOR: proxy: convert proxy flags to uint
    - MINOR: server: refactor srv_detach()
    - MINOR: proxy: define a basic "del backend" CLI
    - MINOR: proxy: define proxy watcher member
    - MINOR: stats: protect proxy iteration via watcher
    - MINOR: promex: use watcher to iterate over backend instances
    - MINOR: lua: use watcher for proxies iterator
    - MINOR: proxy: add refcount to proxies
    - MINOR: proxy: rename default refcount to avoid confusion
    - MINOR: server: take proxy refcount when deleting a server
    - MINOR: lua: handle proxy refcount
    - MINOR: proxy: prevent backend removal when unsupported
    - MINOR: proxy: prevent deletion of backend referenced by config elements
    - MINOR: proxy: prevent backend deletion if server still exists in it
    - MINOR: server: mark backend removal as forbidden if QUIC was used
    - MINOR: cli: implement wait on be-removable
    - MINOR: proxy: add comment for defaults_px_ref/unref_all()
    - MEDIUM: proxy: add lock for global accesses during proxy free
    - MEDIUM: proxy: add lock for global accesses during default free
    - MINOR: proxy: use atomic ops for default proxy refcount
    - MEDIUM: proxy: implement backend deletion
    - REGTESTS: add a test on "del backend"
    - REGTESTS: complete "del backend" with unnamed defaults ref free
    - BUG/MINOR: hlua: fix return with push nil on proxy check
    - BUG/MEDIUM: stream: Handle TASK_WOKEN_RES as a stream event
    - MINOR: quic: use signed char type for ALPN manipulation
    - MINOR: quic/h3: reorganize stream reject after MUX closure
    - MINOR: mux-quic: add function for ALPN to app-ops conversion
    - MEDIUM: quic/mux-quic: adjust app-ops install
    - MINOR: quic: use server cache for ALPN on BE side
    - BUG/MEDIUM: hpack: correctly deal with too large decoded numbers
    - BUG/MAJOR: qpack: unchecked length passed to huffman decoder
    - BUG/MINOR: qpack: fix 1-byte OOB read in qpack_decode_fs_pfx()
    - BUG/MINOR: quic: fix OOB read in preferred_address transport parameter
    - BUG/MEDIUM: qpack: correctly deal with too large decoded numbers
    - BUG/MINOR: hlua: Properly enable/disable line receives from HTTP applet
    - BUG/MEDIUM: hlua: Fix end of request detection when retrieving payload
    - BUG/MINOR: hlua: Properly enable/disable receives for TCP applets
    - MINOR: htx: Add a function to retrieve the HTTP version from a start-line
    - MINOR: h1-htx: Reports non-HTTP version via dedicated flags
    - BUG/MINOR: h1-htx: Be sure that H1 response version starts by "HTTP/"
    - MINOR: http-ana: Save the message version in the http_msg structure
    - MEDIUM: http-fetch: Rework how HTTP message version is retrieved
    - MEDIUM: http-ana: Use the version of the opposite side for internal messages
    - DEBUG: stream: Display the currently running rule in stream dump
    - MINOR: filters: Use filter API as far as poissible to break loops on filters
    - MINOR: filters: Set last_entity when a filter fails on stream_start callback
    - MINOR: stream: Display the currently running filter per channel in stream dump
    - DOC: config: Use the right alias for %B
    - BUG/MINOR: channel: Increase the stconn bytes_in value in channel_add_input()
    - BUG/MINOR: sample: Fix sample to retrieve the number of bytes received and sent
    - BUG/MINOR: http-ana: Increment scf bytes_out value if an haproxy error is sent
    - BUG/MAJOR: fcgi: Fix param decoding by properly checking its size
    - BUG/MAJOR: resolvers: Properly lowered the names found in DNS response
    - BUG/MEDIUM: mux-fcgi: Use a safe loop to resume each stream eligible for sending
    - MINOR: mux-fcgi: Use a dedicated function to resume streams eligible for sending
    - CLEANUP: qpack: simplify length checks in qpack_decode_fs()
    - MINOR: counters: Introduce COUNTERS_UPDATE_MAX()
    - MINOR: listeners: Update the frequency counters separately when needed
    - MINOR: proxies: Update beconn separately
    - MINOR: stats: Add an option to disable the calculation of max counters
2026-03-05 15:55:28 +01:00
Olivier Houchard
5d02d33ee1 MINOR: stats: Add an option to disable the calculation of max counters
Add a new option, "stats calculate-max-counters [on|off]".
It makes it possible to disable the calculation of max counters, as they
can have a performance cost.
2026-03-05 15:39:42 +01:00
Olivier Houchard
1544842801 MINOR: proxies: Update beconn separately
Update beconn separately from the call to COUNTERS_UPDATE_MAX(), as soon
there will be an option to get COUNTERS_UPDATE_MAX() to do nothing, and
we still want beconn to be properly updated, as it is used for other
purposes.
2026-03-05 15:39:42 +01:00
Olivier Houchard
88bc2bdfc9 MINOR: listeners: Update the frequency counters separately when needed
Update the frequency counters that are exported to the stats page
outside of the call to COUNTERS_UPDATE_MAX(), so that they will
happen even if COUNTERS_UPDATE_MAX() ends up doing nothing.
2026-03-05 15:39:42 +01:00
Olivier Houchard
0087651128 MINOR: counters: Introduce COUNTERS_UPDATE_MAX()
Introduce COUNTERS_UPDATE_MAX(), and use it instead of using
HA_ATOMIC_UPDATE_MAX() directly.
For now it just calls HA_ATOMIC_UPDATE_MAX(), but will later be modified
so that we can disable max calculation.
This can be backported up to 2.8 if the usage of COUNTERS_UPDATE_MAX()
generates too many conflicts.
2026-03-05 15:39:42 +01:00
Frederic Lecaille
65d3416da5 CLEANUP: qpack: simplify length checks in qpack_decode_fs()
This patch simplifies the decoding loop by merging the variable-length
integer truncation check (len == -1) with the subsequent buffer
availability check (len < length).

This removes redundant code blocks and improves readability without
changing the decoding logic.

Note that the second removal is correct, as the check was duplicate and
unnecessary."
2026-03-05 15:42:02 +01:00
Christopher Faulet
7d48e80da5 MINOR: mux-fcgi: Use a dedicated function to resume streams eligible for sending
The same code was duplicated in fcgi_process_mux() and fcgi_sedn(). So let's
move it in a dedicated function.
2026-03-05 15:35:36 +01:00
Christopher Faulet
9b22f22858 BUG/MEDIUM: mux-fcgi: Use a safe loop to resume each stream eligible for sending
At the end of fcgi_send(), if the connection is not full anymore, we loop on
the send list to resume FCGI stream for sending. But a streams may be
removed from the this list during the loop. So a safe loop must be used.

This patch should be backported to all stable versions.
2026-03-05 15:35:36 +01:00
Christopher Faulet
25d6e65aae BUG/MAJOR: resolvers: Properly lowered the names found in DNS response
Names found in DNS responses are lowered to be compared. A name is composed
of several labels, strings precedeed by their length on one byte. For
instance:

 3www7haproxy3org

There is an bug when labels are lowered. The label length is not skipped and
tolower() function is called on it. So for label length in the range [65-90]
(uppercase char), 32 is added to the label length due to the conversion of a
uppercase char to lowercase. This bugs can lead to OOB read later in the
resolvers code.

The fix is quite obvious, the label length must be skipped when the label is
lowered.

Thank you to Kamil Frankowicz for having reported this.

This patch must be backported to all stable versions.
2026-03-05 15:35:31 +01:00
Christopher Faulet
96286b2a84 BUG/MAJOR: fcgi: Fix param decoding by properly checking its size
In functions used to decode a FCGI parameter, the test on the data length
before reading the parameter's name and value did not consider the offset
value used to skip already parsed data. So it was possible to read more data
than available (OOB read). To do so, a malicious FCGI server must send a
forged GET_VALUES_RESULT record containing a parameter with wrong name/value
length.

Thank you to Kamil Frankowicz for having reported this.

This patch must be backported to all stable versions.
2026-03-05 15:35:21 +01:00
Christopher Faulet
306931dfb1 BUG/MINOR: http-ana: Increment scf bytes_out value if an haproxy error is sent
When an HAproxy error is sent, we must not forget to increment bytes_out
value on the front stconn.

This patch must be backport to 3.3.
2026-03-05 15:34:47 +01:00
Christopher Faulet
4791501011 BUG/MINOR: sample: Fix sample to retrieve the number of bytes received and sent
There was an issue in the if/else statement in smp_fetch_bytes() function.
When req.bytes_in or req.bytes_out was requested, res.bytes_in was always
returned. It is now fixed.

This patch must be backported to 3.3.
2026-03-05 15:34:47 +01:00
Christopher Faulet
e0728ebcf4 BUG/MINOR: channel: Increase the stconn bytes_in value in channel_add_input()
This function is no longer used. So it is not really an bug. But it is still
available and could be used by legacy applets. In that case, we must take
care to increment the stconn bytes_in value accordingly when input data are
inserted.

This patch must be backported to 3.3.
2026-03-05 15:34:47 +01:00
Christopher Faulet
97a63835af DOC: config: Use the right alias for %B
In custom log format part, %[req.bytes_in] was erroneously documented as the
alias of %B. The good alias is %[res.bytes_in]. It is now fixed.

This patch must be backported to 3.3.
2026-03-05 15:34:47 +01:00
Christopher Faulet
50fb37e5fe MINOR: stream: Display the currently running filter per channel in stream dump
Since the 3.1, when stream's info are dump, it is possible to print the
yielding filter on each channel, if any. It was useful to detect buggy
filter on spinning loop. But it is not possible to detect a filter consuming
too much CPU per-execution. We can see a filter was executing in the
backtrace reported by the watchdog, but we are unable to spot the specific
one.

Thanks to this patch, it is now possible. When a dump is emitted, the
running or yield filters on each channel are now displayed with their
current state (RUNNING or YIELDING).

This patch could be backported as far as 3.2 because it could be useful to
spot issues. But the filter API was slightly refactored in 3.4, so this
patch should be adapted.
2026-03-05 15:34:47 +01:00
Christopher Faulet
cd9f159210 MINOR: filters: Set last_entity when a filter fails on stream_start callback
On the stream, the last_entity should reference the last rule or the last
filter evaluated during the stream processing. However, this info was not
saved when a filter failed on strem_start callback function. It is now
fixed.

This patch could be backported as far as 3.1.
2026-03-05 15:34:47 +01:00
Christopher Faulet
3eadf887f7 MINOR: filters: Use filter API as far as poissible to break loops on filters
When the filters API was refactored to improve loops on filters, some places
were not updated (or not fully updated). Some loops were not relying on
resume_filter_list_break() while it was possible. So let's do so with this
patch.
2026-03-05 15:34:47 +01:00
Christopher Faulet
9f1e9ee0ed DEBUG: stream: Display the currently running rule in stream dump
Since the 2.5, when stream's info are dump, it is possible to print the
yielding rule, if any. It was useful to detect buggy rules on spinning
loop. But it is not possible to detect a rule consuming too much CPU
per-execution. We can see a rule was executing in the backtrace reported by
the watchdog, but we are unable to spot the specific rule.

Thanks to this patch, it is now possible. When a dump is emitted, the
running or yield rule is now displayed with its current state (RUNNING or
YIELDING).

This patch could be backported as far as 3.2 because it could be useful to
spot issues.
2026-03-05 15:34:47 +01:00
Christopher Faulet
4939f18ff7 MEDIUM: http-ana: Use the version of the opposite side for internal messages
When the response is send by HAProxy, from a applet or for the analyzers,
The request version is used for the response. The main reason is that there
is not real version for the response when this happens. "HTTP/1.1" is used,
but it is in fact just an HTX response. So the version of the request is
used.

In the same manner, when the request is sent from an applet (httpclient),
the response version is used, once available.

The purpose of this change is to return the most accurate version from the
user point of view.
2026-03-05 15:34:46 +01:00
Christopher Faulet
b2ba3c6662 MEDIUM: http-fetch: Rework how HTTP message version is retrieved
Thanks to previous patches, we can now rely on the version stored in the
http_msg structure to get the request or the response version.

"req.ver" and "res.ver" sample fetch functions returns the string
representation of the version, without the prefix, so "<major>.<minor>", but
only if the version is valid. For the response, "res.ver" may be added from
a health-check context, in that case, the HTX message is used.

"capture.req.ver" and "capture.res.ver" does the same but the "HTTP/" prefix
is added to the result. And "capture.res.ver" cannot be called from a
health-check.

To ease the version formatting and avoid code duplication, an helper
function was added. So these samples are now relying on "get_msg_version()".
2026-03-05 15:34:46 +01:00
Christopher Faulet
7bfb66d2b1 MINOR: http-ana: Save the message version in the http_msg structure
When the request or the response is received, the numerical value of the
message version is now saved. To do so, the field "vsn" was added in the
http_msg structure. It is an unsigned char. The 4 MSB bits are used for the
major digit and the 4 LSB bits for the minor one.

Of couse, the version must be valid. the HTX_SL_F_NOT_HTTP flag of the
start-line is used to be sure the version is valid. But because this flag is
quite new, we also take care the string representation of the version is 8
bytes length. 0 means the version is not valid.
2026-03-05 15:34:46 +01:00
Christopher Faulet
7a474855b4 BUG/MINOR: h1-htx: Be sure that H1 response version starts by "HTTP/"
When the response is parsed, we test the version to be sure it is
valid. However, the protocol was not tested. Now we take care that the
response version starts by "HTTP/", otherwise an error is returned.

Of course, it is still possible to by-pass this test with
"accept-unsafe-violations-in-http-response" option.

This patch could be backported to all stable versions.
2026-03-05 15:34:46 +01:00
Christopher Faulet
88765b69e0 MINOR: h1-htx: Reports non-HTTP version via dedicated flags
Now, when the HTTP version format is not strictly valid, flags are set on
the h1 parser and the HTX start-line. H1_MF_NOT_HTTP is set on the H1 parser
and HTX_SL_F_NOT_HTTP is set on the HTX start-line. These flags were
introduced to avoid parsing again and again the version to know if it is a
valid version or not, escpecially because it is most of time valid.
2026-03-05 15:34:46 +01:00
Christopher Faulet
9951f9cf85 MINOR: htx: Add a function to retrieve the HTTP version from a start-line
htx_sl_vsn() function can now be used to retrieve the ist string
representing the HTTP version from a start-line passed as parameter. This
function takes care to return the right part of the start-line, depending on
its type (request or response).
2026-03-05 15:34:46 +01:00
Christopher Faulet
e6a8ef5521 BUG/MINOR: hlua: Properly enable/disable receives for TCP applets
From a lua TCP applet, in functions used to retrieve data (receive,
try_receive and getline), we must take care to disable receives when data
are returned (or on failure) and to restart receives when these functions
are called again. In addition, when an applet execution is finished, we must
restart receives to properly drain the request.

This patch should be backported to 3.3. On older version, no bug was
reported so we can wait a report first.
2026-03-05 15:34:46 +01:00
Christopher Faulet
7fe1a92bb3 BUG/MEDIUM: hlua: Fix end of request detection when retrieving payload
When the lua HTTP applet was refactored to use its own buffers, a bug was
introduced in receive() and getline() function. We rely on HTX_FL_EOM flag
to detect the end of the request. But there is nothing preventing extra
calls to these function, when the whole request was consumed. If this
happens, the call will yield waiting for more data with no way to stop it.

To fix the issue, APPLET_REQ_RECV flag was added to know the whole request
was received.

This patch should fix #3293. It must be backported to 3.3.
2026-03-05 15:34:46 +01:00
Christopher Faulet
a779d0d23a BUG/MINOR: hlua: Properly enable/disable line receives from HTTP applet
From a lua HTTP applet, in the getline() function, we must take care to
disable receives when a line is retrieved and to restart receives when the
function is called again. In addition, when an applet execution is finished,
we must restart receives to properly drain the request.

This patch could help to fix #3293. It must be backported to 3.3. On older
version, no bug was reported so we can wait a report first. But in that
case, hlua_applet_http_recv() should also be fixed (on 3.3 it was fixed
during the applets refactoring).
2026-03-05 15:34:46 +01:00
Frederic Lecaille
0a02acecf3 BUG/MEDIUM: qpack: correctly deal with too large decoded numbers
Same fix as this one for hpack:

	7315428615 ("BUG/MEDIUM: hpack: correctly deal with too large decoded numbers")

Indeed, the encoding of integers for QPACK is the same as for HPACK but for 64 bits
integers.

Must be backported as far as 2.6.
2026-03-05 15:02:02 +01:00
Frederic Lecaille
cdcdc016cc BUG/MINOR: quic: fix OOB read in preferred_address transport parameter
This bug impacts only the QUIC backend. A QUIC server does receive
a server preferred address transport parameter.

In quic_transport_param_dec_pref_addr(), the boundary check for the
connection ID was inverted and incorrect. This could lead to an
out-of-bounds read during the following memcpy.

This patch fixes the comparison to ensure the buffer has enough input data
for both the CID and the mandatory Stateless Reset Token.

Thank you to Kamil Frankowicz for having reported this.

Must be backported to 3.3.
2026-03-05 15:02:02 +01:00
Frederic Lecaille
54b614d2b5 BUG/MINOR: qpack: fix 1-byte OOB read in qpack_decode_fs_pfx()
In qpack_decode_fs_pfx(), if the first qpack_get_varint() call
consumes the entire buffer, the code would perform a 1-byte
out-of-bounds read when accessing the sign bit via **raw.

This patch adds an explicit length check at the beginning of
qpack_get_varint(), which systematically secures all other callers
against empty inputs. It also adds a necessary check before the
second varint call in qpack_decode_fs_pfx() to ensure data is still
available before dereferencing the pointer to extract the sign bit,
returning QPACK_RET_TRUNCATED if the buffer is exhausted.

Thank you to Kamil Frankowicz for having reported this.

Must be backported as far as 2.6.
2026-03-05 15:02:02 +01:00
Frederic Lecaille
e38b86e72c BUG/MAJOR: qpack: unchecked length passed to huffman decoder
A call to huffman decoder function (huff_dec()) is made from qpack_decode_fs()
without checking the buffer length passed to this function, leading to OOB read
which can crash the process.

Thank you to Kamil Frankowicz for having reported this.

Must be backport as far as 2.6.
2026-03-05 15:02:02 +01:00
Willy Tarreau
7315428615 BUG/MEDIUM: hpack: correctly deal with too large decoded numbers
The varint hpack decoder supports unbounded numbers but returns 32-bit
results. This means that possible truncation my happen on some field
lengths or indexes that would be emitted as quantities that do not fit
in a 32-bit number. The final value will also depend on how the left
shift operation behaves on the target architecture (e.g. whether bits
are lost or used modulo 31). This could lead to a desynchronization of
the HPACK stream decoding compared to what an external observer would
see (e.g. from a network traffic capture). However, there isn't any
impact between streams, HPACK is performed at the connection level,
not at the stream level, so no stream may try to leverage this
limitation to have any effect on another one.

For the fix, instead of adding checks everywhere in the loop and for
the final stage, let's rewrite the decoder to compare the read value
to a max value that is shifted by 7 bits for every 7 bits read. This
allows a sender to continue to emit zeroes for higher bits without
being blocked, while detecting that a received value would overflow.
The loop is now simpler as it deals both with values with the higher
bit set and the final ones, and stops once the final value was recorded.

A test on non-zero before performing the shift was added to please
ubsan, though in practice zero shifted by any quantity remains zero.
But the test is cheap so that's OK.

Thanks to Guillaume Meunier, Head of Vulnerability Operations Center
France at Orange Cyberdefense, for reporting this bug.

This should be backported to all stable versions.
2026-03-05 14:33:21 +01:00
Amaury Denoyelle
b1441c6440 MINOR: quic: use server cache for ALPN on BE side
Some checks failed
Contrib / build (push) Has been cancelled
alpine/musl / gcc (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
On the backend side, QUIC MUX may be started preemptively before the
ALPN negotiation. This is useful notably for 0-RTT implementation.

However, this was a source of crashes. ALPN was expected to be retrieved
from the server cache, however QUIC MUX still used the ALPN from the
transport layer. This could cause a crash, especially when several
connections runs in parallel as the server cache is shared among
threads.

Thanks to the previous patch which reworks QUIC MUX init, this solution
can now be fixed. Indeed, if conn_get_alpn() is not successful, MUX can
look at the server cache again to use the expected value.

Note that this could still prevent the MUX to work as expected if the
server cache is resetted between connect_server() and MUX init. Thus,
the ultimate solution would be to copy the cached ALPN into the
connection. This problem is not specific to QUIC though, and must be
fixed in a separate patch.
2026-03-03 16:23:03 +01:00
Amaury Denoyelle
940e1820f6 MEDIUM: quic/mux-quic: adjust app-ops install
This patch reworks the installation of app-ops layer by QUIC MUX.
Previously, app_ops field was stored directly into the quic_conn
structure. Then the MUX reused it directly during its qmux_init().

This patch removes app_ops field from quic_conn and replaces it with a
copy of the negotiated ALPN. By using quic_alpn_to_app_ops(), it ensures
it remains compatible with a known application layer.

On the MUX layer, qcc_install_app_ops() now uses the standard
conn_get_alpn() to retrieve the ALPN from the transport layer. This is
done via the newly defined <get_alpn> QUIC xprt callback.

This new architecture should be cleaner as it better highlights the
responsibility of each layers in the ALPN/app negotiation.
2026-03-03 16:22:57 +01:00
Amaury Denoyelle
9c7cf1c684 MINOR: mux-quic: add function for ALPN to app-ops conversion
Extract the conversion from ALPN to qcc_app_ops type from quic_conn
source file into QUIC MUX. The newly created function is named
quic_alpn_to_app_ops(). This will serve as a central point to identify
which ALPNs are currently supported in our QUIC stack.

This patch is purely a small refactoring. It will be useful for the next
one which rework MUX app-ops layer init. The current cleanup allows
notably to remove H3/hq-interop headers from quic_conn source file.
2026-03-03 16:20:16 +01:00
Amaury Denoyelle
4120faf289 MINOR: quic/h3: reorganize stream reject after MUX closure
The QUIC MUX layer is closed after its transport counterpart. This may
be necessary then to reject any new streams opened by the remote peer.
This operation is dependent however from the application protocol.

Previously, a function qc_h3_request_reject() was directly implemented
in quic_conn source file for use when HTTP/3 was previously negotiated.
However, this solution was not evolutive and broke layering.

This patch introduces a new proper separation with a <strm_reject>
callback defined in quic_conn structure. When set, it will be used to
preemptively close any new stream. QUIC MUX is responsible to set it
just before its closure.

No functional change. This patch is purely a refactoring with a better
architecture design. Especially, H3 specific code from transport layer
is now completely removed.
2026-03-03 16:19:13 +01:00
Amaury Denoyelle
58830990d0 MINOR: quic: use signed char type for ALPN manipulation
In most of haproxy code, ALPN is used as a signed char pointer. In QUIC
code instead, it is manipulated as unsigned.

Unifies this by using signed type in QUIC code. This allows to remove a
bunch of unnecessary casts.
2026-03-03 16:11:58 +01:00
Christopher Faulet
13c3445163 BUG/MEDIUM: stream: Handle TASK_WOKEN_RES as a stream event
The conversion of TASK_WOKEN_RES to a stream event was missing. Among other
things, this wakeup reason is used when a stream is dequeued. So it was
possible to skip the connection establishment if the stream was also woken
up for a timer reason. When this happened, the stream was blocked till the
queue timeout expiration.

Converting TASK_WOKEN_RES to STRM_EVT_RES fixes the issue.

This patch should fix the issue #3290. It must be backported as far as 3.2.
2026-03-03 15:16:10 +01:00
Amaury Denoyelle
f41e684e9a BUG/MINOR: hlua: fix return with push nil on proxy check
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
hlua_check_proxy() may now return NULL if the target proxy instance has
been flagged for deletion. Thus, proxies method have been adjusted and
may push nil to report such case.

This patch fixes these error paths. When nil is pushed, 1 must be
returned instead of 0. This represents the count of pushed values on the
stack which can be retrieved by the caller.

No need to backport.
2026-03-03 08:45:27 +01:00
Amaury Denoyelle
e07a75c764 REGTESTS: complete "del backend" with unnamed defaults ref free
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
Complete delete backend regtests by checking deletion of a proxy with a
reference on an unnamed defaults instance. This operation is sensible as
the defaults refcount is decremented, and when the last backend is
removed, the defaults is also freed.
2026-03-02 14:15:53 +01:00
Amaury Denoyelle
2f5030c847 REGTESTS: add a test on "del backend"
Add a reg-tests to test "del backend" CLI command. First, checks are
performed to ensure a backend cannot be deleted if not in the expected
state.

Then, a "del backend" success is tested. Stats are dumped to ensure the
backend instance is indeed removed.
2026-03-02 14:14:05 +01:00
Amaury Denoyelle
712055f2f8 MEDIUM: proxy: implement backend deletion
This patch finalizes "del backend" handler by implementing the proper
proxy deletion.

After ensuring backend deletion can be performed, several steps are
executed. First, any watcher elements are updated to point on the next
proxy instance. The backend is then removed from ID and name global
trees and is finally detached from proxies_list.

Once the backend instance is removed from proxies_list, the backend
cannot be found by new elements. Thread isolation is lifted and
proxy_drop() is called, which will purge the proxy if its refcount is
null. Thanks to recently introduced PROXIES_DEL_LOCK, proxy_drop() is
thread safe.
2026-03-02 14:14:05 +01:00
Amaury Denoyelle
6145f52d9c MINOR: proxy: use atomic ops for default proxy refcount
Default proxy refcount <def_ref> is used to comptabilize reference on a
default proxy instance by standard proxies. Currently, this is necessary
when a default proxy defines TCP/HTTP rules or a tcpcheck ruleset.

Transform every access on <def_ref> so that atomic operations are now
used. Currently, this is not strictly needed as default proxies
references are only manipulated at init or deinit in single thread mode.
However, when dynamic backends deletion will be implemented, <def_ref>
will be decremented at runtime also.
2026-03-02 14:14:05 +01:00
Amaury Denoyelle
f64aa036d8 MEDIUM: proxy: add lock for global accesses during default free
This patch is similar to the previous one, but this time it deals with
functions related to defaults proxies instances. Lock PROXIES_DEL_LOCK
is used to protect accesses on global collections.

This patch will be necessary to implement dynamic backend deletion, even
if defaults won't be use as direct target of a "del backend" CLI.
However, a backend may have a reference on a default instance. When the
backend is freed, this references is released, which can in turn cause
the freeing of the default proxy instance. All of this will occur at
runtime, outside of thread isolation.
2026-03-02 14:10:46 +01:00
Amaury Denoyelle
f58b2698ce MEDIUM: proxy: add lock for global accesses during proxy free
Define a new lock with label PROXIES_DEL_LOCK. Its purpose is to protect
operations performed on global lists or trees while a proxy is freed.

Currently, this lock is unneeded as proxies are only freed on
single-thread init or deinit. However, with the incoming dynamic backend
deletion, this operation will be also performed at runtime, outside of
thread isolation.
2026-03-02 14:09:25 +01:00
Amaury Denoyelle
a7d1c59a92 MINOR: proxy: add comment for defaults_px_ref/unref_all()
Write documentation for functions related to default proxies instances.
2026-03-02 14:08:30 +01:00
Amaury Denoyelle
98c8c5e16e MINOR: cli: implement wait on be-removable
Implement be-removable argument to CLI wait. This is implemented via
be_check_for_deletion() invokation, also used by "del backend" handler.

The objective is to test whether a backend instance can be removed. If
this is not the case, the command may returns immediately if the target
proxy is incompatible with dynamic removal or if a user action is
required. Else, the command will wait until the temporary restriction is
lifted.
2026-03-02 14:08:30 +01:00
Amaury Denoyelle
5ddfbd4b03 MINOR: server: mark backend removal as forbidden if QUIC was used
Currenly, quic_conn on the backend side may access their parent proxy
instance during their lifetime. In particular, this is the case for
counters update, with <prx_counters> field directly referencing a proxy
memory zone.

As such, this prevents safe backend removal. One solution would be to
check if the upper connection instance is still alive, as a proxy cannot
be removed if connection are still active. However, this would
completely prevent proxy counters update via
quic_conn_prx_cntrs_update(), as this is performed on quic_conn release.

Another solution would be to use refcount, or a dedicated counter on the
which account for QUIC connections on a backend instance. However,
refcount is currently only used by short-term references, and it could
also have a negative impact on performance.

Thus, the simplest solution for now is to disable a backend removal if a
QUIC server is/was used in it. This is considered acceptable for now as
QUIC on the backend side is experimental.
2026-03-02 14:08:30 +01:00
Amaury Denoyelle
053887cc98 MINOR: proxy: prevent backend deletion if server still exists in it
Ensure a backend instance cannot be removed if there is still server in
it. This is checked via be_check_for_deletion() to ensure "del backend"
cannot be executed. The only solution is to use "del server" to remove
on the servers instances.

This check only covers servers not yet targetted via "del server". For
deleted servers not yet purged (due to their refcount), the proxy
refcount is incremented but this does not block "del backend"
invokation.
2026-03-02 14:08:30 +01:00
Amaury Denoyelle
7f725f0754 MINOR: proxy: prevent deletion of backend referenced by config elements
Define a new proxy flag PR_FL_NON_PURGEABLE. This is used to mark every
proxy instance explicitely referenced in the config. Such instances
cannot be deleted at runtime.

Static use_backend/default_backend rules are handled in
proxy_finalize(). Also, sample expression proxy references are protected
via smp_resolve_args().

Note that this last case also incidentally protects any proxies
referenced via a CLI "set var" expression. This should not be the case
as in this case variable value is instantly resolved so the proxy
reference is not needed anymore. This also affects dynamic servers.
2026-03-02 14:08:30 +01:00
Amaury Denoyelle
7bf3020952 MINOR: proxy: prevent backend removal when unsupported
Prevent removal of a backend which relies on features not compatible
with dynamic backends. This is the case if either dispatch or
transparent option is used, or if a stick-table is declared.

These limitations are similar to the "add backend" ones.
2026-03-02 14:08:30 +01:00
Amaury Denoyelle
ad1e00b2ac MINOR: lua: handle proxy refcount
Implement proxy refcount for Lua proxy class. This is similar to the
server class.

In summary, proxy_take() is used to increment refcount when a Lua proxy
is instantiated. proxy_drop() is called via Lua garbage collector. To
ensure a deleted backend is released asap, hlua_check_proxy() now
returns NULL if PR_FL_DELETED is set.

This approach is directly dependable on Lua GC execution. As such, it
probably suffers from the same limitations as the ones already described
in the previous commit. With the current patch, "del backend" is not
directly impacted though. However, the final proxy deinit may happen
after a long period of time, which could cause memory pressure increase.

One final observations regarding deinit : it is necessary to delay a
BUG_ON() which checks that defaults proxies list is empty. Now this must
be executed after Lua deinit (called via post_deinit_list). This should
guarantee that all proxies and their defaults refcount are null.
2026-03-02 14:08:30 +01:00
Amaury Denoyelle
f521c2ce2d MINOR: server: take proxy refcount when deleting a server
When a server is deleted via "del server", increment refcount of its
parent backend. This is necessary as the server is not referenced
anymore in the backend, but can still access it via its own <proxy>
member. Thus, backend removal must not happen until the complete purge
of the server.

The proxy refcount is released in srv_drop() if the flag SRV_F_DELETED
is set, which indicates that "del server" was used. This operation is
performed after the complete release of the server instance to ensure no
access will be performed on the proxy via itself. The refcount must not
be decremented if a server is freed without "del server" invokation.

Another solution could be for servers to always increment the refcount.
However, for now in haproxy refcount usage is limited, so the current
approach is preferred. It should also ensure that if the refcount is
still incremented, it may indicate that some servers are not completely
purged themselves.

Note that this patch may cause issues if "del backend" are used in
parallel with LUA scripts referencing servers. Currently, any servers
referenced by LUA must be released by its garbage collector to ensure it
can be finally freed. However, it appeas that in some case the gc does
not run for several minutes. At least this has been observed with Lua
version 5.4.8. In the end, this will result in indefinitely blocking of
"del backend" commands.
2026-03-02 14:08:30 +01:00
Amaury Denoyelle
ee1f0527c6 MINOR: proxy: rename default refcount to avoid confusion
Rename proxy conf <refcount> to <def_ref>. This field only serves for
defaults proxy instances. The objective is to avoid confusion with the
newly introduced <refcount> field used for dynamic backends.

As an optimization, it could be possible to remove <def_ref> and only
use <refcount> also for defaults proxies usage. However for now the
simplest solution is implemented.

This patch does not bring any functional change.
2026-03-02 14:07:40 +01:00
Amaury Denoyelle
f3127df74d MINOR: proxy: add refcount to proxies
Implement refcount notion into proxy structure. The objective is to be
able to increment refcount on proxy to prevent its deletion temporarily.
This is similar to the server refcount : "del backend" is not blocked
and will remove the targetted instance from the global proxies_list.
However, the final free operation is delayed until the refcount is null.

As stated above, the API is similar to servers. Proxies are initialized
with a refcount of 1. Refcount can be incremented via proxy_take(). When
no longer useful, refcount is decremented via proxy_drop() which
replaces the older free_proxy(). Deinit is only performed once refcount
is null.

This commit also defines flag PR_FL_DELETED. It is set when a proxy
instance has been removed via a "del backend" CLI command. This should
serve as indication to modules which may still have a refcount on the
target proxy so that they can release it as soon as possible.

Note that this new refcount is completely ignored for a default proxy
instance. For them, proxy_take() is pure noop. Free is immediately
performed on first proxy_drop() invokation.
2026-03-02 10:44:59 +01:00
Amaury Denoyelle
ebbdfc5915 MINOR: lua: use watcher for proxies iterator
Ensures proxies iteration via lua functions is safe via a new watcher
member. The principle is similar to the one already used for servers
iteration.
2026-03-02 10:36:21 +01:00
Amaury Denoyelle
dd1990a97a MINOR: promex: use watcher to iterate over backend instances
Ensures proxies iteration in promex applet is safe via a new watcher
member. The principle is similar to the one already used for servers
iteration.

Note that ctx.p[0] is not updated anymore at the end of a function, as
this is automatically done via the watcher itself.
2026-02-27 10:28:24 +01:00
Amaury Denoyelle
20376c54e2 MINOR: stats: protect proxy iteration via watcher
Define a new <px_watch> watcher member in stats applet context. It is
used to register the applet on a proxy when iterating over the proxies
list. <obj1> is automatically updated via the watcher interaction.
Watcher is first initialized prior to stats_dump_proxies() invocation.

This guarantees that stats dump is safe even if applet yields and a
backend is removed in parallel.
2026-02-27 10:28:24 +01:00
Amaury Denoyelle
4bcfc09acf MINOR: proxy: define proxy watcher member
Define a new member watcher_list in proxy. It will be used to register
modules which iterate over the proxies list. This will ensure that the
operation is safe even if a backend is removed in parallel.
2026-02-27 10:28:24 +01:00
Amaury Denoyelle
08623228a1 MINOR: proxy: define a basic "del backend" CLI
Add "del backend" handler which is restricted to admin level. Along with
it, a new function be_check_for_deletion() is used to test if the
backend is removable.
2026-02-27 10:28:24 +01:00
Amaury Denoyelle
d166894fef MINOR: server: refactor srv_detach()
Correct documentation for srv_detach() which previously stated that this
function could be called for a server even if not stored in its proxy
list. In fact there is a BUG_ON() which detects this case.
2026-02-26 18:24:36 +01:00
Amaury Denoyelle
dd55f2246e MINOR: proxy: convert proxy flags to uint
Proxy flags member were of type char. This will soon enough not be
sufficient as new flags will be defined. As such, convert flags member
to unsigned int type.
2026-02-26 18:24:36 +01:00
Amaury Denoyelle
78549c66c5 BUG/MINOR: proxy: add dynamic backend into ID tree
Add missing proxy_index_id() call in "add backend" handler. This step is
responsible to store the newly created proxy instance in the
used_proxy_id global tree.

No need to backport.
2026-02-26 18:24:36 +01:00
Amaury Denoyelle
5000f0b2ef BUG/MINOR: promex: fix server iteration when last server is deleted
Servers iteration via promex is now resilient to server runtime deletion
thanks to the watcher mechanism. However, the watcher was not correctly
initialized which could cause duplicate metrics reporting.

This issue happens when promex dump yielded when manipulating the last
server of a proxy. If this server is removed in parallel, <sv> pointer
will be set to NULL when promex resumes. Instead of switching to another
proxy, the code would reuse the same one and iterate again on the same
server list.

To fix this issue, <sv> pointer must not be reinitialized just after a
resumption point. Instead, this is now performed before
promex_dump_srv_metrics(), or just after switching to another proxy
instance. Thus, on resumption, if promex_dump_srv_metrics() is started
with <sv> as NULL, it means that the server was deleted and the end of
the current proxy list is reached, hence iteration is restarted on the
next proxy instance.

Note that ctx.p[1] does not need to be manually updated at the end of
promex_dump_srv_metrics() as srv_watch already does that.

This patch must be backported up to 3.0.
2026-02-26 18:24:36 +01:00
Amaury Denoyelle
00a106059e MINOR: promex: test applet resume in stress mode
Implement a stress mode with force yield for promex applet each time a
metric is displayed. This is implemented by returning 0 in
promex_dump_ts() each time the output buffer is not empty.

To test this, haproxy must be compiled with DEBUG_STRESS and the
following configuration must be used :

  global
    stress-level 1
2026-02-26 18:24:36 +01:00
Willy Tarreau
9db62d408a BUG/MINOR: call EXTRA_COUNTERS_FREE() before srv_free_params() in srv_drop()
Some checks failed
Contrib / build (push) Has been cancelled
alpine/musl / gcc (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
As seen with the last changes to counters allocation, the move of the
counters storage to the thread group as operated in commit 04a9f86a85
("MEDIUM: counters: add a dedicated storage for extra_counters in various
structs") causes some random errors when using ASAN, because the extra
counters are freed in srv_drop() after calling srv_free_params(), which
is responsible for freeing the per-thread group storage.

For the proxies however it's OK because free calls are made before the
call to deinit_proxy() which frees the per_tgrp area.

No backport is needed, this is purely 3.4-dev.
2026-02-26 17:24:59 +01:00
Willy Tarreau
b604064980 MEDIUM: counters: make EXTRA_COUNTERS_GET() consider tgid
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
Now we store and retrieve only counters for the current tgid when more
than one is supported. This allows to significantly reduce contention
on shared stats. The haterm utility saw its performance increase from
4.9 to 5.8M req/s in H1, and 6.0 to 7.6M for H2, both with 5 groups of
16 threads, showing that we don't necessarily need insane amounts of
groups.
2026-02-26 17:03:53 +01:00
Willy Tarreau
9019a5db93 MEDIUM: counters: return aggregate extra counters in ->fill_stats()
Now thanks to new macro EXTRA_COUNTERS_AGGR() we can iterate over all
thread groups storages when returning the data for a given metric. This
remains convenient and mostly transparent. The caller continues to pass
the pointer to the metric in the first group, and offsets are calculated
for all other groups and data summed. For now all groups except the
first one contain only zeroes but reported values are nevertheless
correct.
2026-02-26 17:03:53 +01:00
Willy Tarreau
de0eddf512 MINOR: counters: add EXTRA_COUNTERS_BASE() to retrieve extra_counters base storage
The goal is to always retrieve the storage address of the first thread
group for the given module. This will be used to iterate over all thread
groups. For now it returns the same value as EXTRA_COUNTERS_GET().
2026-02-26 17:03:53 +01:00
Willy Tarreau
a60e1fcf7f MEDIUM: counters: store the number of thread groups accessing extra_counters
In order to be able to properly allocate all storage and retrieve data
from there, we'll need to know how many thread groups are supposed to
access it. Let's store the number of thread groups at init time. If the
tgrp_step is zero, there's always only one tg though.

Now EXTRA_COUNTERS_ALLOC() takes this number of thread groups in argument
and stores it in the structure. It also allocates as many areas as needed,
incrementing the datap pointer by the step for each of them.

EXTRA_COUNTERS_FREE() uses this info to free all allocated areas.

EXTRA_COUNTERS_INIT() initializes all allocated areas, this is used
elsewhere to clear/preset counters, e.g. in proxy_stats_clear_counters().
It involves a memcpy() call for each array, which is normally preset to
something empty but might also be used to preset certain non-scalar
fields such as an instance name.
2026-02-26 17:03:53 +01:00
Willy Tarreau
7ac47910a2 MINOR: counters: store a tgroup step for extra_counters to access multiple tgroups
We'll need to permit any user to update its own tgroup's extra counters
instead of the global ones. For this we now store the per-tgroup step
between two consecutive data storages, for when they're stored in a
tgroup array. When shared (e.g. resolvers or listeners), we just store
zero to indicate that it doesn't scale with tgroups. For now only the
registration was handled, it's not used yet.
2026-02-26 17:03:53 +01:00
Willy Tarreau
04a9f86a85 MEDIUM: counters: add a dedicated storage for extra_counters in various structs
Servers, proxies, listeners and resolvers all use extra_counters. We'll
need to move the storage to per-tgroup for those where it matters. Now
we're relying on an external storage, and the data member of the struct
was replaced with a pointer to that pointer to data called datap. When
the counters are registered, these datap are set to point to relevant
locations. In the case of proxies and servers, it points to the first
tgrp's storage. For listeners and resolvers, it points to a local
storage. The rationale here is that listeners are limited to a single
group anyway, and that resolvers have a low enough load so that we do
not care about contention there.

Nothing should change for the user at this point.
2026-02-26 17:03:47 +01:00
Willy Tarreau
8dd22a62a4 CLEANUP: counters: only retrieve zeroes for unallocated extra_counters
Since version 2.4 with commit 7f8f6cb926 ("BUG/MEDIUM: stats: prevent
crash if counters not alloc with dummy one") we can afford to always
update extra_counters because we know they're always either allocated
or linked to a dedicated trash. However, the ->fill_stats() callbacks
continue to access such values, making it technically possible to
retrieve random counters from this trash, which is not really clean.
Let's implement an explicit test in the ->fill_stats() functions to
only return 0 for the metric when not allocated like this. It's much
cleaner because it guarantees that we're returning an empty counter
in this case rather than random values.

The situation currently happens for dummy servers like the ones used
in Lua proxies as well as those used by rings (e.g. used for logging
or traces). Normally, none of the objects retrieved via stats or
Prometheus is concerned by this unallocated extra_counters situation,
so this is more about a cleanup than a real fix.
2026-02-26 08:24:03 +01:00
Willy Tarreau
95a9f472d2 MEDIUM: counters: change the fill_stats() API to pass the module and extra_counters
We'll soon need to iterate over thread groups in the fill_stats() functions,
so let's first pass the extra_counters and stats_module pointers to the
fill_stats functions. They now call EXTRA_COUNTERS_GET() themselves with
these elements in order to retrieve the required pointer. Nothing else
changed, and it's getting even a bit more transparent for callers.

This doesn't change anything visible however.
2026-02-26 08:24:03 +01:00
Willy Tarreau
56fc12d6fa CLEANUP: stats: drop stats.h / stats-t.h where not needed
A number of C files include stats.h or stats-t.h, many of which were
just to access the counters. Now those which really need counters rely
on counters.h or counters-t.h, which already reduces the amount of
preprocessed code to be built (~3000 lines or about 0.05%).
2026-02-26 08:24:03 +01:00
Willy Tarreau
2b463e9b1f REORG: stats/counters: move extra_counters to counters not stats
It was always difficult to find extra_counters when the rest of the
counters are now in counters-t.h. Let's move the types to counters-t.h
and the macros to counters.h. Stats include them since they're used
there. But some users could be cleaned from the stats definitions now.
2026-02-26 08:24:03 +01:00
Willy Tarreau
9910af6117 CLEANUP: quic-stats: include counters from quic_stats
There's something a bit awkward in the way stats counters are inherited
through the QUIC modules: quic_conn-t includes quic_stats-t.h, which
declares quic_stats_module as extern from a type that's not known from
this file. And anyway externs should not be exported from type defintions
since they're not part of the ABI itself.

This commit moves the declaration to quic_stats.h which now takes care
to include stats-t.h to get the definition of struct stats_module. The
few users who used to learn it through quic_conn-t.h now include it
explicitly. As a bonus this reduces the number of preprocessed lines
by 5000 (~0.1%).

By the way, it looks like struct stats_module could benefit from being
moved off stats-t.h since it's only used at places where the rest of
the stats is not needed. Maybe something to consider for a future
cleanup.
2026-02-26 08:24:03 +01:00
Willy Tarreau
fb5e280e0d CLEANUP: tree-wide: drop a few useless null-checks before free()
We only support platforms where free(NULL) is a NOP so that
null checks are useless before free(). Let's drop them to keep
the code clean. There were a few in cfgparse-global, flt_trace,
ssl_sock and stats.
2026-02-26 08:24:03 +01:00
Willy Tarreau
709c3be845 BUG/MINOR: server: adjust initialization order for dynamic servers
It appears that in cli_parse_add_server(), we're calling srv_alloc_lb()
and stats_allocate_proxy_counters_internal() before srv_preinit() which
allocates the thread groups. LB algos can make use of the per_tgrp part
which is initialized by srv_preinit(). Fortunately for now no algo uses
both tgrp and ->server_init() so this explains why this remained
unnoticed to date. Also, extra counters will soon require per_tgrp to
already be initialized. So let's move these between srv_preinit() and
srv_postinit(). It's possible that other parts will have to be moved
in between.

This could be backported to recent versions for the sake of safety but
it looks like the current code cannot tell the difference.
2026-02-26 08:24:03 +01:00
Willy Tarreau
44932b6c41 BUG/MEDIUM: mux-h2: make sure to always report pending errors to the stream
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
Some stream parsing errors that do not affect the connection result in
the parsed block not being transferred from the rx buffer to the channel
and not being reported upstream in rcv_buf(), causing the stconn to time
out. Let's detect this condition, and propagate term flags anyway since
no more progress will be made otherwise.

This should be backported at least till 3.2, probably even 2.8.
2026-02-26 00:30:42 +01:00
Willy Tarreau
e67e36c9eb MINOR: mux-h2: add a new setting, "tune.h2.log-errors" to tweak error logging
The H2 mux currently logs whenever some decoding fails. Most of the errors
happen at the connection level, but some are even at the stream level,
meaning that multiple logs can be emitted for a given connection, which
can quickly use some resource for little value. This new setting allows
to tweak this and decide to only log errors that affect the connection,
or even none at all.

This should be backported at least as far as 3.2.
2026-02-25 22:43:40 +01:00
Willy Tarreau
cad6e0b3da MINOR: mux-h2: also count glitches on invalid trailers
Two cases were not causing glitches to be incremented:
  - invalid trailers
  - trailers on closed streams

This patch addresses this. It could be backported, at least to 3.2.
2026-02-25 22:03:16 +01:00
Frederic Lecaille
5af42fa342 CLEANUP: ssl: remove outdated comments
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
ssl_sock_srv_try_reuse_sess() was modified by this commit to no longer
fail (it now returns void), but the related comments remained:

  BUG/MINOR: quic: missing app ops init during backend 0-RTT sessions

This patch cleans them up.
2026-02-25 11:25:05 +01:00
Frederic Lecaille
89c75b0777 BUG/MINOR: quic: missing app ops init during backend 0-RTT sessions
The QUIC mux requires "application operations" (app ops), which are a list
of callbacks associated with the application level (i.e., h3, h0.9) and
derived from the ALPN. For 0-RTT, when the session cache cannot be reused
before activation, the current code fails to reach the initialization of
these app ops, causing the mux to crash during its initialization.

To fix this, this patch restores the behavior of
ssl_sock_srv_try_reuse_sess(), whose purpose was to reuse sessions stored
in the session cache regardless of whether 0-RTT was enabled, prior to
this commit:

  MEDIUM: quic-be: modify ssl_sock_srv_try_reuse_sess() to reuse backend
  sessions (0-RTT)

With this patch, this function now does only one thing: attempt to reuse a
session, and that's it!

This patch allows ignoring whether a session was successfully reused from
the cache or not. This directly fixes the issue where app ops
initialization was skipped upon a session cache reuse failure. From a
functional standpoint, starting a mux without reusing the session cache
has no negative impact; the mux will start, but with no early data to
send.

Finally, there is the case where the ALPN is reset when the backend is
stopped. It is critical to continue locking read access to the ALPN to
secure shared access, which this patch does. It is indeed possible for the
server to be stopped between the call to connect_server() and
quic_reuse_srv_params(). But this cannot prevent the mux to start
without app ops. This is why a 'TODO' section was added, as a reminder that a
race condition regarding the ALPN reset still needs to be fixed.

Must be backported to 3.3
2026-02-25 11:13:52 +01:00
Olivier Houchard
84837b6e70 BUG/MEDIUM: cpu-topo: Distribute CPUs fairly across groups
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
Make sure CPUs are distributed fairly across groups, in case the number
of groups to generate is not a divider of the number of CPUs, otherwise
we may end up with a few groups that will have no CPU bound to them.

This was introduced in 3.4-dev2 with commit 56fd0c1a5c ("MEDIUM: cpu-topo:
Add an optional directive for per-group affinity"). No backport is
needed unless this commit is backported.
2026-02-24 08:17:16 +01:00
Frederic Lecaille
ca5332a9c3 BUG/MINOR: haterm: cannot reset default "haterm" mode
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
When "mode haterm" was set in a "defaults" section, it could not be
overridden in subsequent sections using the "mode" keyword. This is because
the proxy stream instantiation callback was not being reset to the
default stream_new() value.

This could break the stats URI with a configuration such as:

    defaults
        mode haterm
        # ...

    frontend stats
		bind :8181
		mode http
		stats uri /

This patch ensures the ->stream_new_from_sc() proxy callback is reset
to stream_new() when the "mode" keyword is parsed for any mode other
than "haterm".

No need to backport.
2026-02-23 17:57:19 +01:00
Maxime Henrion
a9dc8e2587 MINOR: quic: add a new metric for ncbuf failures
Some checks failed
Contrib / build (push) Has been cancelled
alpine/musl / gcc (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
This counts the number of times we failed to add data to the ncbuf
buffer because of the gap size limit.
2026-02-23 17:47:45 +01:00
Amaury Denoyelle
05d73aa81c MINOR: proxy: improve code when checking server name conflicts
During proxy_finalize(), a lookup is performed over the servers by name
tree to detect any collision. Only the first conflict for each server
instance is reported to avoid a combinatory explosion with too many
alerts shown.

Previously, this was written using a for loop without any iteration.
Replace this by a simple if statement as this is cleaner.

This should fix github issue #3276.
2026-02-23 16:28:41 +01:00
Amaury Denoyelle
c528824094 MINOR: ncbmbuf: improve itbmap_next() code
itbmap_next() advances an iterator over a ncbmbuf buffer storage. When
reaching the end of the buffer, <b> field is set to NULL, and the caller
is expected to stop working with the iterator.

Complete this part to ensure that itbmap type is fully initialized in
case null iterator value is returned. This is not strictly required
given the above description, but this is better to avoid any possible
future mistake.

This should fix coverity issue from github #3273.

This could be backported up to 2.8.
2026-02-23 16:28:41 +01:00
Willy Tarreau
868dd3e88b MINOR: traces: always mark trace_source as thread-aligned
Some perf profiles occasionally show that reading the trace source's
state can take some time, which is not expected at all. It just happens
that the trace_source is not cache-aligned so depending on linkage, it
may share a cache line with a more active variable, thereby inducing a
slow down to all threads trying to read the variable.

Let's always mark it aligned to avoid this. For now the problem was not
observed again.
2026-02-23 16:22:59 +01:00
Christopher Faulet
c2b5446292 BUG/MEDIUM: spoe: Acquire context buffer in applet before consuming a frame
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
Changes brought to support large buffers revealed a bug in the SPOE applet
when a frame is copied in the SPOE context buffer. A b_xfer() was performed
without allocating the SPOE context buffer. It is not expected. As stated in
the function documentation, the caller is responsible for ensuring there is
enough space in the destination buffer. So first of all, it must ensure this
buffer was allocated.

With recent changes, we are able to hit a BUG_ON() because the swap is no
longer possible if source and destination buffers size are not the same.

This patch should fix the issue #3286. It could be backported as far as 3.1.
2026-02-23 15:47:25 +01:00
Willy Tarreau
bbd8492c22 CLENAUP: cfgparse: accept-invalid-http-* does not support "no"/"defaults"
Some options do not support "no" nor "defaults" and they're placed after
the check for their absence. However, "accept-invalid-http-request" and
"accept-invalid-http-response" still used to check for the flags that
come with these prefixes, but Coverity noticed this was dead code in
github issue #3272. Let's just drop the test.

No backport needed as it's just dead code.
2026-02-23 15:44:40 +01:00
Ilia Shipitsin
bf363a7135 CI: remove redundant "halog" compilation
since 6499c0a0d5 halog is being build
in vtest workflow, no need to build it two times
2026-02-23 11:11:26 +01:00
Ilia Shipitsin
c44d6c6c71 CI: use the latest docker for QUIC Interop
quic-interop runner is using features available in Docker v28.1
while Github runner includes v28.0

let's for sure setup the latest available
2026-02-23 11:11:20 +01:00
Frederic Lecaille
dfa8907a3d CLEANUP: ssl: Remove a useless variable from ssl_gen_x509()
This function was recently created by moving code from acme_gen_tmp_x509()
(in acme.c) to ssl_gencrt.c (ssl_gen_x509()). The <ctmp> variable was
initialized and then freed without ever being used. This was already the
case in the original acme_gen_tmp_x509() function.

This patch removes these useless statements.

Reported in GH #3284
2026-02-23 10:47:15 +01:00
Frederic Lecaille
bb3304c6af CLEANUP: haterm: avoid static analyzer warnings about rand() use
Avoid such a warnings from coverity:

CID 1645121: (#1 of 1): Calling risky function (DC.WEAK_CRYPTO)
dont_call: random should not be used for security-related applications,
because linear congruential algorithms are too easy to break.

Reported in GH #3283 and #3285
2026-02-23 10:39:59 +01:00
Frederic Lecaille
a5a053e612 CLEANUP: haterm: remove unreachable labels hstream_add_data()
This function does not return a status. It nevers fails.
Remove some useled dead code.
2026-02-23 10:13:25 +01:00
Mia Kanashi
5aa30847ae BUG/MINOR: acme: fix incorrect number of arguments allowed in config
Fix incorrect number of arguments allowed in config for keywords
"chalenge" and "account-key".
2026-02-23 09:46:34 +01:00
Hyeonggeun Oh
ca5c07b677 MINOR: ssl: clarify error reporting for unsupported keywords
This patch changes the registration of the following keywords to be
unconditional:
  - ssl-dh-param-file
  - ssl-engine
  - ssl-propquery, ssl-provider, ssl-provider-path
  - ssl-default-bind-curves, ssl-default-server-curves
  - ssl-default-bind-sigalgs, ssl-default-server-sigalgs
  - ssl-default-bind-client-sigalgs, ssl-default-server-client-sigalgs

Instead of excluding them at compile time via #ifdef guards in the keyword
registration table, their parsing functions now check feature availability
at runtime and return a descriptive error when the feature is missing.

For features controlled by the SSL library (providers, curves, sigalgs,
DH), the error message includes the actual OpenSSL version string via
OpenSSL_version(OPENSSL_VERSION), so users can immediately identify which
library they are running rather than seeing cryptic internal macro names.

For ssl-dh-param-file, the message also includes "(no DH support)" as a
hint, since OPENSSL_NO_DH can be set either by an OpenSSL build or by
HAProxy itself in certain configurations.

For ssl-engine, which depends on a HAProxy build-time flag (USE_ENGINE),
the message retains the flag name as it is more actionable for the user.

This addresses issue https://github.com/haproxy/haproxy/issues/3246.
2026-02-23 09:40:18 +01:00
William Lallemand
8d54cda0af BUG/MINOR: acme: wrong labels logic always memprintf errmsg
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
In acme_req_finalize(), acme_req_challenge(), acme_req_neworder(),
acme_req_account(), and acme_post_as_get(), the success path always
calls unconditionally memprintf(errmsg, ...).

This may result in a leak of errmsg.

Additionally, acme_res_chkorder(), acme_res_finalize(), acme_res_auth(),
and acme_res_neworder() had unused 'out:' labels that were removed.

Must be backported as far as 3.2.
2026-02-22 22:30:38 +01:00
William Lallemand
e63722fed4 BUG/MINOR: acme: acme_ctx_destroy() leaks auth->dns
365a696 ("MINOR: acme: emit a log for DNS-01 challenge response")
introduces the auth->dns member which is istdup(). But this member is
never free, instead auth->token was freed twice by mistake.

Must be backported to 3.2.
2026-02-21 16:10:29 +01:00
Amaury Denoyelle
0f95e73032 BUG/MINOR: quic/h3: display QUIC/H3 backend module on HTML stats
Some checks failed
Contrib / build (push) Has been cancelled
alpine/musl / gcc (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
QUIC is now implemented on the backend side. Complete definitions for
QUIC/H3 stats module to add STATS_PX_CAP_BE capability.

This change is necessary to display QUIC/H3 counters on backend lines
for HTML stats page.

This should be backported up to 3.3.
2026-02-20 14:08:27 +01:00
Amaury Denoyelle
5f26cf162c MINOR: quic: add BUG_ON() on half_open_conn counter access from BE
half_open_conn is a proxy counter used to account for quic_conn in
half-open state : this represents a connection whose address is not yet
validated (handshake successful, or via token validation).

This counter only has sense for the frontend side. Currently, code is
safe as access is only performed if quic_conn is not yet flagged with
QUIC_FL_CONN_PEER_VALIDATED_ADDR, which is always set for backend
connections.

To better reflect this, add a BUG_ON() when half_open_conn is
incremented/decremented to ensure this never occurs for backend
connections.
2026-02-20 14:08:27 +01:00
Amaury Denoyelle
b8cb8e1a65 BUG/MINOR: quic: fix counters used on BE side
quic_conn is initialized with a pointer to its proxy counters. These
counters are then updated during the connection lifetime.

Counters pointer was incorrect for backend quic_conn, as it always
referenced frontend counters. For pure backend, no stats would be
updated. For listen instances, this resulted in incorrect stats
reporting.

Fix this by correctly set proxy counters based on the connection side.

This must be backported up to 3.3.
2026-02-20 14:08:27 +01:00
Frederic Lecaille
db360d466b BUG/MINOR: haterm: missing allocation check in copy_argv()
This is a very minor bug with a very low probability of occurring.
However, it could be flagged by a static analyzer or result in a small
contribution, which is always time-consuming for very little gain.
2026-02-20 12:12:10 +01:00
Frederic Lecaille
92581043fb MINOR: haterm: add long options for QUIC and TCP "bind" settings
Add the --quic-bind-opts and --tcp-bind-opts long options to append
settings to all QUIC and TCP bind lines. This requires modifying the argv
parser to first process these new options, ensuring they are available
during the second argv pass to be added to each relevant "bind" line.
2026-02-20 12:00:34 +01:00
Frederic Lecaille
8927426f78 MINOR: haterm: provide -b and -c options (RSA key size, ECDSA curves)
Add -b and -c options to the haterm argv parser. Use -b to specify the RSA
private key size (in bits) and -c to define the ECDSA certificate curves.
These self-signed certificates are required for haterm SSL bindings.
2026-02-20 10:43:54 +01:00
Amaury Denoyelle
f71b2f4338 BUG/MINOR: server: enable no-check-sni-auto for dynamic servers
Allows server keyword "no-check-sni-auto" for dynamic servers. This may
be necessary to users who do not want to benefit from auto SNI for
checks.

Keyword "check-sni-auto" is still deactivated for dynamic servers, for
the same reason as "sni-auto" (cf the previous patch for a complete
explanation).

This must be backported up to 3.3.
2026-02-20 09:02:47 +01:00
Amaury Denoyelle
de5fc2f515 BUG/MINOR: server: set auto SNI for dynamic servers
Auto SNI configuration is configured during check config validity.
However, nothing was implemented for dynamic servers.

Fix this by implementing auto SNI configuration during "add server" CLI
handler. Auto SNI configuration code is moved in a dedicated function
srv_configure_auto_sni() called both for static and dynamic servers.

Along with this, allows the keyword "no-sni-auto" on dynamic servers, so
that this process can be deactivated if wanted. Note that "sni-auto"
remains unavailable as it only makes sense with default-servers which
are never used for dynamic server creation.

This must be backported up to 3.3.
2026-02-20 09:02:47 +01:00
Amaury Denoyelle
2b0fc33114 BUG/MINOR: proxy: detect strdup error on server auto SNI
There was no check on the result of strdup() used to setup auto SNI on a
server instance during check config validity. In case of failure, the
error would be silently ignored as the following server_parse_exprs()
does nothing when <sni_expr> server field is NULL. Hence, no SNI would
be used on the server, without any error nor warning reported.

Fix this by adding a check on strdup() return value. On error, ERR_ABORT
is reported along with an alert, parsing should be interrupted as soon
as possible.

This must be backported up to 3.3. Note that the related code in this
case is present in cfgparse.c source file.
2026-02-20 09:02:47 +01:00
William Lallemand
f5a182c7e7 CLEANUP: acme: remove duplicate includes
Some checks are pending
Contrib / build (push) Waiting to run
alpine/musl / gcc (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
Remove a bunch of duplicate includes in acme.c.
2026-02-19 23:57:20 +01:00
191 changed files with 6947 additions and 2756 deletions

11
.github/matrix.py vendored
View file

@ -19,9 +19,10 @@ from packaging import version
# #
# this CI is used for both development and stable branches of HAProxy # this CI is used for both development and stable branches of HAProxy
# #
# naming convention used, if branch name matches: # naming convention used, if branch/tag name matches:
# #
# "haproxy-" - stable branches # "haproxy-" - stable branches
# "vX.Y.Z" - release tags
# otherwise - development branch (i.e. "latest" ssl variants, "latest" github images) # otherwise - development branch (i.e. "latest" ssl variants, "latest" github images)
# #
@ -120,11 +121,13 @@ def clean_compression(compression):
def main(ref_name): def main(ref_name):
print("Generating matrix for branch '{}'.".format(ref_name)) print("Generating matrix for branch '{}'.".format(ref_name))
is_stable = "haproxy-" in ref_name or re.match(r'^v\d+\.\d+\.\d+$', ref_name)
matrix = [] matrix = []
# Ubuntu # Ubuntu
if "haproxy-" in ref_name: if is_stable:
os = "ubuntu-24.04" # stable branch os = "ubuntu-24.04" # stable branch
os_arm = "ubuntu-24.04-arm" # stable branch os_arm = "ubuntu-24.04-arm" # stable branch
else: else:
@ -228,7 +231,7 @@ def main(ref_name):
# "BORINGSSL=yes", # "BORINGSSL=yes",
] ]
if "haproxy-" not in ref_name: # development branch if not is_stable: # development branch
ssl_versions = ssl_versions + [ ssl_versions = ssl_versions + [
"OPENSSL_VERSION=latest", "OPENSSL_VERSION=latest",
"LIBRESSL_VERSION=latest", "LIBRESSL_VERSION=latest",
@ -276,7 +279,7 @@ def main(ref_name):
) )
# macOS on dev branches # macOS on dev branches
if "haproxy-" not in ref_name: if not is_stable:
os = "macos-26" # development branch os = "macos-26" # development branch
TARGET = "osx" TARGET = "osx"

View file

@ -11,9 +11,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- name: Compile admin/halog/halog
run: |
make admin/halog/halog
- name: Compile dev/flags/flags - name: Compile dev/flags/flags
run: | run: |
make dev/flags/flags make dev/flags/flags

View file

@ -21,6 +21,9 @@ jobs:
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- name: Update Docker to the latest
uses: docker/setup-docker-action@v4
- name: Build Docker image - name: Build Docker image
id: push id: push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6

View file

@ -21,6 +21,9 @@ jobs:
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- name: Update Docker to the latest
uses: docker/setup-docker-action@v4
- name: Build Docker image - name: Build Docker image
id: push id: push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6

244
CHANGELOG
View file

@ -1,6 +1,250 @@
ChangeLog : ChangeLog :
=========== ===========
2026/03/20 : 3.4-dev7
- BUG/MINOR: stconn: Increase SC bytes_out value in se_done_ff()
- BUG/MINOR: ssl-sample: Fix sample_conv_sha2() by checking EVP_Digest* failures
- BUG/MINOR: backend: Don't get proto to use for webscoket if there is no server
- BUG/MINOR: jwt: Missing 'jwt_tokenize' return value check
- MINOR: flt_http_comp: define and use proxy_get_comp() helper function
- MEDIUM: flt_http_comp: split "compression" filter in 2 distinct filters
- CLEANUP: flt_http_comp: comp_state doesn't bother about the direction anymore
- BUG/MINOR: admin: haproxy-reload use explicit socat address type
- MEDIUM: admin: haproxy-reload conversion to POSIX sh
- BUG/MINOR: admin: haproxy-reload rename -vv long option
- SCRIPTS: git-show-backports: hide the common ancestor warning in quiet mode
- SCRIPTS: git-show-backports: add a restart-from-last option
- MINOR: mworker: add a BUG_ON() on mproxy_li in _send_status
- BUG/MINOR: mworker: don't set the PROC_O_LEAVING flag on master process
- Revert "BUG/MINOR: jwt: Missing 'jwt_tokenize' return value check"
- MINOR: jwt: Improve 'jwt_tokenize' function
- MINOR: jwt: Convert EC JWK to EVP_PKEY
- MINOR: jwt: Parse ec-specific fields in jose header
- MINOR: jwt: Manage ECDH-ES algorithm in jwt_decrypt_jwk function
- MINOR: jwt: Add ecdh-es+axxxkw support in jwt_decrypt_jwk converter
- MINOR: jwt: Manage ec certificates in jwt_decrypt_cert
- DOC: jwt: Add ECDH support in jwt_decrypt converters
- MINOR: stconn: Call sc_conn_process from the I/O callback if TASK_WOKEN_MSG state was set
- MINOR: mux-h2: Rely on h2s_notify_send() when resuming h2s for sending
- MINOR: mux-spop: Rely on spop_strm_notify_send() when resuming streams for sending
- MINOR: muxes: Wakup the data layer from a mux stream with TASK_WOKEN_IO state
- MAJOR: muxes: No longer use app_ops .wake() callback function from muxes
- MINOR: applet: Call sc_applet_process() instead of .wake() callback function
- MINOR: connection: Call sc_conn_process() instead of .wake() callback function
- MEDIUM: stconn: Remove .wake() callback function from app_ops
- MINOR: check: Remove wake_srv_chk() function
- MINOR: haterm: Remove hstream_wake() function
- MINOR: stconn: Wakup the SC with TASK_WOKEN_IO state from opposite side
- MEDIUM: stconn: Merge all .chk_rcv() callback functions in sc_chk_rcv()
- MINOR: stconn: Remove .chk_rcv() callback functions
- MEDIUM: stconn: Merge all .chk_snd() callback functions in sc_chk_snd()
- MINOR: stconn: Remove .chk_snd() callback functions
- MEDIUM: stconn: Merge all .abort() callback functions in sc_abort()
- MINOR: stconn: Remove .abort() callback functions
- MEDIUM: stconn: Merge all .shutdown() callback functions in sc_shutdown()
- MINOR: stconn: Remove .shutdown() callback functions
- MINOR: stconn: Totally app_ops from the stconns
- MINOR: stconn: Simplify sc_abort/sc_shutdown by merging calls to se_shutdown
- DEBUG: stconn: Add a CHECK_IF() when I/O are performed on a orphan SC
- MEDIUM: mworker: exiting when couldn't find the master mworker_proc element
- BUILD: ssl: use ASN1_STRING accessors for OpenSSL 4.0 compatibility
- BUILD: ssl: make X509_NAME usage OpenSSL 4.0 ready
- BUG/MINOR: tcpcheck: Fix typo in error error message for `http-check expect`
- BUG/MINOR: jws: fix memory leak in jws_b64_signature
- DOC: configuration: http-check expect example typo
- DOC/CLEANUP: config: update mentions of the old "Global parameters" section
- BUG/MEDIUM: ssl: Handle receiving early data with BoringSSL/AWS-LC
- BUG/MINOR: mworker: always stop the receiving listener
- BUG/MEDIUM: ssl: Don't report read data as early data with AWS-LC
- BUILD: makefile: fix range build without test command
- BUG/MINOR: memprof: avoid a small memory leak in "show profiling"
- BUG/MINOR: proxy: do not forget to validate quic-initial rules
- MINOR: activity: use dynamic allocation for "show profiling" entries
- MINOR: tools: extend the pointer hashing code to ease manipulations
- MINOR: tools: add a new pointer hash function that also takes an argument
- MINOR: memprof: attempt different retry slots for different hashes on collision
- MINOR: tinfo: start to add basic thread_exec_ctx
- MINOR: memprof: prepare to consider exec_ctx in reporting
- MINOR: memprof: also permit to sort output by calling context
- MINOR: tools: add a function to write a thread execution context.
- MINOR: debug: report the execution context on thread dumps
- MINOR: memprof: report the execution context on profiling output
- MINOR: initcall: record the file and line declaration of an INITCALL
- MINOR: tools: decode execution context TH_EX_CTX_INITCALL
- MINOR: tools: support decoding ha_caller type exec context
- MINOR: sample: store location for fetch/conv via initcalls
- MINOR: sample: also report contexts registered directly
- MINOR: tools: support an execution context that is just a function
- MINOR: actions: store the location of keywords registered via initcalls
- MINOR: actions: also report execution contexts registered directly
- MINOR: filters: set the exec context to the current filter config
- MINOR: ssl: set the thread execution context during message callbacks
- MINOR: connection: track mux calls to report their allocation context
- MINOR: task: set execution context on task/tasklet calls
- MINOR: applet: set execution context on applet calls
- MINOR: cli: keep the info of the current keyword being processed in the appctx
- MINOR: cli: keep track of the initcall context since kw registration
- MINOR: cli: implement execution context for manually registered keywords
- MINOR: activity: support aggregating by caller also for memprofile
- MINOR: activity: raise the default number of memprofile buckets to 4k
- DOC: internals: short explanation on how thread_exec_ctx works
- BUG/MINOR: mworker: only match worker processes when looking for unspawned proc
- MINOR: traces: defer processing of "-dt" options
- BUG/MINOR: mworker: fix typo &= instead of & in proc list serialization
- BUG/MINOR: mworker: set a timeout on the worker socketpair read at startup
- BUG/MINOR: mworker: avoid passing NULL version in proc list serialization
- BUG/MINOR: sockpair: set FD_CLOEXEC on fd received via SCM_RIGHTS
- BUG/MEDIUM: stconn: Don't forget to wakeup applets on shutdown
- BUG/MINOR: spoe: Properly switch SPOE filter to WAITING_ACK state
- BUG/MEDIUM: spoe: Properly abort processing on client abort
- BUG/MEDIUM: stconn: Fix abort on close when a large buffer is used
- BUG/MEDIUM: stconn: Don't perform L7 retries with large buffer
- BUG/MINOR: h2/h3: Only test number of trailers inserted in HTX message
- MINOR: htx: Add function to truncate all blocks after a specific block
- BUG/MINOR: h2/h3: Never insert partial headers/trailers in an HTX message
- BUG/MINOR: http-ana: Swap L7 buffer with request buffer by hand
- BUG/MINOR: stream: Fix crash in stream dump if the current rule has no keyword
- BUG/MINOR: mjson: make mystrtod() length-aware to prevent out-of-bounds reads
- MEDIUM: stats-file/clock: automatically update now_offset based on shared clock
- MINOR: promex: export "haproxy_sticktable_local_updates" metric
- BUG/MINOR: spoe: Fix condition to abort processing on client abort
- BUILD: spoe: Remove unsused variable
- MINOR: tools: add a function to create a tar file header
- MINOR: tools: add a function to load a file into a tar archive
- MINOR: config: support explicit "on" and "off" for "set-dumpable"
- MINOR: debug: read all libs in memory when set-dumpable=libs
- DEV: gdb: add a new utility to extract libs from a core dump: libs-from-core
- MINOR: debug: copy debug symbols from /usr/lib/debug when present
- MINOR: debug: opportunistically load libthread_db.so.1 with set-dumpable=libs
- BUG/MINOR: mworker: don't try to access an initializing process
- BUG/MEDIUM: peers: enforce check on incoming table key type
- BUG/MINOR: mux-h2: properly ignore R bit in GOAWAY stream ID
- BUG/MINOR: mux-h2: properly ignore R bit in WINDOW_UPDATE increments
- OPTIM: haterm: use chunk builders for generated response headers
- BUG/MAJOR: h3: check body size with content-length on empty FIN
- BUG/MEDIUM: h3: reject unaligned frames except DATA
- BUG/MINOR: mworker/cli: fix show proc pagination losing entries on resume
- CI: github: treat vX.Y.Z release tags as stable like haproxy-* branches
- MINOR: freq_ctr: add a function to add values with a peak
- MINOR: task: maintain a per-thread indicator of the peak run-queue size
- MINOR: mux-h2: store the concurrent streams hard limit in the h2c
- MINOR: mux-h2: permit to moderate the advertised streams limit depending on load
- MINOR: mux-h2: permit to fix a minimum value for the advertised streams limit
- BUG/MINOR: mworker: fix sort order of mworker_proc in 'show proc'
- CLEANUP: mworker: fix tab/space mess in mworker_env_to_proc_list()
2026/03/05 : 3.4-dev6
- CLEANUP: acme: remove duplicate includes
- BUG/MINOR: proxy: detect strdup error on server auto SNI
- BUG/MINOR: server: set auto SNI for dynamic servers
- BUG/MINOR: server: enable no-check-sni-auto for dynamic servers
- MINOR: haterm: provide -b and -c options (RSA key size, ECDSA curves)
- MINOR: haterm: add long options for QUIC and TCP "bind" settings
- BUG/MINOR: haterm: missing allocation check in copy_argv()
- BUG/MINOR: quic: fix counters used on BE side
- MINOR: quic: add BUG_ON() on half_open_conn counter access from BE
- BUG/MINOR: quic/h3: display QUIC/H3 backend module on HTML stats
- BUG/MINOR: acme: acme_ctx_destroy() leaks auth->dns
- BUG/MINOR: acme: wrong labels logic always memprintf errmsg
- MINOR: ssl: clarify error reporting for unsupported keywords
- BUG/MINOR: acme: fix incorrect number of arguments allowed in config
- CLEANUP: haterm: remove unreachable labels hstream_add_data()
- CLEANUP: haterm: avoid static analyzer warnings about rand() use
- CLEANUP: ssl: Remove a useless variable from ssl_gen_x509()
- CI: use the latest docker for QUIC Interop
- CI: remove redundant "halog" compilation
- CLENAUP: cfgparse: accept-invalid-http-* does not support "no"/"defaults"
- BUG/MEDIUM: spoe: Acquire context buffer in applet before consuming a frame
- MINOR: traces: always mark trace_source as thread-aligned
- MINOR: ncbmbuf: improve itbmap_next() code
- MINOR: proxy: improve code when checking server name conflicts
- MINOR: quic: add a new metric for ncbuf failures
- BUG/MINOR: haterm: cannot reset default "haterm" mode
- BUG/MEDIUM: cpu-topo: Distribute CPUs fairly across groups
- BUG/MINOR: quic: missing app ops init during backend 0-RTT sessions
- CLEANUP: ssl: remove outdated comments
- MINOR: mux-h2: also count glitches on invalid trailers
- MINOR: mux-h2: add a new setting, "tune.h2.log-errors" to tweak error logging
- BUG/MEDIUM: mux-h2: make sure to always report pending errors to the stream
- BUG/MINOR: server: adjust initialization order for dynamic servers
- CLEANUP: tree-wide: drop a few useless null-checks before free()
- CLEANUP: quic-stats: include counters from quic_stats
- REORG: stats/counters: move extra_counters to counters not stats
- CLEANUP: stats: drop stats.h / stats-t.h where not needed
- MEDIUM: counters: change the fill_stats() API to pass the module and extra_counters
- CLEANUP: counters: only retrieve zeroes for unallocated extra_counters
- MEDIUM: counters: add a dedicated storage for extra_counters in various structs
- MINOR: counters: store a tgroup step for extra_counters to access multiple tgroups
- MEDIUM: counters: store the number of thread groups accessing extra_counters
- MINOR: counters: add EXTRA_COUNTERS_BASE() to retrieve extra_counters base storage
- MEDIUM: counters: return aggregate extra counters in ->fill_stats()
- MEDIUM: counters: make EXTRA_COUNTERS_GET() consider tgid
- BUG/MINOR: call EXTRA_COUNTERS_FREE() before srv_free_params() in srv_drop()
- MINOR: promex: test applet resume in stress mode
- BUG/MINOR: promex: fix server iteration when last server is deleted
- BUG/MINOR: proxy: add dynamic backend into ID tree
- MINOR: proxy: convert proxy flags to uint
- MINOR: server: refactor srv_detach()
- MINOR: proxy: define a basic "del backend" CLI
- MINOR: proxy: define proxy watcher member
- MINOR: stats: protect proxy iteration via watcher
- MINOR: promex: use watcher to iterate over backend instances
- MINOR: lua: use watcher for proxies iterator
- MINOR: proxy: add refcount to proxies
- MINOR: proxy: rename default refcount to avoid confusion
- MINOR: server: take proxy refcount when deleting a server
- MINOR: lua: handle proxy refcount
- MINOR: proxy: prevent backend removal when unsupported
- MINOR: proxy: prevent deletion of backend referenced by config elements
- MINOR: proxy: prevent backend deletion if server still exists in it
- MINOR: server: mark backend removal as forbidden if QUIC was used
- MINOR: cli: implement wait on be-removable
- MINOR: proxy: add comment for defaults_px_ref/unref_all()
- MEDIUM: proxy: add lock for global accesses during proxy free
- MEDIUM: proxy: add lock for global accesses during default free
- MINOR: proxy: use atomic ops for default proxy refcount
- MEDIUM: proxy: implement backend deletion
- REGTESTS: add a test on "del backend"
- REGTESTS: complete "del backend" with unnamed defaults ref free
- BUG/MINOR: hlua: fix return with push nil on proxy check
- BUG/MEDIUM: stream: Handle TASK_WOKEN_RES as a stream event
- MINOR: quic: use signed char type for ALPN manipulation
- MINOR: quic/h3: reorganize stream reject after MUX closure
- MINOR: mux-quic: add function for ALPN to app-ops conversion
- MEDIUM: quic/mux-quic: adjust app-ops install
- MINOR: quic: use server cache for ALPN on BE side
- BUG/MEDIUM: hpack: correctly deal with too large decoded numbers
- BUG/MAJOR: qpack: unchecked length passed to huffman decoder
- BUG/MINOR: qpack: fix 1-byte OOB read in qpack_decode_fs_pfx()
- BUG/MINOR: quic: fix OOB read in preferred_address transport parameter
- BUG/MEDIUM: qpack: correctly deal with too large decoded numbers
- BUG/MINOR: hlua: Properly enable/disable line receives from HTTP applet
- BUG/MEDIUM: hlua: Fix end of request detection when retrieving payload
- BUG/MINOR: hlua: Properly enable/disable receives for TCP applets
- MINOR: htx: Add a function to retrieve the HTTP version from a start-line
- MINOR: h1-htx: Reports non-HTTP version via dedicated flags
- BUG/MINOR: h1-htx: Be sure that H1 response version starts by "HTTP/"
- MINOR: http-ana: Save the message version in the http_msg structure
- MEDIUM: http-fetch: Rework how HTTP message version is retrieved
- MEDIUM: http-ana: Use the version of the opposite side for internal messages
- DEBUG: stream: Display the currently running rule in stream dump
- MINOR: filters: Use filter API as far as poissible to break loops on filters
- MINOR: filters: Set last_entity when a filter fails on stream_start callback
- MINOR: stream: Display the currently running filter per channel in stream dump
- DOC: config: Use the right alias for %B
- BUG/MINOR: channel: Increase the stconn bytes_in value in channel_add_input()
- BUG/MINOR: sample: Fix sample to retrieve the number of bytes received and sent
- BUG/MINOR: http-ana: Increment scf bytes_out value if an haproxy error is sent
- BUG/MAJOR: fcgi: Fix param decoding by properly checking its size
- BUG/MAJOR: resolvers: Properly lowered the names found in DNS response
- BUG/MEDIUM: mux-fcgi: Use a safe loop to resume each stream eligible for sending
- MINOR: mux-fcgi: Use a dedicated function to resume streams eligible for sending
- CLEANUP: qpack: simplify length checks in qpack_decode_fs()
- MINOR: counters: Introduce COUNTERS_UPDATE_MAX()
- MINOR: listeners: Update the frequency counters separately when needed
- MINOR: proxies: Update beconn separately
- MINOR: stats: Add an option to disable the calculation of max counters
2026/02/19 : 3.4-dev5 2026/02/19 : 3.4-dev5
- DOC: internals: addd mworker V3 internals - DOC: internals: addd mworker V3 internals
- BUG/MINOR: threads: Initialize maxthrpertgroup earlier. - BUG/MINOR: threads: Initialize maxthrpertgroup earlier.

View file

@ -1043,7 +1043,7 @@ IGNORE_OPTS=help install install-man install-doc install-bin \
uninstall clean tags cscope tar git-tar version update-version \ uninstall clean tags cscope tar git-tar version update-version \
opts reg-tests reg-tests-help unit-tests admin/halog/halog dev/flags/flags \ opts reg-tests reg-tests-help unit-tests admin/halog/halog dev/flags/flags \
dev/haring/haring dev/ncpu/ncpu dev/poll/poll dev/tcploop/tcploop \ dev/haring/haring dev/ncpu/ncpu dev/poll/poll dev/tcploop/tcploop \
dev/term_events/term_events dev/gdb/pm-from-core dev/term_events/term_events dev/gdb/pm-from-core dev/gdb/libs-from-core
ifneq ($(TARGET),) ifneq ($(TARGET),)
ifeq ($(filter $(firstword $(MAKECMDGOALS)),$(IGNORE_OPTS)),) ifeq ($(filter $(firstword $(MAKECMDGOALS)),$(IGNORE_OPTS)),)
@ -1077,6 +1077,9 @@ admin/dyncookie/dyncookie: admin/dyncookie/dyncookie.o
dev/flags/flags: dev/flags/flags.o dev/flags/flags: dev/flags/flags.o
$(cmd_LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@ $^ $(LDOPTS) $(cmd_LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@ $^ $(LDOPTS)
dev/gdb/libs-from-core: dev/gdb/libs-from-core.o
$(cmd_LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@ $^ $(LDOPTS)
dev/gdb/pm-from-core: dev/gdb/pm-from-core.o dev/gdb/pm-from-core: dev/gdb/pm-from-core.o
$(cmd_LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@ $^ $(LDOPTS) $(cmd_LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@ $^ $(LDOPTS)
@ -1178,7 +1181,7 @@ distclean: clean
$(Q)rm -f admin/dyncookie/dyncookie $(Q)rm -f admin/dyncookie/dyncookie
$(Q)rm -f dev/haring/haring dev/ncpu/ncpu{,.so} dev/poll/poll dev/tcploop/tcploop $(Q)rm -f dev/haring/haring dev/ncpu/ncpu{,.so} dev/poll/poll dev/tcploop/tcploop
$(Q)rm -f dev/hpack/decode dev/hpack/gen-enc dev/hpack/gen-rht $(Q)rm -f dev/hpack/decode dev/hpack/gen-enc dev/hpack/gen-rht
$(Q)rm -f dev/qpack/decode dev/gdb/pm-from-core $(Q)rm -f dev/qpack/decode dev/gdb/pm-from-core dev/gdb/libs-from-core
tags: tags:
$(Q)find src include \( -name '*.c' -o -name '*.h' \) -print0 | \ $(Q)find src include \( -name '*.c' -o -name '*.h' \) -print0 | \
@ -1332,7 +1335,8 @@ range:
echo "[ $$index/$$count ] $$commit #############################"; \ echo "[ $$index/$$count ] $$commit #############################"; \
git checkout -q $$commit || die 1; \ git checkout -q $$commit || die 1; \
$(MAKE) all || die 1; \ $(MAKE) all || die 1; \
[ -z "$(TEST_CMD)" ] || $(TEST_CMD) || die 1; \ set -- $(TEST_CMD); \
[ "$$#" -eq 0 ] || "$$@" || die 1; \
index=$$((index + 1)); \ index=$$((index + 1)); \
done; \ done; \
echo;echo "Done! $${count} commit(s) built successfully for RANGE $${RANGE}" ; \ echo;echo "Done! $${count} commit(s) built successfully for RANGE $${RANGE}" ; \

View file

@ -1,2 +1,2 @@
$Format:%ci$ $Format:%ci$
2026/02/19 2026/03/20

View file

@ -1 +1 @@
3.4-dev5 3.4-dev7

View file

@ -407,6 +407,7 @@ listed below. Metrics from extra counters are not listed.
+----------------------------------------------------+ +----------------------------------------------------+
| haproxy_sticktable_size | | haproxy_sticktable_size |
| haproxy_sticktable_used | | haproxy_sticktable_used |
| haproxy_sticktable_local_updates |
+----------------------------------------------------+ +----------------------------------------------------+
* Resolvers metrics * Resolvers metrics

View file

@ -36,6 +36,7 @@
#include <haproxy/stats.h> #include <haproxy/stats.h>
#include <haproxy/stconn.h> #include <haproxy/stconn.h>
#include <haproxy/stream.h> #include <haproxy/stream.h>
#include <haproxy/stress.h>
#include <haproxy/task.h> #include <haproxy/task.h>
#include <haproxy/tools.h> #include <haproxy/tools.h>
#include <haproxy/version.h> #include <haproxy/version.h>
@ -82,6 +83,7 @@ struct promex_ctx {
unsigned field_num; /* current field number (ST_I_PX_* etc) */ unsigned field_num; /* current field number (ST_I_PX_* etc) */
unsigned mod_field_num; /* first field number of the current module (ST_I_PX_* etc) */ unsigned mod_field_num; /* first field number of the current module (ST_I_PX_* etc) */
int obj_state; /* current state among PROMEX_{FRONT|BACK|SRV|LI}_STATE_* */ int obj_state; /* current state among PROMEX_{FRONT|BACK|SRV|LI}_STATE_* */
struct watcher px_watch; /* watcher to automatically update next pointer */
struct watcher srv_watch; /* watcher to automatically update next pointer */ struct watcher srv_watch; /* watcher to automatically update next pointer */
struct list modules; /* list of promex modules to export */ struct list modules; /* list of promex modules to export */
struct eb_root filters; /* list of filters to apply on metrics name */ struct eb_root filters; /* list of filters to apply on metrics name */
@ -347,6 +349,10 @@ static int promex_dump_ts(struct appctx *appctx, struct ist prefix,
istcat(&n, prefix, PROMEX_MAX_NAME_LEN); istcat(&n, prefix, PROMEX_MAX_NAME_LEN);
istcat(&n, name, PROMEX_MAX_NAME_LEN); istcat(&n, name, PROMEX_MAX_NAME_LEN);
/* In stress mode, force yielding on each metric. */
if (STRESS_RUN1(istlen(*out), 0))
goto full;
if ((ctx->flags & PROMEX_FL_METRIC_HDR) && if ((ctx->flags & PROMEX_FL_METRIC_HDR) &&
!promex_dump_ts_header(n, desc, type, out, max)) !promex_dump_ts_header(n, desc, type, out, max))
goto full; goto full;
@ -626,8 +632,6 @@ static int promex_dump_front_metrics(struct appctx *appctx, struct htx *htx)
} }
list_for_each_entry_from(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) { list_for_each_entry_from(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) {
void *counters;
if (!(stats_px_get_cap(mod->domain_flags) & STATS_PX_CAP_FE)) if (!(stats_px_get_cap(mod->domain_flags) & STATS_PX_CAP_FE))
continue; continue;
@ -664,8 +668,7 @@ static int promex_dump_front_metrics(struct appctx *appctx, struct htx *htx)
if ((px->flags & PR_FL_DISABLED) || px->uuid <= 0 || !(px->cap & PR_CAP_FE)) if ((px->flags & PR_FL_DISABLED) || px->uuid <= 0 || !(px->cap & PR_CAP_FE))
goto next_px2; goto next_px2;
counters = EXTRA_COUNTERS_GET(px->extra_counters_fe, mod); if (!mod->fill_stats(mod, px->extra_counters_fe, stats + ctx->field_num, &ctx->mod_field_num))
if (!mod->fill_stats(counters, stats + ctx->field_num, &ctx->mod_field_num))
return -1; return -1;
val = stats[ctx->field_num + ctx->mod_field_num]; val = stats[ctx->field_num + ctx->mod_field_num];
@ -817,8 +820,6 @@ static int promex_dump_listener_metrics(struct appctx *appctx, struct htx *htx)
} }
list_for_each_entry_from(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) { list_for_each_entry_from(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) {
void *counters;
if (!(stats_px_get_cap(mod->domain_flags) & STATS_PX_CAP_LI)) if (!(stats_px_get_cap(mod->domain_flags) & STATS_PX_CAP_LI))
continue; continue;
@ -864,8 +865,7 @@ static int promex_dump_listener_metrics(struct appctx *appctx, struct htx *htx)
labels[lb_idx+1].name = ist("mod"); labels[lb_idx+1].name = ist("mod");
labels[lb_idx+1].value = ist2(mod->name, strlen(mod->name)); labels[lb_idx+1].value = ist2(mod->name, strlen(mod->name));
counters = EXTRA_COUNTERS_GET(li->extra_counters, mod); if (!mod->fill_stats(mod, li->extra_counters, stats + ctx->field_num, &ctx->mod_field_num))
if (!mod->fill_stats(counters, stats + ctx->field_num, &ctx->mod_field_num))
return -1; return -1;
val = stats[ctx->field_num + ctx->mod_field_num]; val = stats[ctx->field_num + ctx->mod_field_num];
@ -941,9 +941,6 @@ static int promex_dump_back_metrics(struct appctx *appctx, struct htx *htx)
if (promex_filter_metric(appctx, prefix, name)) if (promex_filter_metric(appctx, prefix, name))
continue; continue;
if (!px)
px = proxies_list;
while (px) { while (px) {
struct promex_label labels[PROMEX_MAX_LABELS-1] = {}; struct promex_label labels[PROMEX_MAX_LABELS-1] = {};
unsigned int srv_state_count[PROMEX_SRV_STATE_COUNT] = { 0 }; unsigned int srv_state_count[PROMEX_SRV_STATE_COUNT] = { 0 };
@ -1098,9 +1095,16 @@ static int promex_dump_back_metrics(struct appctx *appctx, struct htx *htx)
&val, labels, &out, max)) &val, labels, &out, max))
goto full; goto full;
next_px: next_px:
px = px->next; px = watcher_next(&ctx->px_watch, px->next);
} }
watcher_detach(&ctx->px_watch);
ctx->flags |= PROMEX_FL_METRIC_HDR; ctx->flags |= PROMEX_FL_METRIC_HDR;
/* Prepare a new iteration for the next stat column.
* Update ctx.p[0] via watcher.
*/
watcher_attach(&ctx->px_watch, proxies_list);
px = proxies_list;
} }
/* Skip extra counters */ /* Skip extra counters */
@ -1113,8 +1117,6 @@ static int promex_dump_back_metrics(struct appctx *appctx, struct htx *htx)
} }
list_for_each_entry_from(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) { list_for_each_entry_from(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) {
void *counters;
if (!(stats_px_get_cap(mod->domain_flags) & STATS_PX_CAP_BE)) if (!(stats_px_get_cap(mod->domain_flags) & STATS_PX_CAP_BE))
continue; continue;
@ -1125,9 +1127,6 @@ static int promex_dump_back_metrics(struct appctx *appctx, struct htx *htx)
if (promex_filter_metric(appctx, prefix, name)) if (promex_filter_metric(appctx, prefix, name))
continue; continue;
if (!px)
px = proxies_list;
while (px) { while (px) {
struct promex_label labels[PROMEX_MAX_LABELS-1] = {}; struct promex_label labels[PROMEX_MAX_LABELS-1] = {};
struct promex_metric metric; struct promex_metric metric;
@ -1151,8 +1150,7 @@ static int promex_dump_back_metrics(struct appctx *appctx, struct htx *htx)
if ((px->flags & PR_FL_DISABLED) || px->uuid <= 0 || !(px->cap & PR_CAP_BE)) if ((px->flags & PR_FL_DISABLED) || px->uuid <= 0 || !(px->cap & PR_CAP_BE))
goto next_px2; goto next_px2;
counters = EXTRA_COUNTERS_GET(px->extra_counters_be, mod); if (!mod->fill_stats(mod, px->extra_counters_be, stats + ctx->field_num, &ctx->mod_field_num))
if (!mod->fill_stats(counters, stats + ctx->field_num, &ctx->mod_field_num))
return -1; return -1;
val = stats[ctx->field_num + ctx->mod_field_num]; val = stats[ctx->field_num + ctx->mod_field_num];
@ -1163,25 +1161,39 @@ static int promex_dump_back_metrics(struct appctx *appctx, struct htx *htx)
goto full; goto full;
next_px2: next_px2:
px = px->next; px = watcher_next(&ctx->px_watch, px->next);
} }
watcher_detach(&ctx->px_watch);
ctx->flags |= PROMEX_FL_METRIC_HDR; ctx->flags |= PROMEX_FL_METRIC_HDR;
/* Prepare a new iteration for the next stat column.
* Update ctx.p[0] via watcher.
*/
watcher_attach(&ctx->px_watch, proxies_list);
px = proxies_list;
} }
ctx->field_num += mod->stats_count; ctx->field_num += mod->stats_count;
ctx->mod_field_num = 0; ctx->mod_field_num = 0;
} }
px = NULL;
mod = NULL;
end: end:
if (out.len) { if (ret) {
if (!htx_add_data_atonce(htx, out)) watcher_detach(&ctx->px_watch);
return -1; /* Unexpected and unrecoverable error */ mod = NULL;
} }
/* Save pointers (0=current proxy, 1=current stats module) of the current context */
ctx->p[0] = px; if (out.len) {
if (!htx_add_data_atonce(htx, out)) {
watcher_detach(&ctx->px_watch);
return -1; /* Unexpected and unrecoverable error */
}
}
/* Save pointers of the current context for dump resumption :
* 0=current proxy, 1=current stats module
* Note that p[0] is already automatically updated via px_watch.
*/
ctx->p[1] = mod; ctx->p[1] = mod;
return ret; return ret;
full: full:
@ -1223,9 +1235,6 @@ static int promex_dump_srv_metrics(struct appctx *appctx, struct htx *htx)
if (promex_filter_metric(appctx, prefix, name)) if (promex_filter_metric(appctx, prefix, name))
continue; continue;
if (!px)
px = proxies_list;
while (px) { while (px) {
struct promex_label labels[PROMEX_MAX_LABELS-1] = {}; struct promex_label labels[PROMEX_MAX_LABELS-1] = {};
enum promex_mt_type type; enum promex_mt_type type;
@ -1245,11 +1254,6 @@ static int promex_dump_srv_metrics(struct appctx *appctx, struct htx *htx)
if ((px->flags & PR_FL_DISABLED) || px->uuid <= 0 || !(px->cap & PR_CAP_BE)) if ((px->flags & PR_FL_DISABLED) || px->uuid <= 0 || !(px->cap & PR_CAP_BE))
goto next_px; goto next_px;
if (!sv) {
watcher_attach(&ctx->srv_watch, px->srv);
sv = px->srv;
}
while (sv) { while (sv) {
labels[lb_idx].name = ist("server"); labels[lb_idx].name = ist("server");
labels[lb_idx].value = ist2(sv->id, strlen(sv->id)); labels[lb_idx].value = ist2(sv->id, strlen(sv->id));
@ -1405,9 +1409,25 @@ static int promex_dump_srv_metrics(struct appctx *appctx, struct htx *htx)
next_px: next_px:
watcher_detach(&ctx->srv_watch); watcher_detach(&ctx->srv_watch);
px = px->next; px = watcher_next(&ctx->px_watch, px->next);
if (px) {
/* Update ctx.p[1] via watcher. */
watcher_attach(&ctx->srv_watch, px->srv);
sv = ctx->p[1];
}
} }
watcher_detach(&ctx->px_watch);
ctx->flags |= PROMEX_FL_METRIC_HDR; ctx->flags |= PROMEX_FL_METRIC_HDR;
/* Prepare a new iteration for the next stat column.
* Update ctx.p[0]/p[1] via px_watch/srv_watch.
*/
watcher_attach(&ctx->px_watch, proxies_list);
px = proxies_list;
if (likely(px)) {
watcher_attach(&ctx->srv_watch, px->srv);
sv = ctx->p[1];
}
} }
/* Skip extra counters */ /* Skip extra counters */
@ -1420,8 +1440,6 @@ static int promex_dump_srv_metrics(struct appctx *appctx, struct htx *htx)
} }
list_for_each_entry_from(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) { list_for_each_entry_from(mod, &stats_module_list[STATS_DOMAIN_PROXY], list) {
void *counters;
if (!(stats_px_get_cap(mod->domain_flags) & STATS_PX_CAP_SRV)) if (!(stats_px_get_cap(mod->domain_flags) & STATS_PX_CAP_SRV))
continue; continue;
@ -1432,9 +1450,6 @@ static int promex_dump_srv_metrics(struct appctx *appctx, struct htx *htx)
if (promex_filter_metric(appctx, prefix, name)) if (promex_filter_metric(appctx, prefix, name))
continue; continue;
if (!px)
px = proxies_list;
while (px) { while (px) {
struct promex_label labels[PROMEX_MAX_LABELS-1] = {}; struct promex_label labels[PROMEX_MAX_LABELS-1] = {};
struct promex_metric metric; struct promex_metric metric;
@ -1455,11 +1470,6 @@ static int promex_dump_srv_metrics(struct appctx *appctx, struct htx *htx)
if ((px->flags & PR_FL_DISABLED) || px->uuid <= 0 || !(px->cap & PR_CAP_BE)) if ((px->flags & PR_FL_DISABLED) || px->uuid <= 0 || !(px->cap & PR_CAP_BE))
goto next_px2; goto next_px2;
if (!sv) {
watcher_attach(&ctx->srv_watch, px->srv);
sv = px->srv;
}
while (sv) { while (sv) {
labels[lb_idx].name = ist("server"); labels[lb_idx].name = ist("server");
labels[lb_idx].value = ist2(sv->id, strlen(sv->id)); labels[lb_idx].value = ist2(sv->id, strlen(sv->id));
@ -1471,8 +1481,7 @@ static int promex_dump_srv_metrics(struct appctx *appctx, struct htx *htx)
goto next_sv2; goto next_sv2;
counters = EXTRA_COUNTERS_GET(sv->extra_counters, mod); if (!mod->fill_stats(mod, sv->extra_counters, stats + ctx->field_num, &ctx->mod_field_num))
if (!mod->fill_stats(counters, stats + ctx->field_num, &ctx->mod_field_num))
goto error; goto error;
val = stats[ctx->field_num + ctx->mod_field_num]; val = stats[ctx->field_num + ctx->mod_field_num];
@ -1488,28 +1497,47 @@ static int promex_dump_srv_metrics(struct appctx *appctx, struct htx *htx)
next_px2: next_px2:
watcher_detach(&ctx->srv_watch); watcher_detach(&ctx->srv_watch);
px = px->next; px = watcher_next(&ctx->px_watch, px->next);
if (px) {
/* Update ctx.p[1] via watcher. */
watcher_attach(&ctx->srv_watch, px->srv);
sv = ctx->p[1];
}
} }
watcher_detach(&ctx->px_watch);
ctx->flags |= PROMEX_FL_METRIC_HDR; ctx->flags |= PROMEX_FL_METRIC_HDR;
/* Prepare a new iteration for the next stat column.
* Update ctx.p[0]/p[1] via px_watch/srv_watch.
*/
watcher_attach(&ctx->px_watch, proxies_list);
px = proxies_list;
if (likely(px)) {
watcher_attach(&ctx->srv_watch, px->srv);
sv = ctx->p[1];
}
} }
ctx->field_num += mod->stats_count; ctx->field_num += mod->stats_count;
ctx->mod_field_num = 0; ctx->mod_field_num = 0;
} }
px = NULL;
sv = NULL;
mod = NULL;
end: end:
if (ret) {
watcher_detach(&ctx->px_watch);
watcher_detach(&ctx->srv_watch);
mod = NULL;
}
if (out.len) { if (out.len) {
if (!htx_add_data_atonce(htx, out)) if (!htx_add_data_atonce(htx, out))
return -1; /* Unexpected and unrecoverable error */ return -1; /* Unexpected and unrecoverable error */
} }
/* Save pointers (0=current proxy, 1=current server, 2=current stats module) of the current context */ /* Save pointers of the current context for dump resumption :
ctx->p[0] = px; * 0=current proxy, 1=current server, 2=current stats module
ctx->p[1] = sv; * Note that p[0]/p[1] are already automatically updated via px_watch/srv_watch.
*/
ctx->p[2] = mod; ctx->p[2] = mod;
return ret; return ret;
full: full:
@ -1517,6 +1545,7 @@ static int promex_dump_srv_metrics(struct appctx *appctx, struct htx *htx)
goto end; goto end;
error: error:
watcher_detach(&ctx->px_watch);
watcher_detach(&ctx->srv_watch); watcher_detach(&ctx->srv_watch);
return -1; return -1;
} }
@ -1739,6 +1768,11 @@ static int promex_dump_metrics(struct appctx *appctx, struct htx *htx)
ctx->field_num = ST_I_PX_PXNAME; ctx->field_num = ST_I_PX_PXNAME;
ctx->mod_field_num = 0; ctx->mod_field_num = 0;
appctx->st1 = PROMEX_DUMPER_BACK; appctx->st1 = PROMEX_DUMPER_BACK;
if (ctx->flags & PROMEX_FL_SCOPE_BACK) {
/* Update ctx.p[0] via watcher. */
watcher_attach(&ctx->px_watch, proxies_list);
}
__fallthrough; __fallthrough;
case PROMEX_DUMPER_BACK: case PROMEX_DUMPER_BACK:
@ -1756,6 +1790,15 @@ static int promex_dump_metrics(struct appctx *appctx, struct htx *htx)
ctx->field_num = ST_I_PX_PXNAME; ctx->field_num = ST_I_PX_PXNAME;
ctx->mod_field_num = 0; ctx->mod_field_num = 0;
appctx->st1 = PROMEX_DUMPER_SRV; appctx->st1 = PROMEX_DUMPER_SRV;
if (ctx->flags & PROMEX_FL_SCOPE_SERVER) {
/* Update ctx.p[0] via watcher. */
watcher_attach(&ctx->px_watch, proxies_list);
if (likely(proxies_list)) {
/* Update ctx.p[1] via watcher. */
watcher_attach(&ctx->srv_watch, proxies_list->srv);
}
}
__fallthrough; __fallthrough;
case PROMEX_DUMPER_SRV: case PROMEX_DUMPER_SRV:
@ -2033,6 +2076,7 @@ static int promex_appctx_init(struct appctx *appctx)
LIST_INIT(&ctx->modules); LIST_INIT(&ctx->modules);
ctx->filters = EB_ROOT; ctx->filters = EB_ROOT;
appctx->st0 = PROMEX_ST_INIT; appctx->st0 = PROMEX_ST_INIT;
watcher_init(&ctx->px_watch, &ctx->p[0], offsetof(struct proxy, watcher_list));
watcher_init(&ctx->srv_watch, &ctx->p[1], offsetof(struct server, watcher_list)); watcher_init(&ctx->srv_watch, &ctx->p[1], offsetof(struct server, watcher_list));
return 0; return 0;
} }
@ -2047,6 +2091,11 @@ static void promex_appctx_release(struct appctx *appctx)
struct promex_metric_filter *flt; struct promex_metric_filter *flt;
struct eb32_node *node, *next; struct eb32_node *node, *next;
if (appctx->st1 == PROMEX_DUMPER_BACK ||
appctx->st1 == PROMEX_DUMPER_SRV) {
watcher_detach(&ctx->px_watch);
}
if (appctx->st1 == PROMEX_DUMPER_SRV) if (appctx->st1 == PROMEX_DUMPER_SRV)
watcher_detach(&ctx->srv_watch); watcher_detach(&ctx->srv_watch);

View file

@ -1,11 +1,10 @@
#!/bin/bash #!/bin/sh
set -e set -e
export VERBOSE=1 export VERBOSE=1
export TIMEOUT=90 export TIMEOUT=90
export MASTER_SOCKET=${MASTER_SOCKET:-/var/run/haproxy-master.sock} export MASTER_SOCKET="${MASTER_SOCKET:-/var/run/haproxy-master.sock}"
export RET=
alert() { alert() {
if [ "$VERBOSE" -ge "1" ]; then if [ "$VERBOSE" -ge "1" ]; then
@ -15,32 +14,38 @@ alert() {
reload() { reload() {
while read -r line; do if [ -S "$MASTER_SOCKET" ]; then
socat_addr="UNIX-CONNECT:${MASTER_SOCKET}"
if [ "$line" = "Success=0" ]; then else
RET=1 case "$MASTER_SOCKET" in
elif [ "$line" = "Success=1" ]; then *:[0-9]*)
RET=0 socat_addr="TCP:${MASTER_SOCKET}"
elif [ "$line" = "Another reload is still in progress." ]; then ;;
alert "$line" *)
elif [ "$line" = "--" ]; then alert "Invalid master socket address '${MASTER_SOCKET}': expected a UNIX socket file or <host>:<port>"
continue; return 1
else ;;
if [ "$RET" = 1 ] && [ "$VERBOSE" = "2" ]; then esac
echo "$line" >&2
elif [ "$VERBOSE" = "3" ]; then
echo "$line" >&2
fi
fi
done < <(echo "reload" | socat -t"${TIMEOUT}" "${MASTER_SOCKET}" -)
if [ -z "$RET" ]; then
alert "Couldn't finish the reload before the timeout (${TIMEOUT})."
return 1
fi fi
return "$RET" echo "reload" | socat -t"${TIMEOUT}" "$socat_addr" - | {
read -r status || { alert "No status received (connection error or timeout after ${TIMEOUT}s)."; exit 1; }
case "$status" in
"Success=1") ret=0 ;;
"Success=0") ret=1 ;;
*) alert "Unexpected response: '$status'"; exit 1 ;;
esac
read -r _ # consume "--"
if [ "$VERBOSE" -ge 3 ] || { [ "$ret" = 1 ] && [ "$VERBOSE" -ge 2 ]; }; then
cat >&2
else
cat >/dev/null
fi
exit "$ret"
}
} }
usage() { usage() {
@ -52,12 +57,12 @@ usage() {
echo " EXPERIMENTAL script!" echo " EXPERIMENTAL script!"
echo "" echo ""
echo "Options:" echo "Options:"
echo " -S, --master-socket <path> Use the master socket at <path> (default: ${MASTER_SOCKET})" echo " -S, --master-socket <addr> Unix socket path or <host>:<port> (default: ${MASTER_SOCKET})"
echo " -d, --debug Debug mode, set -x" echo " -d, --debug Debug mode, set -x"
echo " -t, --timeout Timeout (socat -t) (default: ${TIMEOUT})" echo " -t, --timeout Timeout (socat -t) (default: ${TIMEOUT})"
echo " -s, --silent Silent mode (no output)" echo " -s, --silent Silent mode (no output)"
echo " -v, --verbose Verbose output (output from haproxy on failure)" echo " -v, --verbose Verbose output (output from haproxy on failure)"
echo " -vv Even more verbose output (output from haproxy on success and failure)" echo " -vv --verbose=all Very verbose output (output from haproxy on success and failure)"
echo " -h, --help This help" echo " -h, --help This help"
echo "" echo ""
echo "Examples:" echo "Examples:"
@ -84,7 +89,7 @@ main() {
VERBOSE=2 VERBOSE=2
shift shift
;; ;;
-vv|--verbose) -vv|--verbose=all)
VERBOSE=3 VERBOSE=3
shift shift
;; ;;

162
dev/gdb/libs-from-core.c Normal file
View file

@ -0,0 +1,162 @@
/*
* Extracts the libs archives from a core dump
*
* Copyright (C) 2026 Willy Tarreau <w@1wt.eu>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
/* Note: builds with no option under glibc, and can be built as a minimal
* uploadable static executable using nolibc as well:
gcc -o libs-from-core -nostdinc -nostdlib -s -Os -static -fno-ident \
-fno-exceptions -fno-asynchronous-unwind-tables -fno-unwind-tables \
-Wl,--gc-sections,--orphan-handling=discard,-znoseparate-code \
-I /path/to/nolibc-sysroot/include libs-from-core.c
*/
#define _GNU_SOURCE
#include <sys/mman.h>
#include <sys/stat.h>
#include <elf.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void usage(const char *progname)
{
const char *slash = strrchr(progname, '/');
if (slash)
progname = slash + 1;
fprintf(stderr,
"Usage: %s [-q] <core_file>\n"
"Locate a libs archive from an haproxy core dump and dump it to stdout.\n"
"Arguments:\n"
" -q Query mode: only report offset and length, do not dump\n"
" core_file Core dump produced by haproxy\n",
progname);
}
int main(int argc, char **argv)
{
Elf64_Ehdr *ehdr;
Elf64_Phdr *phdr;
struct stat st;
uint8_t *mem;
int i, fd;
const char *fname;
int quiet = 0;
int arg;
for (arg = 1; arg < argc; arg++) {
if (*argv[arg] != '-')
break;
if (strcmp(argv[arg], "-q") == 0)
quiet = 1;
else if (strcmp(argv[arg], "--") == 0) {
arg++;
break;
}
}
if (arg < argc) {
fname = argv[arg];
} else {
usage(argv[0]);
exit(1);
}
fd = open(fname, O_RDONLY);
/* Let's just map the core dump as an ELF header */
fstat(fd, &st);
mem = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (mem == MAP_FAILED) {
perror("mmap()");
exit(1);
}
/* get the program headers */
ehdr = (Elf64_Ehdr *)mem;
/* check that it's really a core. Should be "\x7fELF" */
if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0) {
fprintf(stderr, "ELF magic not found.\n");
exit(1);
}
if (ehdr->e_ident[EI_CLASS] != ELFCLASS64) {
fprintf(stderr, "Only 64-bit ELF supported.\n");
exit(1);
}
if (ehdr->e_type != ET_CORE) {
fprintf(stderr, "ELF type %d, not a core dump.\n", ehdr->e_type);
exit(1);
}
/* OK we can safely go with program headers */
phdr = (Elf64_Phdr *)(mem + ehdr->e_phoff);
for (i = 0; i < ehdr->e_phnum; i++) {
uint64_t size = phdr[i].p_filesz;
uint64_t offset = phdr[i].p_offset;
int ret = 0;
if (phdr[i].p_type != PT_LOAD)
continue;
//fprintf(stderr, "Scanning segment %d...\n", ehdr->e_phnum);
//fprintf(stderr, "\r%-5d: off=%lx va=%lx sz=%lx ", i, (long)offset, (long)phdr[i].p_vaddr, (long)size);
if (!size)
continue;
if (size < 512) // minimum for a tar header
continue;
/* tar magic */
if (memcmp(mem + offset + 257, "ustar\0""00", 8) != 0)
continue;
/* uid, gid */
if (memcmp(mem + offset + 108, "0000000\0""0000000\0", 16) != 0)
continue;
/* link name */
if (memcmp(mem + offset + 157, "haproxy-libs-dump\0", 18) != 0)
continue;
/* OK that's really it */
if (quiet)
printf("offset=%#lx size=%#lx\n", offset, size);
else
ret = (write(1, mem + offset, size) == size) ? 0 : 1;
return ret;
}
//fprintf(stderr, "\r%75s\n", "\r");
fprintf(stderr, "libs archive not found. Was 'set-dumpable' set to 'libs' ?\n");
return 1;
}

View file

@ -3,7 +3,7 @@
Configuration Manual Configuration Manual
---------------------- ----------------------
version 3.4 version 3.4
2026/02/19 2026/03/20
This document covers the configuration language as implemented in the version This document covers the configuration language as implemented in the version
@ -1725,7 +1725,7 @@ Assuming haproxy is in $PATH, test these configurations in a shell with:
$ sudo haproxy -f configuration.conf -c $ sudo haproxy -f configuration.conf -c
3. Global parameters 3. Global section
-------------------- --------------------
Parameters in the "global" section are process-wide and often OS-specific. They Parameters in the "global" section are process-wide and often OS-specific. They
@ -1886,10 +1886,13 @@ The following keywords are supported in the "global" section :
- tune.h2.be.glitches-threshold - tune.h2.be.glitches-threshold
- tune.h2.be.initial-window-size - tune.h2.be.initial-window-size
- tune.h2.be.max-concurrent-streams - tune.h2.be.max-concurrent-streams
- tune.h2.be.max-frames-at-once
- tune.h2.be.rxbuf - tune.h2.be.rxbuf
- tune.h2.fe.glitches-threshold - tune.h2.fe.glitches-threshold
- tune.h2.fe.initial-window-size - tune.h2.fe.initial-window-size
- tune.h2.fe.max-concurrent-streams - tune.h2.fe.max-concurrent-streams
- tune.h2.fe.max-frames-at-once
- tune.h2.fe.max-rst-at-once
- tune.h2.fe.max-total-streams - tune.h2.fe.max-total-streams
- tune.h2.fe.rxbuf - tune.h2.fe.rxbuf
- tune.h2.header-table-size - tune.h2.header-table-size
@ -3148,10 +3151,29 @@ server-state-file <file>
configuration. See also "server-state-base" and "show servers state", configuration. See also "server-state-base" and "show servers state",
"load-server-state-from-file" and "server-state-file-name" "load-server-state-from-file" and "server-state-file-name"
set-dumpable set-dumpable [ on | off | libs ]
This option helps choose the core dump behavior in case of process crash.
Available options are:
- on this enables core dumping at the process level if it was
previously disabled.
- off this disables a previously enabled core dumping.
- libs this enables core dumping with an embedded copy of the binaries and
libraries that are required for debugging. This may be requested by
developers. In this case haproxy will try to load the libraries it
depends on into memory and keep them preciously. If the process
crashes, they will be dumped into the core so there is no need for
retrieving them from the file system anymore and no risk that they
do not match the core. This takes a few megabytes to a few tens of
megabytes of additional RAM, so it is better not to use it on small
systems.
This option is better left disabled by default and enabled only upon a This option is better left disabled by default and enabled only upon a
developer's request. If it has been enabled, it may still be forcibly developer's request. By default it is disabled. Without argument, it defaults
disabled by prefixing it with the "no" keyword. It has no impact on to "on". If it has been enabled, it may still be forcibly disabled by prefixing
it with the "no" keyword or by setting it to "off". It has no impact on
performance nor stability but will try hard to re-enable core dumps that were performance nor stability but will try hard to re-enable core dumps that were
possibly disabled by file size limitations (ulimit -f), core size limitations possibly disabled by file size limitations (ulimit -f), core size limitations
(ulimit -c), or "dumpability" of a process after changing its UID/GID (such (ulimit -c), or "dumpability" of a process after changing its UID/GID (such
@ -3604,6 +3626,11 @@ ssl-skip-self-issued-ca
certificates. It's useless for BoringSSL, .issuer is ignored because ocsp certificates. It's useless for BoringSSL, .issuer is ignored because ocsp
bits does not need it. Requires at least OpenSSL 1.0.2. bits does not need it. Requires at least OpenSSL 1.0.2.
stats calculate-max-counters [on|off]
Activates or deactivates the calculation of stats max counters. If you
don't need them, deactivating them may increase performances a bit.
The default is on.
stats maxconn <connections> stats maxconn <connections>
By default, the stats socket is limited to 10 concurrent connections. It is By default, the stats socket is limited to 10 concurrent connections. It is
possible to change this value with "stats maxconn". possible to change this value with "stats maxconn".
@ -4138,8 +4165,11 @@ tune.bufsize.small <size>
If however a small buffer is not sufficient, a reallocation is automatically If however a small buffer is not sufficient, a reallocation is automatically
done to switch to a standard size buffer. done to switch to a standard size buffer.
For the moment, it is used only by HTTP/3 protocol to emit the response For the moment, it is automatically used only by HTTP/3 protocol to emit the
headers. response headers. Otherwise, small buffers support can be enabled for
specific proxies via the "use-small-buffers" option.
See also: option use-small-buffers
tune.comp.maxlevel <number> tune.comp.maxlevel <number>
Sets the maximum compression level. The compression level affects CPU Sets the maximum compression level. The compression level affects CPU
@ -4344,6 +4374,13 @@ tune.h2.be.max-concurrent-streams <number>
case). It is highly recommended not to increase this value; some might find case). It is highly recommended not to increase this value; some might find
it optimal to run at low values (1..5 typically). it optimal to run at low values (1..5 typically).
tune.h2.be.max-frames-at-once <number>
Sets the maximum number of HTTP/2 incoming frames that will be processed at
once on a backend connection. It can be useful to set this to a low value
(a few tens to a few hundreds) when dealing with very large buffers in order
to maintain a low latency and a better fairness between multiple connections.
The default value is zero, which means that no limitation is enforced.
tune.h2.be.rxbuf <size> tune.h2.be.rxbuf <size>
Sets the HTTP/2 receive buffer size for outgoing connections, in bytes. This Sets the HTTP/2 receive buffer size for outgoing connections, in bytes. This
size will be rounded up to the next multiple of tune.bufsize and will be size will be rounded up to the next multiple of tune.bufsize and will be
@ -4394,7 +4431,7 @@ tune.h2.fe.initial-window-size <number>
See also: tune.h2.initial-window-size. See also: tune.h2.initial-window-size.
tune.h2.fe.max-concurrent-streams <number> tune.h2.fe.max-concurrent-streams <number> [args...]
Sets the HTTP/2 maximum number of concurrent streams per incoming connection Sets the HTTP/2 maximum number of concurrent streams per incoming connection
(i.e. the number of outstanding requests on a single connection from a (i.e. the number of outstanding requests on a single connection from a
client). When not set, the default set by tune.h2.max-concurrent-streams client). When not set, the default set by tune.h2.max-concurrent-streams
@ -4402,7 +4439,56 @@ tune.h2.fe.max-concurrent-streams <number>
the page load time for complex sites with lots of small objects over high the page load time for complex sites with lots of small objects over high
latency networks but can also result in using more memory by allowing a latency networks but can also result in using more memory by allowing a
client to allocate more resources at once. The default value of 100 is client to allocate more resources at once. The default value of 100 is
generally good and it is recommended not to change this value. generally good and it is recommended not to change this value. A larger
concurrency also has an impact on the processing load and latency when
dealing with large numbers of connections which are themselves using many
streams, and it may lower the barrier to denial of service attacks. The
command supports the following optional arguments after the number:
- rq-load { <number> | auto | ignore }:
The optional argument "rq-load" permits to dynamically adjust the
advertised concurrency based on the executing thread's run-queue load:
as long as the thread's load remains below the indicated threshold, the
configured streams limit will be advertised. When the thread's load
increases beyond the configured limit, the advertised streams limit will be
decreased proportionally to the square of the excess ratio. Target load
levels between 50 and 100 generally show very good moderation under heavy
loads. Alternately, instead of specifying an explicit number, the keyword
accepts "ignore", which is the default and means that the thread's
run-queue load will not be considered to moderate the advertised streams
limit, and "auto", which sets the limit to the "tune.runqueue-depth"
value, which generally provides good results without having to tweak
the configuration any further.
- min <number>:
This sets the minimum advertised concurrency level when rq-load is used,
even if this results in a higher load than the configured target. This
allows to maintain a good level of interactivity on a site under very
heavy load. The minimum and default value is 1, but values between 5
and 15 can improve user experience.
Example:
tune.h2.fe.max-concurrent-streams 100 rq-load auto min 15
tune.h2.fe.max-frames-at-once <number>
Sets the maximum number of HTTP/2 incoming frames that will be processed at
once on a frontend connection. It can be useful to set this to a low value
(a few tens to a few hundreds) when dealing with very large buffers in order
to maintain a low latency and a better fairness between multiple connections.
The default value is zero, which means that no limitation is enforced.
tune.h2.fe.max-rst-at-once <number>
Sets the maximum number of HTTP/2 incoming RST_STREAM that will be processed
at once on a frontend connection. Once the specified number of RST_STREAM
frames are received, the connection handler will be placed in a low priority
queue and be processed after all other tasks. It can be useful to set this to
a very low value (1 or a few units) to significantly reduce the impacts of
RST_STREAM floods. RST_STREAM do happen when a user clicks on the Stop button
in their browser, but the few extra milliseconds caused by this requeuing are
generally unnoticeable, however they are generally effective at significantly
lowering the load caused from such floods. The default value is zero, which
means that no limitation is enforced.
tune.h2.fe.max-total-streams <number> tune.h2.fe.max-total-streams <number>
Sets the HTTP/2 maximum number of total streams processed per incoming Sets the HTTP/2 maximum number of total streams processed per incoming
@ -4478,6 +4564,16 @@ tune.h2.initial-window-size <number>
specific settings tune.h2.fe.initial-window-size and specific settings tune.h2.fe.initial-window-size and
tune.h2.be.initial-window-size. tune.h2.be.initial-window-size.
tune.h2.log-errors { none | connection | stream }
Sets the level of errors in the H2 demultiplexer that will generate a log.
The default is "stream", which means that any decoding error encountered in
the demultiplexer will lead to the emission of a log. The "connection" value
indicates that only logs that result in invalidating the connection will
produce a log. Finally, "none" indicates that no decoding error will produce
any log. It is recommended to set at least "connection" in order to detect
protocol anomalies, even if this means temporarily switching to "none" during
difficult periods.
tune.h2.max-concurrent-streams <number> tune.h2.max-concurrent-streams <number>
Sets the default HTTP/2 maximum number of concurrent streams per connection Sets the default HTTP/2 maximum number of concurrent streams per connection
(i.e. the number of outstanding requests on a single connection). This value (i.e. the number of outstanding requests on a single connection). This value
@ -4540,22 +4636,22 @@ tune.http.maxhdr <number>
protocols. This limit is large enough but not documented on purpose. The same protocols. This limit is large enough but not documented on purpose. The same
limit is applied on the first steps of the decoding for the same reason. limit is applied on the first steps of the decoding for the same reason.
tune.idle-pool.shared { on | off } tune.idle-pool.shared { auto | on | off }
Enables ('on') or disables ('off') sharing of idle connection pools between Controls sharing idle connection pools between threads for a same server.
threads for a same server. The default is to share them between threads in It can be enabled for all threads in a same thread group ('on'), enabled for
order to minimize the number of persistent connections to a server, and to all threads ('full') or disabled ('off'). The default is to share them
optimize the connection reuse rate. But to help with debugging or when between threads in the same thread group ('on'), in order to minimize the
number of persistent connections to a server, and to optimize the connection
reuse rate. Sharing with threads from other thread groups can have a
performance impact, and is not enabled by default, but can be useful if
maximizing connection reuse is a priority. To help with debugging or when
suspecting a bug in HAProxy around connection reuse, it can be convenient to suspecting a bug in HAProxy around connection reuse, it can be convenient to
forcefully disable this idle pool sharing between multiple threads, and force forcefully disable this idle pool sharing between multiple threads,
this option to "off". The default is on. It is strongly recommended against and force this option to "off". It is strongly recommended against disabling
disabling this option without setting a conservative value on "pool-low-conn" this option without setting a conservative value on "pool-low-conn" for all
for all servers relying on connection reuse to achieve a high performance servers relying on connection reuse to achieve a high performance level,
level, otherwise connections might be closed very often as the thread count otherwise connections might be closed very often as the thread count
increases. Note that in any case, connections are only shared between threads increases.
of the same thread group. This means that systems with many NUMA nodes may
show slightly more persistent connections while machines with unified caches
and many CPU cores per node may experience higher CPU usage. In the latter
case, the "max-thread-per-group" tunable may be used to improve the behavior.
tune.idletimer <timeout> tune.idletimer <timeout>
Sets the duration after which HAProxy will consider that an empty buffer is Sets the duration after which HAProxy will consider that an empty buffer is
@ -5491,6 +5587,9 @@ tune.takeover-other-tg-connections <value>
connections. connections.
Note that using connections from other thread groups can occur performance Note that using connections from other thread groups can occur performance
penalties, so it should not be used unless really needed. penalties, so it should not be used unless really needed.
Note that this behavior is now controlled by tune.idle-pool.shared, and
this keyword is just there for compatibility with older configurations, and
will be deprecated.
tune.vars.global-max-size <size> tune.vars.global-max-size <size>
tune.vars.proc-max-size <size> tune.vars.proc-max-size <size>
@ -5878,6 +5977,8 @@ errorloc302 X X X X
-- keyword -------------------------- defaults - frontend - listen -- backend - -- keyword -------------------------- defaults - frontend - listen -- backend -
errorloc303 X X X X errorloc303 X X X X
error-log-format X X X - error-log-format X X X -
external-check command X - X X
external-check path X - X X
force-persist - - X X force-persist - - X X
force-be-switch - X X - force-be-switch - X X -
filter - X X X filter - X X X
@ -5923,6 +6024,7 @@ option disable-h2-upgrade (*) X X X -
option dontlog-normal (*) X X X - option dontlog-normal (*) X X X -
option dontlognull (*) X X X - option dontlognull (*) X X X -
-- keyword -------------------------- defaults - frontend - listen -- backend - -- keyword -------------------------- defaults - frontend - listen -- backend -
option external-check X - X X
option forwardfor X X X X option forwardfor X X X X
option forwarded (*) X - X X option forwarded (*) X - X X
option h1-case-adjust-bogus-client (*) X X X - option h1-case-adjust-bogus-client (*) X X X -
@ -5941,9 +6043,9 @@ option httpchk X - X X
option httpclose (*) X X X X option httpclose (*) X X X X
option httplog X X X - option httplog X X X -
option httpslog X X X - option httpslog X X X -
option idle-close-on-response (*) X X X -
option independent-streams (*) X X X X option independent-streams (*) X X X X
option ldap-check X - X X option ldap-check X - X X
option external-check X - X X
option log-health-checks (*) X - X X option log-health-checks (*) X - X X
option log-separate-errors (*) X X X - option log-separate-errors (*) X X X -
option logasap (*) X X X - option logasap (*) X X X -
@ -5970,9 +6072,7 @@ option tcp-smart-connect (*) X - X X
option tcpka X X X X option tcpka X X X X
option tcplog X X X - option tcplog X X X -
option transparent (deprecated) (*) X - X X option transparent (deprecated) (*) X - X X
option idle-close-on-response (*) X X X - option use-small-buffers (*) X - X X
external-check command X - X X
external-check path X - X X
persist rdp-cookie X - X X persist rdp-cookie X - X X
quic-initial X (!) X X - quic-initial X (!) X X -
rate-limit sessions X X X - rate-limit sessions X X X -
@ -6920,12 +7020,16 @@ compression offload
See also : "compression type", "compression algo", "compression direction" See also : "compression type", "compression algo", "compression direction"
compression direction <direction> compression direction <direction> (deprecated)
Makes haproxy able to compress both requests and responses. Makes haproxy able to compress both requests and responses.
Valid values are "request", to compress only requests, "response", to Valid values are "request", to compress only requests, "response", to
compress only responses, or "both", when you want to compress both. compress only responses, or "both", when you want to compress both.
The default value is "response". The default value is "response".
This directive is only relevant when legacy "filter compression" was
enabled, as with explicit comp-req and comp-res filters compression
direction is redundant.
May be used in the following contexts: http May be used in the following contexts: http
See also : "compression type", "compression algo", "compression offload" See also : "compression type", "compression algo", "compression offload"
@ -7631,6 +7735,96 @@ force-persist { if | unless } <condition>
and section 7 about ACL usage. and section 7 about ACL usage.
external-check command <command>
Executable to run when performing an external-check
May be used in the following contexts: tcp, http, log
May be used in sections : defaults | frontend | listen | backend
yes | no | yes | yes
Arguments :
<command> is the external command to run
The arguments passed to the to the command are:
<proxy_address> <proxy_port> <server_address> <server_port>
The <proxy_address> and <proxy_port> are derived from the first listener
that is either IPv4, IPv6 or a UNIX socket. In the case of a UNIX socket
listener the proxy_address will be the path of the socket and the
<proxy_port> will be the string "NOT_USED". In a backend section, it's not
possible to determine a listener, and both <proxy_address> and <proxy_port>
will have the string value "NOT_USED".
Some values are also provided through environment variables.
Environment variables :
HAPROXY_PROXY_ADDR The first bind address if available (or empty if not
applicable, for example in a "backend" section).
HAPROXY_PROXY_ID The backend id.
HAPROXY_PROXY_NAME The backend name.
HAPROXY_PROXY_PORT The first bind port if available (or empty if not
applicable, for example in a "backend" section or
for a UNIX socket).
HAPROXY_SERVER_ADDR The server address.
HAPROXY_SERVER_CURCONN The current number of connections on the server.
HAPROXY_SERVER_ID The server id.
HAPROXY_SERVER_MAXCONN The server max connections.
HAPROXY_SERVER_NAME The server name.
HAPROXY_SERVER_PORT The server port if available (or empty for a UNIX
socket).
HAPROXY_SERVER_SSL "0" when SSL is not used, "1" when it is used
HAPROXY_SERVER_PROTO The protocol used by this server, which can be one
of "cli" (the haproxy CLI), "syslog" (syslog TCP
server), "peers" (peers TCP server), "h1" (HTTP/1.x
server), "h2" (HTTP/2 server), or "tcp" (any other
TCP server).
PATH The PATH environment variable used when executing
the command may be set using "external-check path".
If the command executed and exits with a zero status then the check is
considered to have passed, otherwise the check is considered to have
failed.
Example :
external-check command /bin/true
See also : "external-check", "option external-check", "external-check path"
external-check path <path>
The value of the PATH environment variable used when running an external-check
May be used in the following contexts: tcp, http, log
May be used in sections : defaults | frontend | listen | backend
yes | no | yes | yes
Arguments :
<path> is the path used when executing external command to run
The default path is "".
Example :
external-check path "/usr/bin:/bin"
See also : "external-check", "option external-check",
"external-check command"
force-be-switch { if | unless } <condition> force-be-switch { if | unless } <condition>
Allow content switching to select a backend instance even if it is disabled Allow content switching to select a backend instance even if it is disabled
or unpublished. This rule can be used by admins to test traffic to services or unpublished. This rule can be used by admins to test traffic to services
@ -8135,6 +8329,11 @@ http-check expect [min-recv <int>] [comment <msg>]
occurred during the expect rule evaluation. <fmt> is a occurred during the expect rule evaluation. <fmt> is a
Custom log format string (see section 8.2.6). Custom log format string (see section 8.2.6).
status-code <expr> is optional and can be used to set the check status code
reported in logs, on success or on error. <expr> is a
standard HAProxy expression formed by a sample-fetch
followed by some converters.
<match> is a keyword indicating how to look for a specific pattern in the <match> is a keyword indicating how to look for a specific pattern in the
response. The keyword may be one of "status", "rstatus", "hdr", response. The keyword may be one of "status", "rstatus", "hdr",
"fhdr", "string", or "rstring". The keyword may be preceded by an "fhdr", "string", or "rstring". The keyword may be preceded by an
@ -8244,7 +8443,7 @@ http-check expect [min-recv <int>] [comment <msg>]
http-check expect status 200,201,300-310 http-check expect status 200,201,300-310
# be sure a sessid coookie is set # be sure a sessid coookie is set
http-check expect header name "set-cookie" value -m beg "sessid=" http-check expect hdr name "set-cookie" value -m beg "sessid="
# consider SQL errors as errors # consider SQL errors as errors
http-check expect ! string SQL\ Error http-check expect ! string SQL\ Error
@ -9852,6 +10051,24 @@ no option dontlognull
See also : "log", "http-ignore-probes", "monitor-uri", and See also : "log", "http-ignore-probes", "monitor-uri", and
section 8 about logging. section 8 about logging.
option external-check
Use external processes for server health checks
May be used in the following contexts: tcp, http, log
May be used in sections : defaults | frontend | listen | backend
yes | no | yes | yes
It is possible to test the health of a server using an external command.
This is achieved by running the executable set using "external-check
command".
Requires the "external-check" global to be set.
See also : "external-check", "external-check command", "external-check path"
option forwarded [ proto ] option forwarded [ proto ]
[ host | host-expr <host_expr> ] [ host | host-expr <host_expr> ]
[ by | by-expr <by_expr> ] [ by_port | by_port-expr <by_port_expr>] [ by | by-expr <by_expr> ] [ by_port | by_port-expr <by_port_expr>]
@ -10642,6 +10859,39 @@ option httpslog
See also : section 8 about logging. See also : section 8 about logging.
option idle-close-on-response
no option idle-close-on-response
Avoid closing idle frontend connections if a soft stop is in progress
May be used in the following contexts: http
May be used in sections : defaults | frontend | listen | backend
yes | yes | yes | no
Arguments : none
By default, idle connections will be closed during a soft stop. In some
environments, a client talking to the proxy may have prepared some idle
connections in order to send requests later. If there is no proper retry on
write errors, this can result in errors while haproxy is reloading. Even
though a proper implementation should retry on connection/write errors, this
option was introduced to support backwards compatibility with haproxy prior
to version 2.4. Indeed before v2.4, haproxy used to wait for a last request
and response to add a "connection: close" header before closing, thus
notifying the client that the connection would not be reusable.
In a real life example, this behavior was seen in AWS using the ALB in front
of a haproxy. The end result was ALB sending 502 during haproxy reloads.
Users are warned that using this option may increase the number of old
processes if connections remain idle for too long. Adjusting the client
timeouts and/or the "hard-stop-after" parameter accordingly might be
needed in case of frequent reloads.
See also: "timeout client", "timeout client-fin", "timeout http-request",
"hard-stop-after"
option independent-streams option independent-streams
no option independent-streams no option independent-streams
Enable or disable independent timeout processing for both directions Enable or disable independent timeout processing for both directions
@ -10706,56 +10956,6 @@ option ldap-check
See also : "option httpchk" See also : "option httpchk"
option external-check
Use external processes for server health checks
May be used in the following contexts: tcp, http, log
May be used in sections : defaults | frontend | listen | backend
yes | no | yes | yes
It is possible to test the health of a server using an external command.
This is achieved by running the executable set using "external-check
command".
Requires the "external-check" global to be set.
See also : "external-check", "external-check command", "external-check path"
option idle-close-on-response
no option idle-close-on-response
Avoid closing idle frontend connections if a soft stop is in progress
May be used in the following contexts: http
May be used in sections : defaults | frontend | listen | backend
yes | yes | yes | no
Arguments : none
By default, idle connections will be closed during a soft stop. In some
environments, a client talking to the proxy may have prepared some idle
connections in order to send requests later. If there is no proper retry on
write errors, this can result in errors while haproxy is reloading. Even
though a proper implementation should retry on connection/write errors, this
option was introduced to support backwards compatibility with haproxy prior
to version 2.4. Indeed before v2.4, haproxy used to wait for a last request
and response to add a "connection: close" header before closing, thus
notifying the client that the connection would not be reusable.
In a real life example, this behavior was seen in AWS using the ALB in front
of a haproxy. The end result was ALB sending 502 during haproxy reloads.
Users are warned that using this option may increase the number of old
processes if connections remain idle for too long. Adjusting the client
timeouts and/or the "hard-stop-after" parameter accordingly might be
needed in case of frequent reloads.
See also: "timeout client", "timeout client-fin", "timeout http-request",
"hard-stop-after"
option log-health-checks option log-health-checks
no option log-health-checks no option log-health-checks
Enable or disable logging of health checks status updates Enable or disable logging of health checks status updates
@ -11717,95 +11917,35 @@ no option transparent (deprecated)
"transparent" option of the "bind" keyword. "transparent" option of the "bind" keyword.
external-check command <command> option use-small-buffers [ queue | l7-retries | check ]*
Executable to run when performing an external-check
May be used in the following contexts: tcp, http, log Enable support for small buffers for the given categories.
May be used in the following contexts: tcp, http
May be used in sections : defaults | frontend | listen | backend May be used in sections : defaults | frontend | listen | backend
yes | no | yes | yes yes | no | yes | yes
Arguments : This option can be used to enable the small buffers support at diffent places
<command> is the external command to run to save memory. By default, with no parameter, small buffers are used as far
as possible at all possible places. Otherwise, it is possible to limit it to
following the places:
The arguments passed to the to the command are: - queue: When set, small buffers will be used to store the requests, if
small enough, when the connection is queued.
- l7-retries: When set, small buffers will be used to save the requests
when L7 retries are enabled.
- check: When set, small buffers will be used for the health-checks
requests.
<proxy_address> <proxy_port> <server_address> <server_port> When enabled, small buffers are used, but only if it is possible. Otherwise,
when data are too large, a regular buffer is automtically used. The size of
small buffers is configurable via the "tune.bufsize.small" global setting.
The <proxy_address> and <proxy_port> are derived from the first listener If this option has been enabled in a "defaults" section, it can be disabled
that is either IPv4, IPv6 or a UNIX socket. In the case of a UNIX socket in a specific instance by prepending the "no" keyword before it.
listener the proxy_address will be the path of the socket and the
<proxy_port> will be the string "NOT_USED". In a backend section, it's not
possible to determine a listener, and both <proxy_address> and <proxy_port>
will have the string value "NOT_USED".
Some values are also provided through environment variables.
Environment variables :
HAPROXY_PROXY_ADDR The first bind address if available (or empty if not
applicable, for example in a "backend" section).
HAPROXY_PROXY_ID The backend id.
HAPROXY_PROXY_NAME The backend name.
HAPROXY_PROXY_PORT The first bind port if available (or empty if not
applicable, for example in a "backend" section or
for a UNIX socket).
HAPROXY_SERVER_ADDR The server address.
HAPROXY_SERVER_CURCONN The current number of connections on the server.
HAPROXY_SERVER_ID The server id.
HAPROXY_SERVER_MAXCONN The server max connections.
HAPROXY_SERVER_NAME The server name.
HAPROXY_SERVER_PORT The server port if available (or empty for a UNIX
socket).
HAPROXY_SERVER_SSL "0" when SSL is not used, "1" when it is used
HAPROXY_SERVER_PROTO The protocol used by this server, which can be one
of "cli" (the haproxy CLI), "syslog" (syslog TCP
server), "peers" (peers TCP server), "h1" (HTTP/1.x
server), "h2" (HTTP/2 server), or "tcp" (any other
TCP server).
PATH The PATH environment variable used when executing
the command may be set using "external-check path".
If the command executed and exits with a zero status then the check is
considered to have passed, otherwise the check is considered to have
failed.
Example :
external-check command /bin/true
See also : "external-check", "option external-check", "external-check path"
external-check path <path>
The value of the PATH environment variable used when running an external-check
May be used in the following contexts: tcp, http, log
May be used in sections : defaults | frontend | listen | backend
yes | no | yes | yes
Arguments :
<path> is the path used when executing external command to run
The default path is "".
Example :
external-check path "/usr/bin:/bin"
See also : "external-check", "option external-check",
"external-check command"
See also: tune.bufsize.small
persist rdp-cookie persist rdp-cookie
persist rdp-cookie(<name>) persist rdp-cookie(<name>)
@ -13564,13 +13704,6 @@ tcp-check expect [min-recv <int>] [comment <msg>]
does not match, the check will wait for more data. If set to 0, does not match, the check will wait for more data. If set to 0,
the evaluation result is always conclusive. the evaluation result is always conclusive.
<match> is a keyword indicating how to look for a specific pattern in the
response. The keyword may be one of "string", "rstring", "binary" or
"rbinary".
The keyword may be preceded by an exclamation mark ("!") to negate
the match. Spaces are allowed between the exclamation mark and the
keyword. See below for more details on the supported keywords.
ok-status <st> is optional and can be used to set the check status if ok-status <st> is optional and can be used to set the check status if
the expect rule is successfully evaluated and if it is the expect rule is successfully evaluated and if it is
the last rule in the tcp-check ruleset. "L7OK", "L7OKC", the last rule in the tcp-check ruleset. "L7OK", "L7OKC",
@ -13618,6 +13751,13 @@ tcp-check expect [min-recv <int>] [comment <msg>]
standard HAProxy expression formed by a sample-fetch standard HAProxy expression formed by a sample-fetch
followed by some converters. followed by some converters.
<match> is a keyword indicating how to look for a specific pattern in the
response. The keyword may be one of "string", "rstring", "binary" or
"rbinary".
The keyword may be preceded by an exclamation mark ("!") to negate
the match. Spaces are allowed between the exclamation mark and the
keyword. See below for more details on the supported keywords.
<pattern> is the pattern to look for. It may be a string or a regular <pattern> is the pattern to look for. It may be a string or a regular
expression. If the pattern contains spaces, they must be escaped expression. If the pattern contains spaces, they must be escaped
with the usual backslash ('\'). with the usual backslash ('\').
@ -15314,15 +15454,14 @@ disable-l7-retry
reason than a connection failure. This can be useful for example to make reason than a connection failure. This can be useful for example to make
sure POST requests aren't retried on failure. sure POST requests aren't retried on failure.
do-log do-log [profile <log_profile>]
Usable in: QUIC Ini| TCP RqCon| RqSes| RqCnt| RsCnt| HTTP Req| Res| Aft Usable in: QUIC Ini| TCP RqCon| RqSes| RqCnt| RsCnt| HTTP Req| Res| Aft
X | X | X | X | X | X | X | X X | X | X | X | X | X | X | X
This action manually triggers a log emission on the proxy. This means This action manually triggers a log emission on the proxy. This means
log options on the proxy will be considered (including formatting options log options on the proxy will be considered (including formatting options
such as "log-format"), but it will not interfere with the logs automatically such as "log-format"), but it will not interfere with the logs automatically
generated by the proxy during transaction handling. It currently doesn't generated by the proxy during transaction handling.
support any argument, though extensions may appear in future versions.
Using "log-profile", it is possible to precisely describe how the log should Using "log-profile", it is possible to precisely describe how the log should
be emitted for each of the available contexts where the action may be used. be emitted for each of the available contexts where the action may be used.
@ -15332,15 +15471,28 @@ do-log
Also, they will be properly reported when using "%OG" logformat alias. Also, they will be properly reported when using "%OG" logformat alias.
Optional "profile" argument may be used to specify the name of a log-profile
section that should be used for this do-log action specifically instead of
the one associated to the current logger that applies by default.
Example: Example:
log-profile myprof log-profile my-dft-prof
on tcp-req-conn format "Connect: %ci" on tcp-req-conn format "Connect: %ci"
log-profile my-local-prof
on tcp-req-conn format "Local Connect: %ci"
frontend myfront frontend myfront
log stdout format rfc5424 profile myprof local0 log stdout format rfc5424 profile my-dft-prof local0
log-format "log generated using proxy logformat, from '%OG'" log-format "log generated using proxy logformat, from '%OG'"
tcp-request connection do-log #uses special log-profile format acl local src 127.0.0.1
tcp-request content do-log #uses proxy logformat # on connection use either log-profile from the logger (my-dft-prof) or
# explicit my-local-prof if source ip is localhost
tcp-request connection do-log if !local
tcp-request connection do-log profile my-local-prof if local
# on content use proxy logformat, since no override was specified
# in my-dft-prof
tcp-request content do-log
do-resolve(<var>,<resolvers>[,ipv4|ipv6]) <expr> do-resolve(<var>,<resolvers>[,ipv4|ipv6]) <expr>
Usable in: QUIC Ini| TCP RqCon| RqSes| RqCnt| RsCnt| HTTP Req| Res| Aft Usable in: QUIC Ini| TCP RqCon| RqSes| RqCnt| RsCnt| HTTP Req| Res| Aft
@ -21484,7 +21636,8 @@ jwt_decrypt_cert(<cert>)
format (five dot-separated base64-url encoded strings). format (five dot-separated base64-url encoded strings).
This converter can be used for tokens that have an algorithm ("alg" field of This converter can be used for tokens that have an algorithm ("alg" field of
the JOSE header) among the following: RSA1_5, RSA-OAEP or RSA-OAEP-256. the JOSE header) among the following: RSA1_5, RSA-OAEP, RSA-OAEP-256,
ECDH-ES, ECDH-ES+A128KW, ECDH-ES+A192KW or ECDH-ES+A256KW.
The JWE token must be provided base64url-encoded and the output will be The JWE token must be provided base64url-encoded and the output will be
provided "raw". If an error happens during token parsing, signature provided "raw". If an error happens during token parsing, signature
@ -21500,8 +21653,9 @@ jwt_decrypt_jwk(<jwk>)
Performs a signature validation of a JSON Web Token following the JSON Web Performs a signature validation of a JSON Web Token following the JSON Web
Encryption format (see RFC 7516) given in input and return its content Encryption format (see RFC 7516) given in input and return its content
decrypted thanks to the provided JSON Web Key (RFC7517). decrypted thanks to the provided JSON Web Key (RFC7517).
The <jwk> parameter must be a valid JWK of type 'oct' or 'RSA' ('kty' field The <jwk> parameter must be a valid JWK of type 'oct', 'EC' or 'RSA' ('kty'
of the JSON key) that can be provided either as a string or via a variable. field of the JSON key) that can be provided either as a string or via a
variable.
The only tokens managed yet are the ones using the Compact Serialization The only tokens managed yet are the ones using the Compact Serialization
format (five dot-separated base64-url encoded strings). format (five dot-separated base64-url encoded strings).
@ -21509,11 +21663,16 @@ jwt_decrypt_jwk(<jwk>)
This converter can be used to decode token that have a symmetric-type This converter can be used to decode token that have a symmetric-type
algorithm ("alg" field of the JOSE header) among the following: A128KW, algorithm ("alg" field of the JOSE header) among the following: A128KW,
A192KW, A256KW, A128GCMKW, A192GCMKW, A256GCMKW, dir. In this case, we expect A192KW, A256KW, A128GCMKW, A192GCMKW, A256GCMKW, dir. In this case, we expect
the provided JWK to be of the 'oct' type. Please note that the A128KW and the provided JWK to be of the 'oct' type.
A192KW algorithms are not available on AWS-LC and decryption will not work.
This converter also manages tokens that have an algorithm ("alg" field of This converter also manages tokens that have an algorithm ("alg" field of the
the JOSE header) among the following: RSA1_5, RSA-OAEP or RSA-OAEP-256. In JOSE header) in the RSA family (RSA1_5, RSA-OAEP or RSA-OAEP-256) when
such a case an 'RSA' type JWK representing a private key must be provided. provided an 'RSA' JWK, or in the ECDH family (ECDH-ES, ECDH-ES+A128KW,
ECDH-ES+A192KW or ECDH-ES+A256KW) when provided an 'EC' JWK.
Please note that the A128KW and A192KW algorithms are not available on AWS-LC
so the A128KW, A192KW, ECDH-ES+A128KW and ECDH-ES+A192KW algorithms won't
work.
The JWE token must be provided base64url-encoded and the output will be The JWE token must be provided base64url-encoded and the output will be
provided "raw". If an error happens during token parsing, signature provided "raw". If an error happens during token parsing, signature
@ -21527,7 +21686,7 @@ jwt_decrypt_jwk(<jwk>)
# Get a JWT from the authorization header, put its decrypted content in an # Get a JWT from the authorization header, put its decrypted content in an
# HTTP header # HTTP header
http-request set-var(txn.bearer) http_auth_bearer http-request set-var(txn.bearer) http_auth_bearer
http-request set-header X-Decrypted %[var(txn.bearer),jwt_decrypt_secret(\'{\"kty\":\"oct\",\"k\":\"wAsgsg\"}\') http-request set-header X-Decrypted %[var(txn.bearer),jwt_decrypt_jwk(\'{\"kty\":\"oct\",\"k\":\"wAsgsg\"}\')
# or via a variable # or via a variable
http-request set-var(txn.bearer) http_auth_bearer http-request set-var(txn.bearer) http_auth_bearer
@ -26897,9 +27056,10 @@ capture.req.uri : string
allocated. allocated.
capture.req.ver : string capture.req.ver : string
This extracts the request's HTTP version and returns either "HTTP/1.0" or This extracts the request's HTTP version and returns it with the format
"HTTP/1.1". Unlike "req.ver", it can be used in both request, response, and "HTTP/<major>.<minor>". It can be used in both request, response, and logs
logs because it relies on a persistent flag. because it relies on a persistent information. If the request version is not
valid, this sample fetch fails.
capture.res.hdr(<idx>) : string capture.res.hdr(<idx>) : string
This extracts the content of the header captured by the "capture response This extracts the content of the header captured by the "capture response
@ -26908,9 +27068,10 @@ capture.res.hdr(<idx>) : string
See also: "capture response header" See also: "capture response header"
capture.res.ver : string capture.res.ver : string
This extracts the response's HTTP version and returns either "HTTP/1.0" or This extracts the response's HTTP version and returns it with the format
"HTTP/1.1". Unlike "res.ver", it can be used in logs because it relies on a "HTTP/<major>.<minor>". It can be used in logs because it relies on a
persistent flag. persistent information. If the response version is not valid, this sample
fetch fails.
cookie([<name>]) : string (deprecated) cookie([<name>]) : string (deprecated)
This extracts the last occurrence of the cookie name <name> on a "Cookie" This extracts the last occurrence of the cookie name <name> on a "Cookie"
@ -27261,16 +27422,14 @@ req.timer.tq : integer
req.ver : string req.ver : string
req_ver : string (deprecated) req_ver : string (deprecated)
Returns the version string from the HTTP request, for example "1.1". This can Returns the version string from the HTTP request, with the format
be useful for ACL. For logs use the "%HV" logformat alias. Some predefined "<major>.<minor>". This can be useful for ACL. Some predefined ACL already
ACL already check for versions 1.0 and 1.1. check for common versions. It can be used in both request, response, and
logs because it relies on a persistent information. If the request version is
not valid, this sample fetch fails.
Common values are "1.0", "1.1", "2.0" or "3.0". Common values are "1.0", "1.1", "2.0" or "3.0".
In the case of http/2 and http/3, the value is not extracted from the HTTP
version in the request line but is determined by the negotiated protocol
version.
ACL derivatives : ACL derivatives :
req.ver : exact string match req.ver : exact string match
@ -27474,8 +27633,9 @@ res.timer.hdr : integer
res.ver : string res.ver : string
resp_ver : string (deprecated) resp_ver : string (deprecated)
Returns the version string from the HTTP response, for example "1.1". This Returns the version string from the HTTP response, with the format
can be useful for logs, but is mostly there for ACL. "<major>.<minor>". This can be useful for logs, but is mostly there for
ACL. If the response version is not valid, this sample fetch fails.
It may be used in tcp-check based expect rules. It may be used in tcp-check based expect rules.
@ -28037,7 +28197,7 @@ Detailed fields description :
limits have been reached. For instance, if actconn is close to 512 when limits have been reached. For instance, if actconn is close to 512 when
multiple connection errors occur, chances are high that the system limits multiple connection errors occur, chances are high that the system limits
the process to use a maximum of 1024 file descriptors and that all of them the process to use a maximum of 1024 file descriptors and that all of them
are used. See section 3 "Global parameters" to find how to tune the system. are used. See section 3 "Global section" to find how to tune the system.
- "feconn" is the total number of concurrent connections on the frontend when - "feconn" is the total number of concurrent connections on the frontend when
the session was logged. It is useful to estimate the amount of resource the session was logged. It is useful to estimate the amount of resource
@ -28277,7 +28437,7 @@ Detailed fields description :
limits have been reached. For instance, if actconn is close to 512 or 1024 limits have been reached. For instance, if actconn is close to 512 or 1024
when multiple connection errors occur, chances are high that the system when multiple connection errors occur, chances are high that the system
limits the process to use a maximum of 1024 file descriptors and that all limits the process to use a maximum of 1024 file descriptors and that all
of them are used. See section 3 "Global parameters" to find how to tune the of them are used. See section 3 "Global section" to find how to tune the
system. system.
- "feconn" is the total number of concurrent connections on the frontend when - "feconn" is the total number of concurrent connections on the frontend when
@ -28735,7 +28895,7 @@ Please refer to the table below for currently defined aliases :
| Others | | Others |
+---+------+------------------------------------------------------+---------+ +---+------+------------------------------------------------------+---------+
| | %B | bytes_read (from server to client) | numeric | | | %B | bytes_read (from server to client) | numeric |
| | | %[req.bytes_in] | | | | | %[res.bytes_in] | |
+---+------+------------------------------------------------------+---------+ +---+------+------------------------------------------------------+---------+
| H | %CC | captured_request_cookie | string | | H | %CC | captured_request_cookie | string |
+---+------+------------------------------------------------------+---------+ +---+------+------------------------------------------------------+---------+
@ -29888,7 +30048,21 @@ a server by adding some latencies in the processing.
9.2. HTTP compression 9.2. HTTP compression
--------------------- ---------------------
filter compression filter comp-req
Enables filter that explicitly tries to compress HTTP requests according to
"compression" settings. Implicitly sets "compression direction request".
filter comp-res
Enables filter that explicitly tries to compress HTTP responses according to
"compression" settings. Implicitly sets "compression direction response"
filter compression (deprecated)
Alias for backward compatibility purposes that is functionnally equivalent to
enabling both "comp-req" and "comp-res" filter. "compression" keyword must be
used to configure appropriate behavior:
The HTTP compression has been moved in a filter in HAProxy 1.7. "compression" The HTTP compression has been moved in a filter in HAProxy 1.7. "compression"
keyword must still be used to enable and configure the HTTP compression. And keyword must still be used to enable and configure the HTTP compression. And

View file

@ -58,8 +58,13 @@ versions. It displays its usage when run without argument or wrong arguments:
-T <line> : multiple option; append <line> to the "traces" section -T <line> : multiple option; append <line> to the "traces" section
-C : dump the configuration and exit -C : dump the configuration and exit
-D : goes daemon -D : goes daemon
-b <keysize> : RSA key size in bits (ex: "2048", "4096"...)
-c <curves> : ECSDA curves (ex: "P-256", "P-384"...)
-v : shows version -v : shows version
-d : enable the traces for all http protocols -d : enable the traces for all http protocols
--quic-bind-opts <opts> : append options to QUIC "bind" lines
--tcp-bind-opts <opts> : append options to TCP "bind" lines
Arguments -G, -F, -T permit to append one or multiple lines at the end of their Arguments -G, -F, -T permit to append one or multiple lines at the end of their
respective sections. A tab character ('\t') is prepended at the beginning of respective sections. A tab character ('\t') is prepended at the beginning of

View file

@ -539,10 +539,22 @@ message. These functions are used by HTX analyzers or by multiplexers.
with the first block not removed, or NULL if everything was removed, and with the first block not removed, or NULL if everything was removed, and
the amount of data drained. the amount of data drained.
- htx_xfer_blks() transfers HTX blocks from an HTX message to another, - htx_xfer() transfers HTX blocks from an HTX message to another, stopping
stopping after the first block of a specified type is transferred or when when a specific amount of bytes, including meta-data, was copied. If the
a specific amount of bytes, including meta-data, was moved. If the tail tail block is a DATA block, it may be partially copied. All other block
block is a DATA block, it may be partially moved. All other block are are transferred at once. By default, copied blocks are removed from the
original HTX message and headers and trailers parts cannot be partially
copied. But flags can be set to change the default behavior:
- HTX_XFER_KEEP_SRC_BLKS: source blocks are not removed
- HTX_XFER_PARTIAL_HDRS_COPY: partial headers and trailers
part can be xferred
- HTX_XFER_HDRS_ONLY: Only the headers part is xferred
- htx_xfer_blks() [DEPRECATED] transfers HTX blocks from an HTX message to
another, stopping after the first block of a specified type is transferred
or when a specific amount of bytes, including meta-data, was moved. If the
tail block is a DATA block, it may be partially moved. All other block are
transferred at once or kept. This function returns a mixed value, with the transferred at once or kept. This function returns a mixed value, with the
last block moved, or NULL if nothing was moved, and the amount of data last block moved, or NULL if nothing was moved, and the amount of data
transferred. When HEADERS or TRAILERS blocks must be transferred, this transferred. When HEADERS or TRAILERS blocks must be transferred, this

View file

@ -0,0 +1,50 @@
2026-03-12 - thread execution context
Thread execution context (thread_exec_ctx) is a combination of type and pointer
that are set in the current running thread at th_ctx->exec_ctx when entering
certain processing (tasks, sample fetch functions, actions, CLI keywords etc).
They're refined along execution, so that a task such as process_stream could
temporarily switch to a converter while evaluating an expression and switch
back to process_stream. They are reported in thread dumps and are mixed with
caller locations for memory profiling. As such they are intentionally not too
precise in order to avoid an explosion of the number of buckets. At the moment,
the level of granularity it provides is sufficient to try to narrow a
misbehaving origin down to a list of keywords. The context types can currently
be:
- something registered via an initcall, with the initcall's location
- something registered via an ha_caller, with the caller's location
- an explicit sample fetch / converter / action / CLI keyword list
- an explicit function (mainly used for actions without keywords)
- a task / tasklet (no distinction is made), using the ->process pointer
- a filter (e.g. compression), via flt_conf, reporting name
- a mux (via the mux_ops, reporting the name)
- an applet (e.g. cache, stats, CLI)
A macro EXEC_CTX_MAKE(type, pointer) makes a thread_exec_ctx from such
values.
A macro EXEC_CTX_NO_RET(ctx, statement) calls a void statement under the
specified context.
A macro EXEC_CTX_WITH_RET(ctx, expr) calls an expression under the specified
context.
Most locations were modified to directly use these macros on the fly, by
retrieving the context from where it was set on the element being evaluated
(e.g. an action rule contains the context inherited by the action keyword
that was used to create it).
In tools.c, chunk_append_thread_ctx() tries to decode the given exec_ctx and
appends it into the provided buffer. It's used by ha_thread_dump_one() and
cli_io_handler_show_activity() for memory profiling. In this latter case,
the detected thread_ctx are reported in the output under brackets prefixed
with "[via ...]" to distinguish call paths to the same allocators.
A good way to test if a context is properly reported is to place a bleeding
malloc() call into one of the monitored functions, e.g.:
DISGUISE(malloc(8));
and issue "show profiling memory" after stressing the function. Its context
must appear on the right with the number of calls.

View file

@ -1740,10 +1740,7 @@ add backend <name> from <defproxy> [mode <mode>] [guid <guid>] [ EXPERIMENTAL ]
All named default proxies can be used, given that they validate the same All named default proxies can be used, given that they validate the same
inheritance rules applied during configuration parsing. There is some inheritance rules applied during configuration parsing. There is some
exceptions though, for example when the mode is neither TCP nor HTTP. Another exceptions though, for example when the mode is neither TCP nor HTTP.
exception is that it is not yet possible to use a default proxies which
reference custom HTTP errors, for example via the errorfiles or http-rules
keywords.
This command is restricted and can only be issued on sockets configured for This command is restricted and can only be issued on sockets configured for
level "admin". Moreover, this feature is still considered in development so it level "admin". Moreover, this feature is still considered in development so it
@ -2124,6 +2121,30 @@ del acl <acl> [<key>|#<ref>]
listing the content of the acl. Note that if the reference <acl> is a name and listing the content of the acl. Note that if the reference <acl> is a name and
is shared with a map, the entry will be also deleted in the map. is shared with a map, the entry will be also deleted in the map.
del backend <name>
Removes the backend proxy with the name <name>.
This operation is only possible for TCP or HTTP proxies. To succeed, the
backend instance must have been first unpublished. Also, all of its servers
must first be removed (via "del server" CLI). Finally, no stream must still
be attached to the backend instance.
There is additional restrictions which prevent backend removal. First, a
backend cannot be removed if it is explicitely referenced by config elements,
for example via a use_backend rule or in sample expressions. Some proxies
options are also incompatible with runtime deletion. Currently, this is the
case when deprecated dispatch or option transparent are used. Also, a backend
cannot be removed if there is a stick-table declared in it. Finally, it is
impossible for now to remove a backend if QUIC servers were present in it.
It can be useful to use "wait be-removable" prior to this command to check
for the aformentioned requisites. This also provides a methode to wait for
the final closure of the streams attached to the target backend.
This command is restricted and can only be issued on sockets configured for
level "admin". Moreover, this feature is still considered in development so it
also requires experimental mode (see "experimental-mode on").
del map <map> [<key>|#<ref>] del map <map> [<key>|#<ref>]
Delete all the map entries from the map <map> corresponding to the key <key>. Delete all the map entries from the map <map> corresponding to the key <key>.
<map> is the #<id> or the <name> returned by "show map". If the <ref> is used, <map> is the #<id> or the <name> returned by "show map". If the <ref> is used,
@ -3335,7 +3356,7 @@ show pools [byname|bysize|byusage] [detailed] [match <pfx>] [<nb>]
- Pool quic_conn_c (152 bytes) : 1337 allocated (203224 bytes), ... - Pool quic_conn_c (152 bytes) : 1337 allocated (203224 bytes), ...
Total: 15 pools, 109578176 bytes allocated, 109578176 used ... Total: 15 pools, 109578176 bytes allocated, 109578176 used ...
show profiling [{all | status | tasks | memory}] [byaddr|bytime|aggr|<max_lines>]* show profiling [{all | status | tasks | memory}] [byaddr|bytime|byctx|aggr|<max_lines>]*
Dumps the current profiling settings, one per line, as well as the command Dumps the current profiling settings, one per line, as well as the command
needed to change them. When tasks profiling is enabled, some per-function needed to change them. When tasks profiling is enabled, some per-function
statistics collected by the scheduler will also be emitted, with a summary statistics collected by the scheduler will also be emitted, with a summary
@ -3344,14 +3365,15 @@ show profiling [{all | status | tasks | memory}] [byaddr|bytime|aggr|<max_lines>
allocations/releases and their sizes will be reported. It is possible to allocations/releases and their sizes will be reported. It is possible to
limit the dump to only the profiling status, the tasks, or the memory limit the dump to only the profiling status, the tasks, or the memory
profiling by specifying the respective keywords; by default all profiling profiling by specifying the respective keywords; by default all profiling
information are dumped. It is also possible to limit the number of lines information are dumped. It is also possible to limit the number of lines of
of output of each category by specifying a numeric limit. If is possible to of output of each category by specifying a numeric limit. If is possible to
request that the output is sorted by address or by total execution time request that the output is sorted by address, by total execution time, or by
instead of usage, e.g. to ease comparisons between subsequent calls or to calling context instead of usage, e.g. to ease comparisons between subsequent
check what needs to be optimized, and to aggregate task activity by called calls or to check what needs to be optimized, and to aggregate task activity
function instead of seeing the details. Please note that profiling is by called function instead of seeing the details. Please note that profiling
essentially aimed at developers since it gives hints about where CPU cycles is essentially aimed at developers since it gives hints about where CPU
or memory are wasted in the code. There is nothing useful to monitor there. cycles or memory are wasted in the code. There is nothing useful to monitor
there.
show resolvers [<resolvers section id>] show resolvers [<resolvers section id>]
Dump statistics for the given resolvers section, or all resolvers sections Dump statistics for the given resolvers section, or all resolvers sections
@ -4526,6 +4548,13 @@ wait { -h | <delay> } [<condition> [<args>...]]
specified condition to be satisfied, to unrecoverably fail, or to remain specified condition to be satisfied, to unrecoverably fail, or to remain
unsatisfied for the whole <delay> duration. The supported conditions are: unsatisfied for the whole <delay> duration. The supported conditions are:
- be-removable <proxy> : this will wait for the specified proxy backend to be
removable by the "del backend" command. Some conditions will never be
accepted (e.g. backend not yet unpublished or with servers in it) and will
cause the report of a specific error message indicating what condition is
not met. If everything is OK before the delay, a success is returned and
the operation is terminated.
- srv-removable <proxy>/<server> : this will wait for the specified server to - srv-removable <proxy>/<server> : this will wait for the specified server to
be removable by the "del server" command, i.e. be in maintenance and no be removable by the "del server" command, i.e. be in maintenance and no
longer have any connection on it (neither active or idle). Some conditions longer have any connection on it (neither active or idle). Some conditions

View file

@ -58,6 +58,7 @@ struct acme_auth {
struct ist auth; /* auth URI */ struct ist auth; /* auth URI */
struct ist chall; /* challenge URI */ struct ist chall; /* challenge URI */
struct ist token; /* token */ struct ist token; /* token */
int validated; /* already validated */
int ready; /* is the challenge ready ? */ int ready; /* is the challenge ready ? */
void *next; void *next;
}; };

View file

@ -198,6 +198,11 @@ struct act_rule {
struct server *srv; /* target server to attach the connection */ struct server *srv; /* target server to attach the connection */
struct sample_expr *name; /* used to differentiate idle connections */ struct sample_expr *name; /* used to differentiate idle connections */
} attach_srv; /* 'attach-srv' rule */ } attach_srv; /* 'attach-srv' rule */
struct {
enum log_orig_id orig;
char *profile_name;
struct log_profile *profile;
} do_log; /* 'do-log' action */
struct { struct {
int value; int value;
struct sample_expr *expr; struct sample_expr *expr;
@ -206,6 +211,7 @@ struct act_rule {
void *p[4]; void *p[4];
} act; /* generic pointers to be used by custom actions */ } act; /* generic pointers to be used by custom actions */
} arg; /* arguments used by some actions */ } arg; /* arguments used by some actions */
struct thread_exec_ctx exec_ctx; /* execution context */
struct { struct {
char *file; /* file name where the rule appears (or NULL) */ char *file; /* file name where the rule appears (or NULL) */
int line; /* line number where the rule appears */ int line; /* line number where the rule appears */
@ -217,7 +223,9 @@ struct action_kw {
enum act_parse_ret (*parse)(const char **args, int *cur_arg, struct proxy *px, enum act_parse_ret (*parse)(const char **args, int *cur_arg, struct proxy *px,
struct act_rule *rule, char **err); struct act_rule *rule, char **err);
int flags; int flags;
/* 4 bytes here */
void *private; void *private;
struct thread_exec_ctx exec_ctx; /* execution context */
}; };
struct action_kw_list { struct action_kw_list {

View file

@ -35,6 +35,7 @@ int act_resolution_cb(struct resolv_requester *requester, struct dns_counters *c
int act_resolution_error_cb(struct resolv_requester *requester, int error_code); int act_resolution_error_cb(struct resolv_requester *requester, int error_code);
const char *action_suggest(const char *word, const struct list *keywords, const char **extra); const char *action_suggest(const char *word, const struct list *keywords, const char **extra);
void free_act_rule(struct act_rule *rule); void free_act_rule(struct act_rule *rule);
void act_add_list(struct list *head, struct action_kw_list *kw_list);
static inline struct action_kw *action_lookup(struct list *keywords, const char *kw) static inline struct action_kw *action_lookup(struct list *keywords, const char *kw)
{ {

View file

@ -24,6 +24,7 @@
#include <haproxy/api-t.h> #include <haproxy/api-t.h>
#include <haproxy/freq_ctr-t.h> #include <haproxy/freq_ctr-t.h>
#include <haproxy/tinfo-t.h>
/* bit fields for the "profiling" global variable */ /* bit fields for the "profiling" global variable */
#define HA_PROF_TASKS_OFF 0x00000000 /* per-task CPU profiling forced disabled */ #define HA_PROF_TASKS_OFF 0x00000000 /* per-task CPU profiling forced disabled */
@ -84,6 +85,7 @@ struct memprof_stats {
unsigned long long alloc_tot; unsigned long long alloc_tot;
unsigned long long free_tot; unsigned long long free_tot;
void *info; // for pools, ptr to the pool void *info; // for pools, ptr to the pool
struct thread_exec_ctx exec_ctx;
}; };
#endif #endif

View file

@ -130,6 +130,7 @@ struct appctx {
int (*io_handler)(struct appctx *appctx); /* used within the cli_io_handler when st0 = CLI_ST_CALLBACK */ int (*io_handler)(struct appctx *appctx); /* used within the cli_io_handler when st0 = CLI_ST_CALLBACK */
void (*io_release)(struct appctx *appctx); /* used within the cli_io_handler when st0 = CLI_ST_CALLBACK, void (*io_release)(struct appctx *appctx); /* used within the cli_io_handler when st0 = CLI_ST_CALLBACK,
if the command is terminated or the session released */ if the command is terminated or the session released */
struct cli_kw *kw; /* the keyword being processed */
} cli_ctx; /* context dedicated to the CLI applet */ } cli_ctx; /* context dedicated to the CLI applet */
struct buffer_wait buffer_wait; /* position in the list of objects waiting for a buffer */ struct buffer_wait buffer_wait; /* position in the list of objects waiting for a buffer */

View file

@ -62,6 +62,13 @@ ssize_t applet_append_line(void *ctx, struct ist v1, struct ist v2, size_t ofs,
static forceinline void applet_fl_set(struct appctx *appctx, uint on); static forceinline void applet_fl_set(struct appctx *appctx, uint on);
static forceinline void applet_fl_clr(struct appctx *appctx, uint off); static forceinline void applet_fl_clr(struct appctx *appctx, uint off);
/* macros to switch the calling context to the applet during a call. There's
* one with a return value for most calls, and one without for the few like
* fct(), shut(), or release() with no return.
*/
#define CALL_APPLET_WITH_RET(applet, func) EXEC_CTX_WITH_RET(EXEC_CTX_MAKE(TH_EX_CTX_APPLET, (applet)), (applet)->func)
#define CALL_APPLET_NO_RET(applet, func) EXEC_CTX_NO_RET(EXEC_CTX_MAKE(TH_EX_CTX_APPLET, (applet)), (applet)->func)
static forceinline uint appctx_app_test(const struct appctx *appctx, uint test) static forceinline uint appctx_app_test(const struct appctx *appctx, uint test)
{ {
@ -126,7 +133,7 @@ static inline int appctx_init(struct appctx *appctx)
task_set_thread(appctx->t, tid); task_set_thread(appctx->t, tid);
if (appctx->applet->init) if (appctx->applet->init)
return appctx->applet->init(appctx); return CALL_APPLET_WITH_RET(appctx->applet, init(appctx));
return 0; return 0;
} }

View file

@ -376,6 +376,7 @@ static inline void channel_add_input(struct channel *chn, unsigned int len)
c_adv(chn, fwd); c_adv(chn, fwd);
} }
/* notify that some data was read */ /* notify that some data was read */
chn_prod(chn)->bytes_in += len;
chn->flags |= CF_READ_EVENT; chn->flags |= CF_READ_EVENT;
} }

View file

@ -59,6 +59,7 @@ enum chk_result {
#define CHK_ST_FASTINTER 0x0400 /* force fastinter check */ #define CHK_ST_FASTINTER 0x0400 /* force fastinter check */
#define CHK_ST_READY 0x0800 /* check ready to migrate or run, see below */ #define CHK_ST_READY 0x0800 /* check ready to migrate or run, see below */
#define CHK_ST_SLEEPING 0x1000 /* check was sleeping, i.e. not currently bound to a thread, see below */ #define CHK_ST_SLEEPING 0x1000 /* check was sleeping, i.e. not currently bound to a thread, see below */
#define CHK_ST_USE_SMALL_BUFF 0x2000 /* Use small buffers if possible for the request */
/* 4 possible states for CHK_ST_SLEEPING and CHK_ST_READY: /* 4 possible states for CHK_ST_SLEEPING and CHK_ST_READY:
* SLP RDY State Description * SLP RDY State Description
@ -188,6 +189,7 @@ struct check {
char **envp; /* the environment to use if running a process-based check */ char **envp; /* the environment to use if running a process-based check */
struct pid_list *curpid; /* entry in pid_list used for current process-based test, or -1 if not in test */ struct pid_list *curpid; /* entry in pid_list used for current process-based test, or -1 if not in test */
struct sockaddr_storage addr; /* the address to check */ struct sockaddr_storage addr; /* the address to check */
struct protocol *proto; /* protocol used for check, may be different from the server's one */
char *pool_conn_name; /* conn name used on reuse */ char *pool_conn_name; /* conn name used on reuse */
char *sni; /* Server name */ char *sni; /* Server name */
char *alpn_str; /* ALPN to use for checks */ char *alpn_str; /* ALPN to use for checks */

View file

@ -78,12 +78,11 @@ struct task *process_chk(struct task *t, void *context, unsigned int state);
struct task *srv_chk_io_cb(struct task *t, void *ctx, unsigned int state); struct task *srv_chk_io_cb(struct task *t, void *ctx, unsigned int state);
int check_buf_available(void *target); int check_buf_available(void *target);
struct buffer *check_get_buf(struct check *check, struct buffer *bptr); struct buffer *check_get_buf(struct check *check, struct buffer *bptr, unsigned int small_buffer);
void check_release_buf(struct check *check, struct buffer *bptr); void check_release_buf(struct check *check, struct buffer *bptr);
const char *init_check(struct check *check, int type); const char *init_check(struct check *check, int type);
void free_check(struct check *check); void free_check(struct check *check);
void check_purge(struct check *check); void check_purge(struct check *check);
int wake_srv_chk(struct stconn *sc);
int init_srv_check(struct server *srv); int init_srv_check(struct server *srv);
int init_srv_agent_check(struct server *srv); int init_srv_agent_check(struct server *srv);

View file

@ -33,6 +33,7 @@
extern struct pool_head *pool_head_trash; extern struct pool_head *pool_head_trash;
extern struct pool_head *pool_head_large_trash; extern struct pool_head *pool_head_large_trash;
extern struct pool_head *pool_head_small_trash;
/* function prototypes */ /* function prototypes */
@ -48,6 +49,7 @@ int chunk_strcmp(const struct buffer *chk, const char *str);
int chunk_strcasecmp(const struct buffer *chk, const char *str); int chunk_strcasecmp(const struct buffer *chk, const char *str);
struct buffer *get_trash_chunk(void); struct buffer *get_trash_chunk(void);
struct buffer *get_large_trash_chunk(void); struct buffer *get_large_trash_chunk(void);
struct buffer *get_small_trash_chunk(void);
struct buffer *get_trash_chunk_sz(size_t size); struct buffer *get_trash_chunk_sz(size_t size);
struct buffer *get_larger_trash_chunk(struct buffer *chunk); struct buffer *get_larger_trash_chunk(struct buffer *chunk);
int init_trash_buffers(int first); int init_trash_buffers(int first);
@ -133,6 +135,29 @@ static forceinline struct buffer *alloc_large_trash_chunk(void)
return chunk; return chunk;
} }
/*
* Allocate a small trash chunk from the reentrant pool. The buffer starts at
* the end of the chunk. This chunk must be freed using free_trash_chunk(). This
* call may fail and the caller is responsible for checking that the returned
* pointer is not NULL.
*/
static forceinline struct buffer *alloc_small_trash_chunk(void)
{
struct buffer *chunk;
if (!pool_head_small_trash)
return NULL;
chunk = pool_alloc(pool_head_small_trash);
if (chunk) {
char *buf = (char *)chunk + sizeof(struct buffer);
*buf = 0;
chunk_init(chunk, buf,
pool_head_small_trash->size - sizeof(struct buffer));
}
return chunk;
}
/* /*
* Allocate a trash chunk accordingly to the requested size. This chunk must be * Allocate a trash chunk accordingly to the requested size. This chunk must be
* freed using free_trash_chunk(). This call may fail and the caller is * freed using free_trash_chunk(). This call may fail and the caller is
@ -140,7 +165,9 @@ static forceinline struct buffer *alloc_large_trash_chunk(void)
*/ */
static forceinline struct buffer *alloc_trash_chunk_sz(size_t size) static forceinline struct buffer *alloc_trash_chunk_sz(size_t size)
{ {
if (likely(size <= pool_head_trash->size)) if (pool_head_small_trash && size <= pool_head_small_trash->size)
return alloc_small_trash_chunk();
else if (size <= pool_head_trash->size)
return alloc_trash_chunk(); return alloc_trash_chunk();
else if (pool_head_large_trash && size <= pool_head_large_trash->size) else if (pool_head_large_trash && size <= pool_head_large_trash->size)
return alloc_large_trash_chunk(); return alloc_large_trash_chunk();
@ -153,10 +180,12 @@ static forceinline struct buffer *alloc_trash_chunk_sz(size_t size)
*/ */
static forceinline void free_trash_chunk(struct buffer *chunk) static forceinline void free_trash_chunk(struct buffer *chunk)
{ {
if (likely(chunk && chunk->size == pool_head_trash->size - sizeof(struct buffer))) if (pool_head_small_trash && chunk && chunk->size == pool_head_small_trash->size - sizeof(struct buffer))
pool_free(pool_head_trash, chunk); pool_free(pool_head_small_trash, chunk);
else else if (pool_head_large_trash && chunk && chunk->size == pool_head_large_trash->size - sizeof(struct buffer))
pool_free(pool_head_large_trash, chunk); pool_free(pool_head_large_trash, chunk);
else
pool_free(pool_head_trash, chunk);
} }
/* copies chunk <src> into <chk>. Returns 0 in case of failure. */ /* copies chunk <src> into <chk>. Returns 0 in case of failure. */

View file

@ -23,6 +23,7 @@
#define _HAPROXY_CLI_T_H #define _HAPROXY_CLI_T_H
#include <haproxy/applet-t.h> #include <haproxy/applet-t.h>
#include <haproxy/tinfo-t.h>
/* Access level for a stats socket (appctx->cli_ctx.level) */ /* Access level for a stats socket (appctx->cli_ctx.level) */
#define ACCESS_LVL_NONE 0x0000 #define ACCESS_LVL_NONE 0x0000
@ -100,6 +101,7 @@ enum cli_wait_err {
enum cli_wait_cond { enum cli_wait_cond {
CLI_WAIT_COND_NONE, // no condition to wait on CLI_WAIT_COND_NONE, // no condition to wait on
CLI_WAIT_COND_SRV_UNUSED,// wait for server to become unused CLI_WAIT_COND_SRV_UNUSED,// wait for server to become unused
CLI_WAIT_COND_BE_UNUSED, // wait for backend to become unused
}; };
struct cli_wait_ctx { struct cli_wait_ctx {
@ -119,6 +121,8 @@ struct cli_kw {
void (*io_release)(struct appctx *appctx); void (*io_release)(struct appctx *appctx);
void *private; void *private;
int level; /* this is the level needed to show the keyword usage and to use it */ int level; /* this is the level needed to show the keyword usage and to use it */
/* 4-byte hole here */
struct thread_exec_ctx exec_ctx; /* execution context */
}; };
struct cli_kw_list { struct cli_kw_list {

View file

@ -34,6 +34,7 @@
#include <haproxy/listener-t.h> #include <haproxy/listener-t.h>
#include <haproxy/obj_type.h> #include <haproxy/obj_type.h>
#include <haproxy/pool-t.h> #include <haproxy/pool-t.h>
#include <haproxy/protocol.h>
#include <haproxy/server.h> #include <haproxy/server.h>
#include <haproxy/session-t.h> #include <haproxy/session-t.h>
#include <haproxy/task-t.h> #include <haproxy/task-t.h>
@ -49,6 +50,13 @@ extern struct mux_stopping_data mux_stopping_data[MAX_THREADS];
#define IS_HTX_CONN(conn) ((conn)->mux && ((conn)->mux->flags & MX_FL_HTX)) #define IS_HTX_CONN(conn) ((conn)->mux && ((conn)->mux->flags & MX_FL_HTX))
/* macros to switch the calling context to the mux during a call. There's one
* with a return value for most calls, and one without for the few like shut(),
* detach() or destroy() with no return.
*/
#define CALL_MUX_WITH_RET(mux, func) EXEC_CTX_WITH_RET(EXEC_CTX_MAKE(TH_EX_CTX_MUX, (mux)), (mux)->func)
#define CALL_MUX_NO_RET(mux, func) EXEC_CTX_NO_RET(EXEC_CTX_MAKE(TH_EX_CTX_MUX, (mux)), (mux)->func)
/* receive a PROXY protocol header over a connection */ /* receive a PROXY protocol header over a connection */
int conn_recv_proxy(struct connection *conn, int flag); int conn_recv_proxy(struct connection *conn, int flag);
int conn_send_proxy(struct connection *conn, unsigned int flag); int conn_send_proxy(struct connection *conn, unsigned int flag);
@ -480,7 +488,7 @@ static inline int conn_install_mux(struct connection *conn, const struct mux_ops
conn->mux = mux; conn->mux = mux;
conn->ctx = ctx; conn->ctx = ctx;
ret = mux->init ? mux->init(conn, prx, sess, &BUF_NULL) : 0; ret = mux->init ? CALL_MUX_WITH_RET(mux, init(conn, prx, sess, &BUF_NULL)) : 0;
if (ret < 0) { if (ret < 0) {
conn->mux = NULL; conn->mux = NULL;
conn->ctx = NULL; conn->ctx = NULL;
@ -602,13 +610,13 @@ void list_mux_proto(FILE *out);
*/ */
static inline const struct mux_proto_list *conn_get_best_mux_entry( static inline const struct mux_proto_list *conn_get_best_mux_entry(
const struct ist mux_proto, const struct ist mux_proto,
int proto_side, int proto_mode) int proto_side, int proto_is_quic, int proto_mode)
{ {
struct mux_proto_list *item; struct mux_proto_list *item;
struct mux_proto_list *fallback = NULL; struct mux_proto_list *fallback = NULL;
list_for_each_entry(item, &mux_proto_list.list, list) { list_for_each_entry(item, &mux_proto_list.list, list) {
if (!(item->side & proto_side) || !(item->mode & proto_mode)) if (!(item->side & proto_side) || !(item->mode & proto_mode) || (proto_is_quic && !(item->mux->flags & MX_FL_FRAMED)))
continue; continue;
if (istlen(mux_proto) && isteq(mux_proto, item->token)) if (istlen(mux_proto) && isteq(mux_proto, item->token))
return item; return item;
@ -633,7 +641,7 @@ static inline const struct mux_ops *conn_get_best_mux(struct connection *conn,
{ {
const struct mux_proto_list *item; const struct mux_proto_list *item;
item = conn_get_best_mux_entry(mux_proto, proto_side, proto_mode); item = conn_get_best_mux_entry(mux_proto, proto_side, proto_is_quic(conn->ctrl), proto_mode);
return item ? item->mux : NULL; return item ? item->mux : NULL;
} }

View file

@ -185,6 +185,29 @@ struct be_counters {
} p; /* protocol-specific stats */ } p; /* protocol-specific stats */
}; };
/* extra counters that are registered at boot by various modules */
enum counters_type {
COUNTERS_FE = 0,
COUNTERS_BE,
COUNTERS_SV,
COUNTERS_LI,
COUNTERS_RSLV,
COUNTERS_OFF_END /* must always be last */
};
struct extra_counters {
char **datap; /* points to pointer to heap containing counters allocated in a linear fashion */
size_t size; /* size of allocated data */
size_t tgrp_step; /* distance in words between two datap for consecutive tgroups, 0 for single */
uint nbtgrp; /* number of thread groups accessing these counters */
enum counters_type type; /* type of object containing the counters */
};
#define EXTRA_COUNTERS(name) \
struct extra_counters *name
#endif /* _HAPROXY_COUNTERS_T_H */ #endif /* _HAPROXY_COUNTERS_T_H */
/* /*

View file

@ -26,6 +26,9 @@
#include <haproxy/counters-t.h> #include <haproxy/counters-t.h>
#include <haproxy/guid-t.h> #include <haproxy/guid-t.h>
#include <haproxy/global.h>
extern THREAD_LOCAL void *trash_counters;
int counters_fe_shared_prepare(struct fe_counters_shared *counters, const struct guid_node *guid, char **errmsg); int counters_fe_shared_prepare(struct fe_counters_shared *counters, const struct guid_node *guid, char **errmsg);
int counters_be_shared_prepare(struct be_counters_shared *counters, const struct guid_node *guid, char **errmsg); int counters_be_shared_prepare(struct be_counters_shared *counters, const struct guid_node *guid, char **errmsg);
@ -101,4 +104,106 @@ void counters_be_shared_drop(struct be_counters_shared *counters);
__ret; \ __ret; \
}) })
#define COUNTERS_UPDATE_MAX(counter, count) \
do { \
if (!(global.tune.options & GTUNE_NO_MAX_COUNTER)) \
HA_ATOMIC_UPDATE_MAX(counter, count); \
} while (0)
/* Manipulation of extra_counters, for boot-time registrable modules */
/* retrieve the base storage of extra counters (first tgroup if any) */
#define EXTRA_COUNTERS_BASE(counters, mod) \
(likely(counters) ? \
((void *)(*(counters)->datap + (mod)->counters_off[(counters)->type])) : \
(trash_counters))
/* retrieve the pointer to the extra counters storage for module <mod> for the
* current TGID.
*/
#define EXTRA_COUNTERS_GET(counters, mod) \
(likely(counters) ? \
((void *)(counters)->datap[(counters)->tgrp_step * (tgid - 1)] + \
(mod)->counters_off[(counters)->type]) : \
(trash_counters))
#define EXTRA_COUNTERS_REGISTER(counters, ctype, alloc_failed_label, storage, step) \
do { \
typeof(*counters) _ctr; \
_ctr = calloc(1, sizeof(*_ctr)); \
if (!_ctr) \
goto alloc_failed_label; \
_ctr->type = (ctype); \
_ctr->tgrp_step = (step); \
_ctr->datap = (storage); \
*(counters) = _ctr; \
} while (0)
#define EXTRA_COUNTERS_ADD(mod, counters, new_counters, csize) \
do { \
typeof(counters) _ctr = (counters); \
(mod)->counters_off[_ctr->type] = _ctr->size; \
_ctr->size += (csize); \
} while (0)
#define EXTRA_COUNTERS_ALLOC(counters, alloc_failed_label, nbtg) \
do { \
typeof(counters) _ctr = (counters); \
char **datap = _ctr->datap; \
uint tgrp; \
_ctr->nbtgrp = _ctr->tgrp_step ? (nbtg) : 1; \
for (tgrp = 0; tgrp < _ctr->nbtgrp; tgrp++) { \
*datap = malloc((_ctr)->size); \
if (!*_ctr->datap) \
goto alloc_failed_label; \
datap += _ctr->tgrp_step; \
} \
} while (0)
#define EXTRA_COUNTERS_INIT(counters, mod, init_counters, init_counters_size) \
do { \
typeof(counters) _ctr = (counters); \
char **datap = _ctr->datap; \
uint tgrp; \
for (tgrp = 0; tgrp < _ctr->nbtgrp; tgrp++) { \
memcpy(*datap + mod->counters_off[_ctr->type], \
(init_counters), (init_counters_size)); \
datap += _ctr->tgrp_step; \
} \
} while (0)
#define EXTRA_COUNTERS_FREE(counters) \
do { \
typeof(counters) _ctr = (counters); \
if (_ctr) { \
char **datap = _ctr->datap; \
uint tgrp; \
for (tgrp = 0; tgrp < _ctr->nbtgrp; tgrp++) { \
ha_free(datap); \
datap += _ctr->tgrp_step; \
} \
free(_ctr); \
} \
} while (0)
/* aggregate all values of <metricp> over the thread groups handled by
* <counters>. <metricp> MUST correspond to an entry of the first tgrp of
* <counters>. The number of groups and the step are found in <counters>. The
* type of the return value is the same as <metricp>, and must be a scalar so
* that values are summed before being returned.
*/
#define EXTRA_COUNTERS_AGGR(counters, metricp) \
({ \
typeof(counters) _ctr = (counters); \
typeof(metricp) *valp, _ret = 0; \
if (_ctr) { \
size_t ofs = (char *)&metricp - _ctr->datap[0]; \
uint tgrp; \
for (tgrp = 0; tgrp < _ctr->nbtgrp; tgrp++) { \
valp = (typeof(valp))(_ctr->datap[tgrp * (counters)->tgrp_step] + ofs); \
_ret += HA_ATOMIC_LOAD(valp); \
} \
} \
_ret; \
})
#endif /* _HAPROXY_COUNTERS_H */ #endif /* _HAPROXY_COUNTERS_H */

View file

@ -536,6 +536,11 @@
#define TIME_STATS_SAMPLES 512 #define TIME_STATS_SAMPLES 512
#endif #endif
/* number of samples used to measure the load in the run queue */
#ifndef RQ_LOAD_SAMPLES
#define RQ_LOAD_SAMPLES 512
#endif
/* max ocsp cert id asn1 encoded length */ /* max ocsp cert id asn1 encoded length */
#ifndef OCSP_MAX_CERTID_ASN1_LENGTH #ifndef OCSP_MAX_CERTID_ASN1_LENGTH
#define OCSP_MAX_CERTID_ASN1_LENGTH 128 #define OCSP_MAX_CERTID_ASN1_LENGTH 128
@ -601,7 +606,7 @@
* store stats. * store stats.
*/ */
#ifndef MEMPROF_HASH_BITS #ifndef MEMPROF_HASH_BITS
# define MEMPROF_HASH_BITS 10 # define MEMPROF_HASH_BITS 12
#endif #endif
#define MEMPROF_HASH_BUCKETS (1U << MEMPROF_HASH_BITS) #define MEMPROF_HASH_BUCKETS (1U << MEMPROF_HASH_BITS)

View file

@ -24,12 +24,12 @@
#include <import/ebtree-t.h> #include <import/ebtree-t.h>
#include <haproxy/connection-t.h>
#include <haproxy/buf-t.h> #include <haproxy/buf-t.h>
#include <haproxy/connection-t.h>
#include <haproxy/counters-t.h>
#include <haproxy/dgram-t.h> #include <haproxy/dgram-t.h>
#include <haproxy/dns_ring-t.h> #include <haproxy/dns_ring-t.h>
#include <haproxy/obj_type-t.h> #include <haproxy/obj_type-t.h>
#include <haproxy/stats-t.h>
#include <haproxy/task-t.h> #include <haproxy/task-t.h>
#include <haproxy/thread.h> #include <haproxy/thread.h>
@ -152,6 +152,7 @@ struct dns_nameserver {
struct dns_stream_server *stream; /* used for tcp dns */ struct dns_stream_server *stream; /* used for tcp dns */
EXTRA_COUNTERS(extra_counters); EXTRA_COUNTERS(extra_counters);
char *extra_counters_storage; /* storage used for extra_counters above */
struct dns_counters *counters; struct dns_counters *counters;
struct list list; /* nameserver chained list */ struct list list; /* nameserver chained list */

View file

@ -37,6 +37,7 @@
extern struct pool_head *pool_head_buffer; extern struct pool_head *pool_head_buffer;
extern struct pool_head *pool_head_large_buffer; extern struct pool_head *pool_head_large_buffer;
extern struct pool_head *pool_head_small_buffer;
int init_buffer(void); int init_buffer(void);
void buffer_dump(FILE *o, struct buffer *b, int from, int to); void buffer_dump(FILE *o, struct buffer *b, int from, int to);
@ -66,6 +67,12 @@ static inline int b_is_large_sz(size_t sz)
return (pool_head_large_buffer && sz == pool_head_large_buffer->size); return (pool_head_large_buffer && sz == pool_head_large_buffer->size);
} }
/* Return 1 if <sz> is the size of a small buffer */
static inline int b_is_small_sz(size_t sz)
{
return (pool_head_small_buffer && sz == pool_head_small_buffer->size);
}
/* Return 1 if <bug> is a default buffer */ /* Return 1 if <bug> is a default buffer */
static inline int b_is_default(struct buffer *buf) static inline int b_is_default(struct buffer *buf)
{ {
@ -78,6 +85,12 @@ static inline int b_is_large(struct buffer *buf)
return b_is_large_sz(b_size(buf)); return b_is_large_sz(b_size(buf));
} }
/* Return 1 if <buf> is a small buffer */
static inline int b_is_small(struct buffer *buf)
{
return b_is_small_sz(b_size(buf));
}
/**************************************************/ /**************************************************/
/* Functions below are used for buffer allocation */ /* Functions below are used for buffer allocation */
/**************************************************/ /**************************************************/
@ -172,6 +185,8 @@ static inline char *__b_get_emergency_buf(void)
* than the default buffers */ \ * than the default buffers */ \
if (unlikely(b_is_large_sz(sz))) \ if (unlikely(b_is_large_sz(sz))) \
pool_free(pool_head_large_buffer, area); \ pool_free(pool_head_large_buffer, area); \
else if (unlikely(b_is_small_sz(sz))) \
pool_free(pool_head_small_buffer, area); \
else if (th_ctx->emergency_bufs_left < global.tune.reserved_bufs) \ else if (th_ctx->emergency_bufs_left < global.tune.reserved_bufs) \
th_ctx->emergency_bufs[th_ctx->emergency_bufs_left++] = area; \ th_ctx->emergency_bufs[th_ctx->emergency_bufs_left++] = area; \
else \ else \
@ -185,6 +200,35 @@ static inline char *__b_get_emergency_buf(void)
__b_free((_buf)); \ __b_free((_buf)); \
} while (0) } while (0)
static inline struct buffer *b_alloc_small(struct buffer *buf)
{
char *area = NULL;
if (!buf->size) {
area = pool_alloc(pool_head_small_buffer);
if (!area)
return NULL;
buf->area = area;
buf->size = global.tune.bufsize_small;
}
return buf;
}
static inline struct buffer *b_alloc_large(struct buffer *buf)
{
char *area = NULL;
if (!buf->size) {
area = pool_alloc(pool_head_large_buffer);
if (!area)
return NULL;
buf->area = area;
buf->size = global.tune.bufsize_large;
}
return buf;
}
/* Offer one or multiple buffer currently belonging to target <from> to whoever /* Offer one or multiple buffer currently belonging to target <from> to whoever
* needs one. Any pointer is valid for <from>, including NULL. Its purpose is * needs one. Any pointer is valid for <from>, including NULL. Its purpose is
* to avoid passing a buffer to oneself in case of failed allocations (e.g. * to avoid passing a buffer to oneself in case of failed allocations (e.g.

View file

@ -28,7 +28,9 @@
#include <haproxy/stream-t.h> #include <haproxy/stream-t.h>
extern const char *trace_flt_id; extern const char *trace_flt_id;
extern const char *http_comp_flt_id; extern const char *http_comp_req_flt_id;
extern const char *http_comp_res_flt_id;
extern const char *cache_store_flt_id; extern const char *cache_store_flt_id;
extern const char *spoe_filter_id; extern const char *spoe_filter_id;
extern const char *fcgi_flt_id; extern const char *fcgi_flt_id;

View file

@ -403,6 +403,25 @@ static inline uint swrate_add_scaled_opportunistic(uint *sum, uint n, uint v, ui
return new_sum; return new_sum;
} }
/* Like swrate_add() except that if <v> is beyond the current average, the
* average is replaced by the peak. This is essentially used to measure peak
* loads in the scheduler, reason why it is provided as a local variant that
* does not involve atomic operations.
*/
static inline uint swrate_add_peak_local(uint *sum, uint n, uint v)
{
uint old_sum, new_sum;
old_sum = *sum;
if (v * n > old_sum)
new_sum = v * n;
else
new_sum = old_sum - (old_sum + n - 1) / n + v;
*sum = new_sum;
return new_sum;
}
/* Returns the average sample value for the sum <sum> over a sliding window of /* Returns the average sample value for the sum <sum> over a sliding window of
* <n> samples. Better if <n> is a power of two. It must be the same <n> as the * <n> samples. Better if <n> is a power of two. It must be the same <n> as the
* one used above in all additions. * one used above in all additions.

View file

@ -79,13 +79,14 @@
#define GTUNE_DISABLE_H2_WEBSOCKET (1<<21) #define GTUNE_DISABLE_H2_WEBSOCKET (1<<21)
#define GTUNE_DISABLE_ACTIVE_CLOSE (1<<22) #define GTUNE_DISABLE_ACTIVE_CLOSE (1<<22)
#define GTUNE_QUICK_EXIT (1<<23) #define GTUNE_QUICK_EXIT (1<<23)
/* (1<<24) unused */ #define GTUNE_COLLECT_LIBS (1<<24)
/* (1<<25) unused */ /* (1<<25) unused */
#define GTUNE_USE_FAST_FWD (1<<26) #define GTUNE_USE_FAST_FWD (1<<26)
#define GTUNE_LISTENER_MQ_FAIR (1<<27) #define GTUNE_LISTENER_MQ_FAIR (1<<27)
#define GTUNE_LISTENER_MQ_OPT (1<<28) #define GTUNE_LISTENER_MQ_OPT (1<<28)
#define GTUNE_LISTENER_MQ_ANY (GTUNE_LISTENER_MQ_FAIR | GTUNE_LISTENER_MQ_OPT) #define GTUNE_LISTENER_MQ_ANY (GTUNE_LISTENER_MQ_FAIR | GTUNE_LISTENER_MQ_OPT)
#define GTUNE_NO_KTLS (1<<29) #define GTUNE_NO_KTLS (1<<29)
#define GTUNE_NO_MAX_COUNTER (1<<30)
/* subsystem-specific debugging options for tune.debug */ /* subsystem-specific debugging options for tune.debug */
#define GDBG_CPU_AFFINITY (1U<< 0) #define GDBG_CPU_AFFINITY (1U<< 0)

View file

@ -58,6 +58,10 @@ extern int devnullfd;
extern int fileless_mode; extern int fileless_mode;
extern struct cfgfile fileless_cfg; extern struct cfgfile fileless_cfg;
/* storage for collected libs */
extern void *lib_storage;
extern size_t lib_size;
struct proxy; struct proxy;
struct server; struct server;
int main(int argc, char **argv); int main(int argc, char **argv);

View file

@ -99,7 +99,7 @@ enum h1m_state {
#define H1_MF_TE_CHUNKED 0x00010000 // T-E "chunked" #define H1_MF_TE_CHUNKED 0x00010000 // T-E "chunked"
#define H1_MF_TE_OTHER 0x00020000 // T-E other than supported ones found (only "chunked" is supported for now) #define H1_MF_TE_OTHER 0x00020000 // T-E other than supported ones found (only "chunked" is supported for now)
#define H1_MF_UPG_H2C 0x00040000 // "h2c" or "h2" used as upgrade token #define H1_MF_UPG_H2C 0x00040000 // "h2c" or "h2" used as upgrade token
#define H1_MF_NOT_HTTP 0x00080000 // Not an HTTP message (e.g "RTSP", only possible if invalid message are accepted)
/* Mask to use to reset H1M flags when we restart headers parsing. /* Mask to use to reset H1M flags when we restart headers parsing.
* *
* WARNING: Don't forget to update it if a new flag must be preserved when * WARNING: Don't forget to update it if a new flag must be preserved when

View file

@ -222,6 +222,7 @@ struct hlua_proxy_list {
}; };
struct hlua_proxy_list_iterator_context { struct hlua_proxy_list_iterator_context {
struct watcher px_watch; /* watcher to automatically update next pointer on backend deletion */
struct proxy *next; struct proxy *next;
char capabilities; char capabilities;
}; };

View file

@ -5,7 +5,6 @@
#include <haproxy/hstream-t.h> #include <haproxy/hstream-t.h>
struct task *sc_hstream_io_cb(struct task *t, void *ctx, unsigned int state); struct task *sc_hstream_io_cb(struct task *t, void *ctx, unsigned int state);
int hstream_wake(struct stconn *sc);
void hstream_shutdown(struct stconn *sc); void hstream_shutdown(struct stconn *sc);
void *hstream_new(struct session *sess, struct stconn *sc, struct buffer *input); void *hstream_new(struct session *sess, struct stconn *sc, struct buffer *input);

View file

@ -228,7 +228,8 @@ enum h1_state {
*/ */
struct http_msg { struct http_msg {
enum h1_state msg_state; /* where we are in the current message parsing */ enum h1_state msg_state; /* where we are in the current message parsing */
/* 3 bytes unused here */ unsigned char vsn; /* HTTP version, 4 bits per digit */
/* 2 bytes unused here */
unsigned int flags; /* flags describing the message (HTTP version, ...) */ unsigned int flags; /* flags describing the message (HTTP version, ...) */
struct channel *chn; /* pointer to the channel transporting the message */ struct channel *chn; /* pointer to the channel transporting the message */
}; };

View file

@ -93,4 +93,22 @@ struct http_errors {
struct list list; /* http-errors list */ struct list list; /* http-errors list */
}; };
/* Indicates the keyword origin of an http-error definition. This is used in
* <conf_errors> type to indicate which part of the internal union should be
* manipulated.
*/
enum http_err_directive {
HTTP_ERR_DIRECTIVE_SECTION = 0, /* "errorfiles" keyword referencing a http-errors section */
HTTP_ERR_DIRECTIVE_INLINE, /* "errorfile" keyword with inline error definition */
};
/* Used with "errorfiles" directives. It indicates for each known HTTP error
* status codes if they are defined in the target http-errors section.
*/
enum http_err_import {
HTTP_ERR_IMPORT_NO = 0,
HTTP_ERR_IMPORT_IMPLICIT, /* import every errcode defined in a section */
HTTP_ERR_IMPORT_EXPLICIT, /* import a specific errcode from a section */
};
#endif /* _HAPROXY_HTTP_HTX_T_H */ #endif /* _HAPROXY_HTTP_HTX_T_H */

View file

@ -78,6 +78,7 @@ struct buffer *http_load_errorfile(const char *file, char **errmsg);
struct buffer *http_load_errormsg(const char *key, const struct ist msg, char **errmsg); struct buffer *http_load_errormsg(const char *key, const struct ist msg, char **errmsg);
struct buffer *http_parse_errorfile(int status, const char *file, char **errmsg); struct buffer *http_parse_errorfile(int status, const char *file, char **errmsg);
struct buffer *http_parse_errorloc(int errloc, int status, const char *url, char **errmsg); struct buffer *http_parse_errorloc(int errloc, int status, const char *url, char **errmsg);
int proxy_check_http_errors(struct proxy *px);
int proxy_dup_default_conf_errors(struct proxy *curpx, const struct proxy *defpx, char **errmsg); int proxy_dup_default_conf_errors(struct proxy *curpx, const struct proxy *defpx, char **errmsg);
void proxy_release_conf_errors(struct proxy *px); void proxy_release_conf_errors(struct proxy *px);

View file

@ -141,6 +141,7 @@
#define HTX_SL_F_NORMALIZED_URI 0x00000800 /* The received URI is normalized (an implicit absolute-uri form) */ #define HTX_SL_F_NORMALIZED_URI 0x00000800 /* The received URI is normalized (an implicit absolute-uri form) */
#define HTX_SL_F_CONN_UPG 0x00001000 /* The message contains "connection: upgrade" header */ #define HTX_SL_F_CONN_UPG 0x00001000 /* The message contains "connection: upgrade" header */
#define HTX_SL_F_BODYLESS_RESP 0x00002000 /* The response to this message is bodyloess (only for reqyest) */ #define HTX_SL_F_BODYLESS_RESP 0x00002000 /* The response to this message is bodyloess (only for reqyest) */
#define HTX_SL_F_NOT_HTTP 0x00004000 /* Not an HTTP message (e.g "RTSP", only possible if invalid message are accepted) */
/* This function is used to report flags in debugging tools. Please reflect /* This function is used to report flags in debugging tools. Please reflect
* below any single-bit flag addition above in the same order via the * below any single-bit flag addition above in the same order via the

View file

@ -37,6 +37,7 @@ struct htx_blk *htx_add_blk(struct htx *htx, enum htx_blk_type type, uint32_t bl
struct htx_blk *htx_remove_blk(struct htx *htx, struct htx_blk *blk); struct htx_blk *htx_remove_blk(struct htx *htx, struct htx_blk *blk);
struct htx_ret htx_find_offset(struct htx *htx, uint32_t offset); struct htx_ret htx_find_offset(struct htx *htx, uint32_t offset);
void htx_truncate(struct htx *htx, uint32_t offset); void htx_truncate(struct htx *htx, uint32_t offset);
void htx_truncate_blk(struct htx *htx, struct htx_blk *blk);
struct htx_ret htx_drain(struct htx *htx, uint32_t max); struct htx_ret htx_drain(struct htx *htx, uint32_t max);
struct htx_blk *htx_replace_blk_value(struct htx *htx, struct htx_blk *blk, struct htx_blk *htx_replace_blk_value(struct htx *htx, struct htx_blk *blk,
@ -56,6 +57,16 @@ size_t htx_add_data(struct htx *htx, const struct ist data);
struct htx_blk *htx_add_last_data(struct htx *htx, struct ist data); struct htx_blk *htx_add_last_data(struct htx *htx, struct ist data);
void htx_move_blk_before(struct htx *htx, struct htx_blk **blk, struct htx_blk **ref); void htx_move_blk_before(struct htx *htx, struct htx_blk **blk, struct htx_blk **ref);
int htx_append_msg(struct htx *dst, const struct htx *src); int htx_append_msg(struct htx *dst, const struct htx *src);
struct buffer *htx_move_to_small_buffer(struct buffer *dst, struct buffer *src);
struct buffer *htx_move_to_large_buffer(struct buffer *dst, struct buffer *src);
struct buffer *htx_copy_to_small_buffer(struct buffer *dst, struct buffer *src);
struct buffer *htx_copy_to_large_buffer(struct buffer *dst, struct buffer *src);
#define HTX_XFER_DEFAULT 0x00000000 /* Default XFER: no partial xfer / remove blocks from source */
#define HTX_XFER_KEEP_SRC_BLKS 0x00000001 /* Don't remove xfer blocks from source messages during xfer */
#define HTX_XFER_PARTIAL_HDRS_COPY 0x00000002 /* Allow partial copy of headers and trailers part */
#define HTX_XFER_HDRS_ONLY 0x00000003 /* Only Transfert header blocks (start-line, header and EOH) */
size_t htx_xfer(struct htx *dst, struct htx *src, size_t count, unsigned int flags);
/* Functions and macros to get parts of the start-line or length of these /* Functions and macros to get parts of the start-line or length of these
* parts. Request and response start-lines are both composed of 3 parts. * parts. Request and response start-lines are both composed of 3 parts.
@ -98,6 +109,11 @@ static inline struct ist htx_sl_p3(const struct htx_sl *sl)
return ist2(HTX_SL_P3_PTR(sl), HTX_SL_P3_LEN(sl)); return ist2(HTX_SL_P3_PTR(sl), HTX_SL_P3_LEN(sl));
} }
static inline struct ist htx_sl_vsn(const struct htx_sl *sl)
{
return ((sl->flags & HTX_SL_F_IS_RESP) ? htx_sl_p1(sl) : htx_sl_p3(sl));
}
static inline struct ist htx_sl_req_meth(const struct htx_sl *sl) static inline struct ist htx_sl_req_meth(const struct htx_sl *sl)
{ {
return htx_sl_p1(sl); return htx_sl_p1(sl);

View file

@ -20,6 +20,11 @@ extern struct list server_deinit_list;
extern struct list per_thread_free_list; extern struct list per_thread_free_list;
extern struct list per_thread_deinit_list; extern struct list per_thread_deinit_list;
/* initcall caller location */
extern const struct initcall *caller_initcall;
extern const char *caller_file;
extern int caller_line;
void hap_register_pre_check(int (*fct)()); void hap_register_pre_check(int (*fct)());
void hap_register_post_check(int (*fct)()); void hap_register_post_check(int (*fct)());
void hap_register_post_proxy_check(int (*fct)(struct proxy *)); void hap_register_post_proxy_check(int (*fct)(struct proxy *));

View file

@ -77,6 +77,8 @@ struct initcall {
void *arg1; void *arg1;
void *arg2; void *arg2;
void *arg3; void *arg3;
const char *loc_file; /* file where the call is declared, or NULL */
int loc_line; /* line where the call is declared, or NULL */
#if defined(USE_OBSOLETE_LINKER) #if defined(USE_OBSOLETE_LINKER)
void *next; void *next;
#endif #endif
@ -107,6 +109,8 @@ struct initcall {
.arg1 = (void *)(a1), \ .arg1 = (void *)(a1), \
.arg2 = (void *)(a2), \ .arg2 = (void *)(a2), \
.arg3 = (void *)(a3), \ .arg3 = (void *)(a3), \
.loc_file = __FILE__, \
.loc_line = linenum, \
} : NULL } : NULL
@ -131,6 +135,8 @@ __attribute__((constructor)) static void __initcb_##linenum() \
.arg1 = (void *)(a1), \ .arg1 = (void *)(a1), \
.arg2 = (void *)(a2), \ .arg2 = (void *)(a2), \
.arg3 = (void *)(a3), \ .arg3 = (void *)(a3), \
.loc_file = __FILE__, \
.loc_line = linenum, \
}; \ }; \
if (stg < STG_SIZE) { \ if (stg < STG_SIZE) { \
entry.next = __initstg[stg]; \ entry.next = __initstg[stg]; \
@ -229,8 +235,15 @@ extern struct initcall *__initstg[STG_SIZE];
const struct initcall **ptr; \ const struct initcall **ptr; \
if (stg >= STG_SIZE) \ if (stg >= STG_SIZE) \
break; \ break; \
FOREACH_INITCALL(ptr, stg) \ FOREACH_INITCALL(ptr, stg) { \
caller_initcall = *ptr; \
caller_file = (*ptr)->loc_file; \
caller_line = (*ptr)->loc_line; \
(*ptr)->fct((*ptr)->arg1, (*ptr)->arg2, (*ptr)->arg3); \ (*ptr)->fct((*ptr)->arg1, (*ptr)->arg2, (*ptr)->arg3); \
caller_initcall = NULL; \
caller_file = NULL; \
caller_line = 0; \
} \
} while (0) } while (0)
#else // USE_OBSOLETE_LINKER #else // USE_OBSOLETE_LINKER
@ -243,8 +256,15 @@ extern struct initcall *__initstg[STG_SIZE];
const struct initcall *ptr; \ const struct initcall *ptr; \
if (stg >= STG_SIZE) \ if (stg >= STG_SIZE) \
break; \ break; \
FOREACH_INITCALL(ptr, stg) \ FOREACH_INITCALL(ptr, stg) { \
caller_initcall = ptr; \
caller_file = (ptr)->loc_file; \
caller_line = (ptr)->loc_line; \
(ptr)->fct((ptr)->arg1, (ptr)->arg2, (ptr)->arg3); \ (ptr)->fct((ptr)->arg1, (ptr)->arg2, (ptr)->arg3); \
caller_initcall = NULL; \
caller_file = NULL; \
caller_line = 0; \
} \
} while (0) } while (0)
#endif // USE_OBSOLETE_LINKER #endif // USE_OBSOLETE_LINKER

View file

@ -27,7 +27,7 @@
#ifdef USE_OPENSSL #ifdef USE_OPENSSL
enum jwt_alg jwt_parse_alg(const char *alg_str, unsigned int alg_len); enum jwt_alg jwt_parse_alg(const char *alg_str, unsigned int alg_len);
int jwt_tokenize(const struct buffer *jwt, struct jwt_item *items, unsigned int *item_num); int jwt_tokenize(const struct buffer *jwt, struct jwt_item *items, unsigned int item_num);
int jwt_tree_load_cert(char *path, int pathlen, int tryload_cert, const char *file, int line, char **err); int jwt_tree_load_cert(char *path, int pathlen, int tryload_cert, const char *file, int line, char **err);
enum jwt_vrfy_status jwt_verify(const struct buffer *token, const struct buffer *alg, enum jwt_vrfy_status jwt_verify(const struct buffer *token, const struct buffer *alg,

View file

@ -28,13 +28,13 @@
#include <import/ebtree-t.h> #include <import/ebtree-t.h>
#include <haproxy/api-t.h> #include <haproxy/api-t.h>
#include <haproxy/counters-t.h>
#include <haproxy/guid-t.h> #include <haproxy/guid-t.h>
#include <haproxy/obj_type-t.h> #include <haproxy/obj_type-t.h>
#include <haproxy/quic_cc-t.h> #include <haproxy/quic_cc-t.h>
#include <haproxy/quic_sock-t.h> #include <haproxy/quic_sock-t.h>
#include <haproxy/quic_tp-t.h> #include <haproxy/quic_tp-t.h>
#include <haproxy/receiver-t.h> #include <haproxy/receiver-t.h>
#include <haproxy/stats-t.h>
#include <haproxy/thread.h> #include <haproxy/thread.h>
/* Some pointer types reference below */ /* Some pointer types reference below */
@ -263,6 +263,7 @@ struct listener {
struct li_per_thread *per_thr; /* per-thread fields (one per thread in the group) */ struct li_per_thread *per_thr; /* per-thread fields (one per thread in the group) */
char *extra_counters_storage; /* storage for extra_counters */
EXTRA_COUNTERS(extra_counters); EXTRA_COUNTERS(extra_counters);
}; };

View file

@ -200,6 +200,8 @@ enum qcc_app_ops_close_side {
/* QUIC application layer operations */ /* QUIC application layer operations */
struct qcc_app_ops { struct qcc_app_ops {
const char *alpn;
/* Initialize <qcc> connection app context. */ /* Initialize <qcc> connection app context. */
int (*init)(struct qcc *qcc); int (*init)(struct qcc *qcc);
/* Finish connection initialization if prelude required. */ /* Finish connection initialization if prelude required. */
@ -232,6 +234,9 @@ struct qcc_app_ops {
void (*inc_err_cnt)(void *ctx, int err_code); void (*inc_err_cnt)(void *ctx, int err_code);
/* Set QCC error code as suspicious activity has been detected. */ /* Set QCC error code as suspicious activity has been detected. */
void (*report_susp)(void *ctx); void (*report_susp)(void *ctx);
/* Free function to close a stream after MUX layer shutdown. */
int (*strm_reject)(struct list *out, uint64_t id);
}; };
#endif /* USE_QUIC */ #endif /* USE_QUIC */

View file

@ -12,6 +12,9 @@
#include <haproxy/mux_quic-t.h> #include <haproxy/mux_quic-t.h>
#include <haproxy/stconn.h> #include <haproxy/stconn.h>
#include <haproxy/h3.h>
#include <haproxy/hq_interop.h>
#define qcc_report_glitch(qcc, inc, ...) ({ \ #define qcc_report_glitch(qcc, inc, ...) ({ \
COUNT_GLITCH(__VA_ARGS__); \ COUNT_GLITCH(__VA_ARGS__); \
_qcc_report_glitch(qcc, inc); \ _qcc_report_glitch(qcc, inc); \
@ -88,7 +91,7 @@ static inline char *qcs_st_to_str(enum qcs_state st)
} }
} }
int qcc_install_app_ops(struct qcc *qcc, const struct qcc_app_ops *app_ops); int qcc_install_app_ops(struct qcc *qcc);
/* Register <qcs> stream for http-request timeout. If the stream is not yet /* Register <qcs> stream for http-request timeout. If the stream is not yet
* attached in the configured delay, qcc timeout task will be triggered. This * attached in the configured delay, qcc timeout task will be triggered. This
@ -115,6 +118,16 @@ void qcc_show_quic(struct qcc *qcc);
void qcc_wakeup(struct qcc *qcc); void qcc_wakeup(struct qcc *qcc);
static inline const struct qcc_app_ops *quic_alpn_to_app_ops(const char *alpn, int alpn_len)
{
if (alpn_len >= 2 && memcmp(alpn, "h3", 2) == 0)
return &h3_ops;
else if (alpn_len >= 10 && memcmp(alpn, "hq-interop", 10) == 0)
return &hq_interop_ops;
return NULL;
}
#endif /* USE_QUIC */ #endif /* USE_QUIC */
#endif /* _HAPROXY_MUX_QUIC_H */ #endif /* _HAPROXY_MUX_QUIC_H */

View file

@ -394,6 +394,12 @@ static inline unsigned long ERR_peek_error_func(const char **func)
#define __OPENSSL_110_CONST__ #define __OPENSSL_110_CONST__
#endif #endif
#if (HA_OPENSSL_VERSION_NUMBER >= 0x40000000L) && (!defined(USE_OPENSSL_WOLFSSL))
#define __X509_NAME_CONST__ const
#else
#define __X509_NAME_CONST__
#endif
/* ERR_remove_state() was deprecated in 1.0.0 in favor of /* ERR_remove_state() was deprecated in 1.0.0 in favor of
* ERR_remove_thread_state(), which was in turn deprecated in * ERR_remove_thread_state(), which was in turn deprecated in
* 1.1.0 and does nothing anymore. Let's simply silently kill * 1.1.0 and does nothing anymore. Let's simply silently kill

View file

@ -124,6 +124,12 @@ static inline int real_family(int ss_family)
return fam ? fam->real_family : AF_UNSPEC; return fam ? fam->real_family : AF_UNSPEC;
} }
static inline int proto_is_quic(const struct protocol *proto)
{
return (proto->proto_type == PROTO_TYPE_DGRAM &&
proto->xprt_type == PROTO_TYPE_STREAM);
}
#endif /* _HAPROXY_PROTOCOL_H */ #endif /* _HAPROXY_PROTOCOL_H */
/* /*

View file

@ -38,7 +38,6 @@
#include <haproxy/obj_type-t.h> #include <haproxy/obj_type-t.h>
#include <haproxy/queue-t.h> #include <haproxy/queue-t.h>
#include <haproxy/server-t.h> #include <haproxy/server-t.h>
#include <haproxy/stats-t.h>
#include <haproxy/tcpcheck-t.h> #include <haproxy/tcpcheck-t.h>
#include <haproxy/thread-t.h> #include <haproxy/thread-t.h>
#include <haproxy/tools-t.h> #include <haproxy/tools-t.h>
@ -157,14 +156,17 @@ enum PR_SRV_STATE_FILE {
#define PR_O2_RSTRICT_REQ_HDR_NAMES_NOOP 0x01000000 /* preserve request header names containing chars outside of [0-9a-zA-Z-] charset */ #define PR_O2_RSTRICT_REQ_HDR_NAMES_NOOP 0x01000000 /* preserve request header names containing chars outside of [0-9a-zA-Z-] charset */
#define PR_O2_RSTRICT_REQ_HDR_NAMES_MASK 0x01c00000 /* mask for restrict-http-header-names option */ #define PR_O2_RSTRICT_REQ_HDR_NAMES_MASK 0x01c00000 /* mask for restrict-http-header-names option */
/* unused : 0x02000000 ... 0x08000000 */
/* server health checks */ /* server health checks */
#define PR_O2_CHK_NONE 0x00000000 /* no L7 health checks configured (TCP by default) */ #define PR_O2_CHK_NONE 0x00000000 /* no L7 health checks configured (TCP by default) */
#define PR_O2_TCPCHK_CHK 0x90000000 /* use TCPCHK check for server health */ #define PR_O2_TCPCHK_CHK 0x02000000 /* use TCPCHK check for server health */
#define PR_O2_EXT_CHK 0xA0000000 /* use external command for server health */ #define PR_O2_EXT_CHK 0x04000000 /* use external command for server health */
/* unused: 0xB0000000 to 0xF000000, reserved for health checks */ #define PR_O2_CHK_ANY 0x06000000 /* Mask to cover any check */
#define PR_O2_CHK_ANY 0xF0000000 /* Mask to cover any check */
#define PR_O2_USE_SBUF_QUEUE 0x08000000 /* use small buffer for request when stream are queued*/
#define PR_O2_USE_SBUF_L7_RETRY 0x10000000 /* use small buffer for request when L7 retires are enabled */
#define PR_O2_USE_SBUF_CHECK 0x20000000 /* use small buffer for request's healthchecks */
#define PR_O2_USE_SBUF_ALL 0x38000000 /* all flags for use-large-buffer option */
/* unused : 0x40000000 ... 0x80000000 */
/* end of proxy->options2 */ /* end of proxy->options2 */
/* bits for proxy->options3 */ /* bits for proxy->options3 */
@ -240,14 +242,16 @@ enum PR_SRV_STATE_FILE {
#define PR_RE_JUNK_REQUEST 0x00020000 /* We received an incomplete or garbage response */ #define PR_RE_JUNK_REQUEST 0x00020000 /* We received an incomplete or garbage response */
/* Proxy flags */ /* Proxy flags */
#define PR_FL_DISABLED 0x01 /* The proxy was disabled in the configuration (not at runtime) */ #define PR_FL_DISABLED 0x00000001 /* The proxy was disabled in the configuration (not at runtime) */
#define PR_FL_STOPPED 0x02 /* The proxy was stopped */ #define PR_FL_STOPPED 0x00000002 /* The proxy was stopped */
#define PR_FL_DEF_EXPLICIT_MODE 0x04 /* Proxy mode is explicitely defined - only used for defaults instance */ #define PR_FL_DEF_EXPLICIT_MODE 0x00000004 /* Proxy mode is explicitely defined - only used for defaults instance */
#define PR_FL_EXPLICIT_REF 0x08 /* The default proxy is explicitly referenced by another proxy */ #define PR_FL_EXPLICIT_REF 0x00000008 /* The default proxy is explicitly referenced by another proxy */
#define PR_FL_IMPLICIT_REF 0x10 /* The default proxy is implicitly referenced by another proxy */ #define PR_FL_IMPLICIT_REF 0x00000010 /* The default proxy is implicitly referenced by another proxy */
#define PR_FL_PAUSED 0x20 /* The proxy was paused at run time (reversible) */ #define PR_FL_PAUSED 0x00000020 /* The proxy was paused at run time (reversible) */
#define PR_FL_CHECKED 0x40 /* The proxy configuration was fully checked (including postparsing checks) */ #define PR_FL_CHECKED 0x00000040 /* The proxy configuration was fully checked (including postparsing checks) */
#define PR_FL_BE_UNPUBLISHED 0x80 /* The proxy cannot be targetted by content switching rules */ #define PR_FL_BE_UNPUBLISHED 0x00000080 /* The proxy cannot be targetted by content switching rules */
#define PR_FL_DELETED 0x00000100 /* Proxy has been deleted and must be manipulated with care */
#define PR_FL_NON_PURGEABLE 0x00000200 /* Proxy referenced by config elements which prevent its runtime removal. */
struct stream; struct stream;
@ -306,13 +310,16 @@ struct error_snapshot {
struct proxy_per_tgroup { struct proxy_per_tgroup {
struct queue queue; struct queue queue;
struct lbprm_per_tgrp lbprm; struct lbprm_per_tgrp lbprm;
char *extra_counters_fe_storage; /* storage for extra_counters_fe */
char *extra_counters_be_storage; /* storage for extra_counters_be */
} THREAD_ALIGNED(); } THREAD_ALIGNED();
struct proxy { struct proxy {
enum obj_type obj_type; /* object type == OBJ_TYPE_PROXY */ enum obj_type obj_type; /* object type == OBJ_TYPE_PROXY */
char flags; /* bit field PR_FL_* */
enum pr_mode mode; /* mode = PR_MODE_TCP, PR_MODE_HTTP, ... */ enum pr_mode mode; /* mode = PR_MODE_TCP, PR_MODE_HTTP, ... */
char cap; /* supported capabilities (PR_CAP_*) */ char cap; /* supported capabilities (PR_CAP_*) */
/* 1 byte hole */
unsigned int flags; /* bit field PR_FL_* */
int to_log; /* things to be logged (LW_*), special value LW_LOGSTEPS == follow log-steps */ int to_log; /* things to be logged (LW_*), special value LW_LOGSTEPS == follow log-steps */
unsigned long last_change; /* internal use only: last time the proxy state was changed */ unsigned long last_change; /* internal use only: last time the proxy state was changed */
@ -476,7 +483,7 @@ struct proxy {
struct log_steps log_steps; /* bitfield of log origins where log should be generated during request handling */ struct log_steps log_steps; /* bitfield of log origins where log should be generated during request handling */
const char *file_prev; /* file of the previous instance found with the same name, or NULL */ const char *file_prev; /* file of the previous instance found with the same name, or NULL */
int line_prev; /* line of the previous instance found with the same name, or 0 */ int line_prev; /* line of the previous instance found with the same name, or 0 */
unsigned int refcount; /* refcount on this proxy (only used for default proxy for now) */ unsigned int def_ref; /* default proxy only refcount */
} conf; /* config information */ } conf; /* config information */
struct http_ext *http_ext; /* http ext options */ struct http_ext *http_ext; /* http ext options */
struct ceb_root *used_server_addr; /* list of server addresses in use */ struct ceb_root *used_server_addr; /* list of server addresses in use */
@ -505,6 +512,8 @@ struct proxy {
struct list filter_configs; /* list of the filters that are declared on this proxy */ struct list filter_configs; /* list of the filters that are declared on this proxy */
struct guid_node guid; /* GUID global tree node */ struct guid_node guid; /* GUID global tree node */
struct mt_list watcher_list; /* list of elems which currently references this proxy instance (currently only used with backends) */
uint refcount; /* refcount to keep proxy from being deleted during runtime */
EXTRA_COUNTERS(extra_counters_fe); EXTRA_COUNTERS(extra_counters_fe);
EXTRA_COUNTERS(extra_counters_be); EXTRA_COUNTERS(extra_counters_be);

View file

@ -26,6 +26,7 @@
#include <haproxy/api.h> #include <haproxy/api.h>
#include <haproxy/applet-t.h> #include <haproxy/applet-t.h>
#include <haproxy/counters.h>
#include <haproxy/freq_ctr.h> #include <haproxy/freq_ctr.h>
#include <haproxy/list.h> #include <haproxy/list.h>
#include <haproxy/listener-t.h> #include <haproxy/listener-t.h>
@ -58,7 +59,7 @@ void stop_proxy(struct proxy *p);
int stream_set_backend(struct stream *s, struct proxy *be); int stream_set_backend(struct stream *s, struct proxy *be);
void deinit_proxy(struct proxy *p); void deinit_proxy(struct proxy *p);
void free_proxy(struct proxy *p); void proxy_drop(struct proxy *p);
const char *proxy_cap_str(int cap); const char *proxy_cap_str(int cap);
const char *proxy_mode_str(int mode); const char *proxy_mode_str(int mode);
enum pr_mode str_to_proxy_mode(const char *mode); enum pr_mode str_to_proxy_mode(const char *mode);
@ -82,6 +83,7 @@ void proxy_unref_defaults(struct proxy *px);
int setup_new_proxy(struct proxy *px, const char *name, unsigned int cap, char **errmsg); int setup_new_proxy(struct proxy *px, const char *name, unsigned int cap, char **errmsg);
struct proxy *alloc_new_proxy(const char *name, unsigned int cap, struct proxy *alloc_new_proxy(const char *name, unsigned int cap,
char **errmsg); char **errmsg);
void proxy_take(struct proxy *px);
struct proxy *parse_new_proxy(const char *name, unsigned int cap, struct proxy *parse_new_proxy(const char *name, unsigned int cap,
const char *file, int linenum, const char *file, int linenum,
const struct proxy *defproxy); const struct proxy *defproxy);
@ -101,6 +103,8 @@ void free_server_rules(struct list *srules);
int proxy_init_per_thr(struct proxy *px); int proxy_init_per_thr(struct proxy *px);
int proxy_finalize(struct proxy *px, int *err_code); int proxy_finalize(struct proxy *px, int *err_code);
int be_check_for_deletion(const char *bename, struct proxy **pb, const char **pm);
/* /*
* This function returns a string containing the type of the proxy in a format * This function returns a string containing the type of the proxy in a format
* suitable for error messages, from its capabilities. * suitable for error messages, from its capabilities.
@ -178,7 +182,7 @@ static inline void proxy_inc_fe_conn_ctr(struct listener *l, struct proxy *fe)
} }
if (l && l->counters && l->counters->shared.tg) if (l && l->counters && l->counters->shared.tg)
_HA_ATOMIC_INC(&l->counters->shared.tg[tgid - 1]->cum_conn); _HA_ATOMIC_INC(&l->counters->shared.tg[tgid - 1]->cum_conn);
HA_ATOMIC_UPDATE_MAX(&fe->fe_counters.cps_max, COUNTERS_UPDATE_MAX(&fe->fe_counters.cps_max,
update_freq_ctr(&fe->fe_counters._conn_per_sec, 1)); update_freq_ctr(&fe->fe_counters._conn_per_sec, 1));
} }
@ -191,7 +195,7 @@ static inline void proxy_inc_fe_sess_ctr(struct listener *l, struct proxy *fe)
} }
if (l && l->counters && l->counters->shared.tg) if (l && l->counters && l->counters->shared.tg)
_HA_ATOMIC_INC(&l->counters->shared.tg[tgid - 1]->cum_sess); _HA_ATOMIC_INC(&l->counters->shared.tg[tgid - 1]->cum_sess);
HA_ATOMIC_UPDATE_MAX(&fe->fe_counters.sps_max, COUNTERS_UPDATE_MAX(&fe->fe_counters.sps_max,
update_freq_ctr(&fe->fe_counters._sess_per_sec, 1)); update_freq_ctr(&fe->fe_counters._sess_per_sec, 1));
} }
@ -218,7 +222,7 @@ static inline void proxy_inc_be_ctr(struct proxy *be)
_HA_ATOMIC_INC(&be->be_counters.shared.tg[tgid - 1]->cum_sess); _HA_ATOMIC_INC(&be->be_counters.shared.tg[tgid - 1]->cum_sess);
update_freq_ctr(&be->be_counters.shared.tg[tgid - 1]->sess_per_sec, 1); update_freq_ctr(&be->be_counters.shared.tg[tgid - 1]->sess_per_sec, 1);
} }
HA_ATOMIC_UPDATE_MAX(&be->be_counters.sps_max, COUNTERS_UPDATE_MAX(&be->be_counters.sps_max,
update_freq_ctr(&be->be_counters._sess_per_sec, 1)); update_freq_ctr(&be->be_counters._sess_per_sec, 1));
} }
@ -238,7 +242,7 @@ static inline void proxy_inc_fe_req_ctr(struct listener *l, struct proxy *fe,
} }
if (l && l->counters && l->counters->shared.tg) if (l && l->counters && l->counters->shared.tg)
_HA_ATOMIC_INC(&l->counters->shared.tg[tgid - 1]->p.http.cum_req[http_ver]); _HA_ATOMIC_INC(&l->counters->shared.tg[tgid - 1]->p.http.cum_req[http_ver]);
HA_ATOMIC_UPDATE_MAX(&fe->fe_counters.p.http.rps_max, COUNTERS_UPDATE_MAX(&fe->fe_counters.p.http.rps_max,
update_freq_ctr(&fe->fe_counters.p.http._req_per_sec, 1)); update_freq_ctr(&fe->fe_counters.p.http._req_per_sec, 1));
} }

View file

@ -38,7 +38,6 @@
#include <haproxy/quic_cc-t.h> #include <haproxy/quic_cc-t.h>
#include <haproxy/quic_frame-t.h> #include <haproxy/quic_frame-t.h>
#include <haproxy/quic_openssl_compat-t.h> #include <haproxy/quic_openssl_compat-t.h>
#include <haproxy/quic_stats-t.h>
#include <haproxy/quic_tls-t.h> #include <haproxy/quic_tls-t.h>
#include <haproxy/quic_tp-t.h> #include <haproxy/quic_tp-t.h>
#include <haproxy/show_flags-t.h> #include <haproxy/show_flags-t.h>
@ -401,6 +400,8 @@ struct quic_conn {
struct eb_root streams_by_id; /* qc_stream_desc tree */ struct eb_root streams_by_id; /* qc_stream_desc tree */
const char *alpn;
/* MUX */ /* MUX */
struct qcc *qcc; struct qcc *qcc;
struct task *timer_task; struct task *timer_task;
@ -409,7 +410,9 @@ struct quic_conn {
/* Handshake expiration date */ /* Handshake expiration date */
unsigned int hs_expire; unsigned int hs_expire;
const struct qcc_app_ops *app_ops; /* Callback to close any stream after MUX closure - set by the MUX itself */
int (*strm_reject)(struct list *out, uint64_t stream_id);
/* Proxy counters */ /* Proxy counters */
struct quic_counters *prx_counters; struct quic_counters *prx_counters;

View file

@ -30,6 +30,7 @@
#include <import/eb64tree.h> #include <import/eb64tree.h>
#include <import/ebmbtree.h> #include <import/ebmbtree.h>
#include <haproxy/counters.h>
#include <haproxy/chunk.h> #include <haproxy/chunk.h>
#include <haproxy/dynbuf.h> #include <haproxy/dynbuf.h>
#include <haproxy/ncbmbuf.h> #include <haproxy/ncbmbuf.h>
@ -83,7 +84,7 @@ void qc_check_close_on_released_mux(struct quic_conn *qc);
int quic_stateless_reset_token_cpy(unsigned char *pos, size_t len, int quic_stateless_reset_token_cpy(unsigned char *pos, size_t len,
const unsigned char *salt, size_t saltlen); const unsigned char *salt, size_t saltlen);
int quic_reuse_srv_params(struct quic_conn *qc, int quic_reuse_srv_params(struct quic_conn *qc,
const unsigned char *alpn, const char *alpn,
const struct quic_early_transport_params *etps); const struct quic_early_transport_params *etps);
/* Returns true if <qc> is used on the backed side (as a client). */ /* Returns true if <qc> is used on the backed side (as a client). */
@ -193,13 +194,17 @@ static inline void *qc_counters(enum obj_type *o, const struct stats_module *m)
p = l ? l->bind_conf->frontend : p = l ? l->bind_conf->frontend :
s ? s->proxy : NULL; s ? s->proxy : NULL;
return p ? EXTRA_COUNTERS_GET(p->extra_counters_fe, m) : NULL; if (l && p)
return EXTRA_COUNTERS_GET(p->extra_counters_fe, m);
else if (s && p)
return EXTRA_COUNTERS_GET(p->extra_counters_be, m);
return NULL;
} }
void chunk_frm_appendf(struct buffer *buf, const struct quic_frame *frm); void chunk_frm_appendf(struct buffer *buf, const struct quic_frame *frm);
void quic_set_connection_close(struct quic_conn *qc, const struct quic_err err); void quic_set_connection_close(struct quic_conn *qc, const struct quic_err err);
void quic_set_tls_alert(struct quic_conn *qc, int alert); void quic_set_tls_alert(struct quic_conn *qc, int alert);
int quic_set_app_ops(struct quic_conn *qc, const unsigned char *alpn, size_t alpn_len); int qc_register_alpn(struct quic_conn *qc, const char *alpn, int alpn_len);
int qc_check_dcid(struct quic_conn *qc, unsigned char *dcid, size_t dcid_len); int qc_check_dcid(struct quic_conn *qc, unsigned char *dcid, size_t dcid_len);
void qc_notify_err(struct quic_conn *qc); void qc_notify_err(struct quic_conn *qc);

View file

@ -6,8 +6,6 @@
#error "Must define USE_OPENSSL" #error "Must define USE_OPENSSL"
#endif #endif
extern struct stats_module quic_stats_module;
enum { enum {
QUIC_ST_RXBUF_FULL, QUIC_ST_RXBUF_FULL,
QUIC_ST_DROPPED_PACKET, QUIC_ST_DROPPED_PACKET,
@ -52,6 +50,7 @@ enum {
QUIC_ST_STREAM_DATA_BLOCKED, QUIC_ST_STREAM_DATA_BLOCKED,
QUIC_ST_STREAMS_BLOCKED_BIDI, QUIC_ST_STREAMS_BLOCKED_BIDI,
QUIC_ST_STREAMS_BLOCKED_UNI, QUIC_ST_STREAMS_BLOCKED_UNI,
QUIC_ST_NCBUF_GAP_LIMIT,
QUIC_STATS_COUNT /* must be the last */ QUIC_STATS_COUNT /* must be the last */
}; };
@ -99,6 +98,7 @@ struct quic_counters {
long long stream_data_blocked; /* total number of times STREAM_DATA_BLOCKED frame was received */ long long stream_data_blocked; /* total number of times STREAM_DATA_BLOCKED frame was received */
long long streams_blocked_bidi; /* total number of times STREAMS_BLOCKED_BIDI frame was received */ long long streams_blocked_bidi; /* total number of times STREAMS_BLOCKED_BIDI frame was received */
long long streams_blocked_uni; /* total number of times STREAMS_BLOCKED_UNI frame was received */ long long streams_blocked_uni; /* total number of times STREAMS_BLOCKED_UNI frame was received */
long long ncbuf_gap_limit; /* total number of times we failed to add data to ncbuf due to gap size limit */
}; };
#endif /* USE_QUIC */ #endif /* USE_QUIC */

View file

@ -7,7 +7,9 @@
#endif #endif
#include <haproxy/quic_stats-t.h> #include <haproxy/quic_stats-t.h>
#include <haproxy/stats-t.h>
extern struct stats_module quic_stats_module;
void quic_stats_transp_err_count_inc(struct quic_counters *ctrs, int error_code); void quic_stats_transp_err_count_inc(struct quic_counters *ctrs, int error_code);
#endif /* USE_QUIC */ #endif /* USE_QUIC */

View file

@ -27,7 +27,6 @@
#include <haproxy/connection-t.h> #include <haproxy/connection-t.h>
#include <haproxy/dns-t.h> #include <haproxy/dns-t.h>
#include <haproxy/obj_type-t.h> #include <haproxy/obj_type-t.h>
#include <haproxy/stats-t.h>
#include <haproxy/task-t.h> #include <haproxy/task-t.h>
#include <haproxy/thread.h> #include <haproxy/thread.h>

View file

@ -24,6 +24,7 @@
#define _HAPROXY_SAMPLE_T_H #define _HAPROXY_SAMPLE_T_H
#include <haproxy/api-t.h> #include <haproxy/api-t.h>
#include <haproxy/tinfo-t.h>
#include <haproxy/sample_data-t.h> #include <haproxy/sample_data-t.h>
/* input and output sample types /* input and output sample types
@ -265,6 +266,7 @@ struct sample_conv {
unsigned int in_type; /* expected input sample type */ unsigned int in_type; /* expected input sample type */
unsigned int out_type; /* output sample type */ unsigned int out_type; /* output sample type */
void *private; /* private values. only used by maps and Lua */ void *private; /* private values. only used by maps and Lua */
struct thread_exec_ctx exec_ctx; /* execution context */
}; };
/* sample conversion expression */ /* sample conversion expression */
@ -288,6 +290,7 @@ struct sample_fetch {
unsigned int use; /* fetch source (SMP_USE_*) */ unsigned int use; /* fetch source (SMP_USE_*) */
unsigned int val; /* fetch validity (SMP_VAL_*) */ unsigned int val; /* fetch validity (SMP_VAL_*) */
void *private; /* private values. only used by Lua */ void *private; /* private values. only used by Lua */
struct thread_exec_ctx exec_ctx; /* execution context */
}; };
/* sample expression */ /* sample expression */

View file

@ -36,6 +36,10 @@
void sc_update_rx(struct stconn *sc); void sc_update_rx(struct stconn *sc);
void sc_update_tx(struct stconn *sc); void sc_update_tx(struct stconn *sc);
void sc_abort(struct stconn *sc);
void sc_shutdown(struct stconn *sc);
void sc_chk_rcv(struct stconn *sc);
struct task *sc_conn_io_cb(struct task *t, void *ctx, unsigned int state); struct task *sc_conn_io_cb(struct task *t, void *ctx, unsigned int state);
int sc_conn_sync_recv(struct stconn *sc); int sc_conn_sync_recv(struct stconn *sc);
int sc_conn_sync_send(struct stconn *sc); int sc_conn_sync_send(struct stconn *sc);
@ -360,38 +364,6 @@ static inline int sc_is_recv_allowed(const struct stconn *sc)
return !(sc->flags & (SC_FL_WONT_READ|SC_FL_NEED_BUFF|SC_FL_NEED_ROOM)); return !(sc->flags & (SC_FL_WONT_READ|SC_FL_NEED_BUFF|SC_FL_NEED_ROOM));
} }
/* This is to be used after making some room available in a channel. It will
* return without doing anything if the stream connector's RX path is blocked.
* It will automatically mark the stream connector as busy processing the end
* point in order to avoid useless repeated wakeups.
* It will then call ->chk_rcv() to enable receipt of new data.
*/
static inline void sc_chk_rcv(struct stconn *sc)
{
if (sc_ep_test(sc, SE_FL_APPLET_NEED_CONN) &&
sc_state_in(sc_opposite(sc)->state, SC_SB_RDY|SC_SB_EST|SC_SB_DIS|SC_SB_CLO)) {
sc_ep_clr(sc, SE_FL_APPLET_NEED_CONN);
sc_ep_report_read_activity(sc);
}
if (!sc_is_recv_allowed(sc))
return;
if (!sc_state_in(sc->state, SC_SB_RDY|SC_SB_EST))
return;
sc_ep_set(sc, SE_FL_HAVE_NO_DATA);
if (likely(sc->app_ops->chk_rcv))
sc->app_ops->chk_rcv(sc);
}
/* Calls chk_snd on the endpoint using the data layer */
static inline void sc_chk_snd(struct stconn *sc)
{
if (likely(sc->app_ops->chk_snd))
sc->app_ops->chk_snd(sc);
}
/* Perform a synchronous receive using the right version, depending the endpoing /* Perform a synchronous receive using the right version, depending the endpoing
* is a connection or an applet. * is a connection or an applet.
@ -536,24 +508,10 @@ static inline void sc_schedule_abort(struct stconn *sc)
sc->flags |= SC_FL_ABRT_WANTED; sc->flags |= SC_FL_ABRT_WANTED;
} }
/* Abort the SC and notify the endpoint using the data layer */
static inline void sc_abort(struct stconn *sc)
{
if (likely(sc->app_ops->abort))
sc->app_ops->abort(sc);
}
/* Schedule a shutdown for the SC */ /* Schedule a shutdown for the SC */
static inline void sc_schedule_shutdown(struct stconn *sc) static inline void sc_schedule_shutdown(struct stconn *sc)
{ {
sc->flags |= SC_FL_SHUT_WANTED; sc->flags |= SC_FL_SHUT_WANTED;
} }
/* Shutdown the SC and notify the endpoint using the data layer */
static inline void sc_shutdown(struct stconn *sc)
{
if (likely(sc->app_ops->shutdown))
sc->app_ops->shutdown(sc);
}
#endif /* _HAPROXY_SC_STRM_H */ #endif /* _HAPROXY_SC_STRM_H */

View file

@ -286,6 +286,7 @@ struct srv_per_tgroup {
struct queue queue; /* pending connections */ struct queue queue; /* pending connections */
struct server *server; /* pointer to the corresponding server */ struct server *server; /* pointer to the corresponding server */
struct eb32_node lb_node; /* node used for tree-based load balancing */ struct eb32_node lb_node; /* node used for tree-based load balancing */
char *extra_counters_storage; /* storage for extra_counters */
struct server *next_full; /* next server in the temporary full list */ struct server *next_full; /* next server in the temporary full list */
unsigned int last_other_tgrp_served; /* Last other tgrp we dequeued from */ unsigned int last_other_tgrp_served; /* Last other tgrp we dequeued from */
unsigned int self_served; /* Number of connection we dequeued from our own queue */ unsigned int self_served; /* Number of connection we dequeued from our own queue */

View file

@ -29,6 +29,7 @@
#include <haproxy/api.h> #include <haproxy/api.h>
#include <haproxy/applet-t.h> #include <haproxy/applet-t.h>
#include <haproxy/arg-t.h> #include <haproxy/arg-t.h>
#include <haproxy/counters.h>
#include <haproxy/freq_ctr.h> #include <haproxy/freq_ctr.h>
#include <haproxy/proxy-t.h> #include <haproxy/proxy-t.h>
#include <haproxy/resolvers-t.h> #include <haproxy/resolvers-t.h>
@ -55,6 +56,7 @@ int srv_update_addr(struct server *s, void *ip, int ip_sin_family, struct server
struct sample_expr *_parse_srv_expr(char *expr, struct arg_list *args_px, struct sample_expr *_parse_srv_expr(char *expr, struct arg_list *args_px,
const char *file, int linenum, char **err); const char *file, int linenum, char **err);
int server_parse_exprs(struct server *srv, struct proxy *px, char **err); int server_parse_exprs(struct server *srv, struct proxy *px, char **err);
int srv_configure_auto_sni(struct server *srv, int *err_code, char **err);
int server_set_inetaddr(struct server *s, const struct server_inetaddr *inetaddr, struct server_inetaddr_updater updater, struct buffer *msg); int server_set_inetaddr(struct server *s, const struct server_inetaddr *inetaddr, struct server_inetaddr_updater updater, struct buffer *msg);
int server_set_inetaddr_warn(struct server *s, const struct server_inetaddr *inetaddr, struct server_inetaddr_updater updater); int server_set_inetaddr_warn(struct server *s, const struct server_inetaddr *inetaddr, struct server_inetaddr_updater updater);
void server_get_inetaddr(struct server *s, struct server_inetaddr *inetaddr); void server_get_inetaddr(struct server *s, struct server_inetaddr *inetaddr);
@ -211,7 +213,7 @@ static inline void srv_inc_sess_ctr(struct server *s)
_HA_ATOMIC_INC(&s->counters.shared.tg[tgid - 1]->cum_sess); _HA_ATOMIC_INC(&s->counters.shared.tg[tgid - 1]->cum_sess);
update_freq_ctr(&s->counters.shared.tg[tgid - 1]->sess_per_sec, 1); update_freq_ctr(&s->counters.shared.tg[tgid - 1]->sess_per_sec, 1);
} }
HA_ATOMIC_UPDATE_MAX(&s->counters.sps_max, COUNTERS_UPDATE_MAX(&s->counters.sps_max,
update_freq_ctr(&s->counters._sess_per_sec, 1)); update_freq_ctr(&s->counters._sess_per_sec, 1));
} }
@ -349,28 +351,26 @@ static inline int srv_is_transparent(const struct server *srv)
(srv->flags & SRV_F_MAPPORTS); (srv->flags & SRV_F_MAPPORTS);
} }
/* Detach server from proxy list. It is supported to call this /* Detach <srv> server from its parent proxy list.
* even if the server is not yet in the list *
* Must be called under thread isolation or when it is safe to assume * Must be called under thread isolation.
* that the parent proxy doesn't is not skimming through the server list
*/ */
static inline void srv_detach(struct server *srv) static inline void srv_detach(struct server *srv)
{ {
struct proxy *px = srv->proxy; struct proxy *px = srv->proxy;
struct server *prev;
if (px->srv == srv) if (px->srv == srv) {
px->srv = srv->next; px->srv = srv->next;
}
else { else {
struct server *prev;
for (prev = px->srv; prev && prev->next != srv; prev = prev->next) for (prev = px->srv; prev && prev->next != srv; prev = prev->next)
; ;
BUG_ON(!prev); /* Server instance not found in proxy list ? */
BUG_ON(!prev);
prev->next = srv->next; prev->next = srv->next;
} }
/* reset the proxy's ready_srv if it was this one */
/* Reset the proxy's ready_srv if it was this one. */
HA_ATOMIC_CAS(&px->ready_srv, &srv, NULL); HA_ATOMIC_CAS(&px->ready_srv, &srv, NULL);
} }

View file

@ -25,12 +25,13 @@
#include <haproxy/connection.h> #include <haproxy/connection.h>
#include <haproxy/counters.h>
#include <haproxy/openssl-compat.h> #include <haproxy/openssl-compat.h>
#include <haproxy/pool-t.h> #include <haproxy/pool-t.h>
#include <haproxy/proxy-t.h> #include <haproxy/proxy-t.h>
#include <haproxy/quic_conn-t.h> #include <haproxy/quic_conn-t.h>
#include <haproxy/ssl_sock-t.h> #include <haproxy/ssl_sock-t.h>
#include <haproxy/stats.h> #include <haproxy/stats-t.h>
#include <haproxy/thread.h> #include <haproxy/thread.h>
extern struct list tlskeys_reference; extern struct list tlskeys_reference;
@ -73,7 +74,7 @@ int ssl_sock_get_alpn(const struct connection *conn, void *xprt_ctx,
const char **str, int *len); const char **str, int *len);
int ssl_bio_and_sess_init(struct connection *conn, SSL_CTX *ssl_ctx, int ssl_bio_and_sess_init(struct connection *conn, SSL_CTX *ssl_ctx,
SSL **ssl, BIO **bio, BIO_METHOD *bio_meth, void *ctx); SSL **ssl, BIO **bio, BIO_METHOD *bio_meth, void *ctx);
int ssl_sock_srv_try_reuse_sess(struct ssl_sock_ctx *ctx, struct server *srv); void ssl_sock_srv_try_reuse_sess(struct ssl_sock_ctx *ctx, struct server *srv);
const char *ssl_sock_get_sni(struct connection *conn); const char *ssl_sock_get_sni(struct connection *conn);
const char *ssl_sock_get_cert_sig(struct connection *conn); const char *ssl_sock_get_cert_sig(struct connection *conn);
const char *ssl_sock_get_cipher_name(struct connection *conn); const char *ssl_sock_get_cipher_name(struct connection *conn);

View file

@ -34,10 +34,10 @@ int cert_get_pkey_algo(X509 *crt, struct buffer *out);
int ssl_sock_get_serial(X509 *crt, struct buffer *out); int ssl_sock_get_serial(X509 *crt, struct buffer *out);
int ssl_sock_crt2der(X509 *crt, struct buffer *out); int ssl_sock_crt2der(X509 *crt, struct buffer *out);
int ssl_sock_get_time(ASN1_TIME *tm, struct buffer *out); int ssl_sock_get_time(ASN1_TIME *tm, struct buffer *out);
int ssl_sock_get_dn_entry(X509_NAME *a, const struct buffer *entry, int pos, int ssl_sock_get_dn_entry(__X509_NAME_CONST__ X509_NAME *a, const struct buffer *entry, int pos,
struct buffer *out); struct buffer *out);
int ssl_sock_get_dn_formatted(X509_NAME *a, const struct buffer *format, struct buffer *out); int ssl_sock_get_dn_formatted(__X509_NAME_CONST__ X509_NAME *a, const struct buffer *format, struct buffer *out);
int ssl_sock_get_dn_oneline(X509_NAME *a, struct buffer *out); int ssl_sock_get_dn_oneline(__X509_NAME_CONST__ X509_NAME *a, struct buffer *out);
X509* ssl_sock_get_peer_certificate(SSL *ssl); X509* ssl_sock_get_peer_certificate(SSL *ssl);
X509* ssl_sock_get_verified_chain_root(SSL *ssl); X509* ssl_sock_get_verified_chain_root(SSL *ssl);
unsigned int openssl_version_parser(const char *version); unsigned int openssl_version_parser(const char *version);

View file

@ -36,7 +36,7 @@ struct shm_stats_file_hdr {
/* 2 bytes hole */ /* 2 bytes hole */
uint global_now_ms; /* global monotonic date (ms) common to all processes using the shm */ uint global_now_ms; /* global monotonic date (ms) common to all processes using the shm */
ullong global_now_ns; /* global monotonic date (ns) common to all processes using the shm */ ullong global_now_ns; /* global monotonic date (ns) common to all processes using the shm */
llong now_offset; /* offset applied to global monotonic date on startup */ ALWAYS_PAD(8); // 8 bytes hole
/* each process uses one slot and is identified using its pid, max 64 in order /* each process uses one slot and is identified using its pid, max 64 in order
* to be able to use bitmask to refer to a process and then look its pid in the * to be able to use bitmask to refer to a process and then look its pid in the
* "slots.pid" map * "slots.pid" map

View file

@ -25,6 +25,7 @@
#include <import/ebtree-t.h> #include <import/ebtree-t.h>
#include <haproxy/api-t.h> #include <haproxy/api-t.h>
#include <haproxy/buf-t.h> #include <haproxy/buf-t.h>
#include <haproxy/counters-t.h>
/* Flags for applet.ctx.stats.flags */ /* Flags for applet.ctx.stats.flags */
#define STAT_F_FMT_HTML 0x00000001 /* dump the stats in HTML format */ #define STAT_F_FMT_HTML 0x00000001 /* dump the stats in HTML format */
@ -515,23 +516,13 @@ struct field {
} u; } u;
}; };
enum counters_type {
COUNTERS_FE = 0,
COUNTERS_BE,
COUNTERS_SV,
COUNTERS_LI,
COUNTERS_RSLV,
COUNTERS_OFF_END
};
/* Entity used to generate statistics on an HAProxy component */ /* Entity used to generate statistics on an HAProxy component */
struct stats_module { struct stats_module {
struct list list; struct list list;
const char *name; const char *name;
/* functor used to generate the stats module using counters provided through data parameter */ /* function used to generate the stats module using counters provided through data parameter */
int (*fill_stats)(void *data, struct field *, unsigned int *); int (*fill_stats)(struct stats_module *, struct extra_counters *, struct field *, unsigned int *);
struct stat_col *stats; /* statistics provided by the module */ struct stat_col *stats; /* statistics provided by the module */
void *counters; /* initial values of allocated counters */ void *counters; /* initial values of allocated counters */
@ -543,12 +534,6 @@ struct stats_module {
char clearable; /* reset on a clear counters */ char clearable; /* reset on a clear counters */
}; };
struct extra_counters {
char *data; /* heap containing counters allocated in a linear fashion */
size_t size; /* size of allocated data */
enum counters_type type; /* type of object containing the counters */
};
/* stats_domain is used in a flag as a 1 byte field */ /* stats_domain is used in a flag as a 1 byte field */
enum stats_domain { enum stats_domain {
STATS_DOMAIN_PROXY = 0, STATS_DOMAIN_PROXY = 0,
@ -593,58 +578,9 @@ struct show_stat_ctx {
int iid, type, sid; /* proxy id, type and service id if bounding of stats is enabled */ int iid, type, sid; /* proxy id, type and service id if bounding of stats is enabled */
int st_code; /* the status code returned by an action */ int st_code; /* the status code returned by an action */
struct buffer chunk; /* temporary buffer which holds a single-line output */ struct buffer chunk; /* temporary buffer which holds a single-line output */
struct watcher px_watch; /* watcher to automatically update obj1 on backend deletion */
struct watcher srv_watch; /* watcher to automatically update obj2 on server deletion */ struct watcher srv_watch; /* watcher to automatically update obj2 on server deletion */
enum stat_state state; /* phase of output production */ enum stat_state state; /* phase of output production */
}; };
extern THREAD_LOCAL void *trash_counters;
#define EXTRA_COUNTERS(name) \
struct extra_counters *name
#define EXTRA_COUNTERS_GET(counters, mod) \
(likely(counters) ? \
((void *)((counters)->data + (mod)->counters_off[(counters)->type])) : \
(trash_counters))
#define EXTRA_COUNTERS_REGISTER(counters, ctype, alloc_failed_label) \
do { \
typeof(*counters) _ctr; \
_ctr = calloc(1, sizeof(*_ctr)); \
if (!_ctr) \
goto alloc_failed_label; \
_ctr->type = (ctype); \
*(counters) = _ctr; \
} while (0)
#define EXTRA_COUNTERS_ADD(mod, counters, new_counters, csize) \
do { \
typeof(counters) _ctr = (counters); \
(mod)->counters_off[_ctr->type] = _ctr->size; \
_ctr->size += (csize); \
} while (0)
#define EXTRA_COUNTERS_ALLOC(counters, alloc_failed_label) \
do { \
typeof(counters) _ctr = (counters); \
_ctr->data = malloc((_ctr)->size); \
if (!_ctr->data) \
goto alloc_failed_label; \
} while (0)
#define EXTRA_COUNTERS_INIT(counters, mod, init_counters, init_counters_size) \
do { \
typeof(counters) _ctr = (counters); \
memcpy(_ctr->data + mod->counters_off[_ctr->type], \
(init_counters), (init_counters_size)); \
} while (0)
#define EXTRA_COUNTERS_FREE(counters) \
do { \
if (counters) { \
free((counters)->data); \
free(counters); \
} \
} while (0)
#endif /* _HAPROXY_STATS_T_H */ #endif /* _HAPROXY_STATS_T_H */

View file

@ -24,6 +24,7 @@
#define _HAPROXY_STATS_H #define _HAPROXY_STATS_H
#include <haproxy/api.h> #include <haproxy/api.h>
#include <haproxy/counters.h>
#include <haproxy/listener-t.h> #include <haproxy/listener-t.h>
#include <haproxy/stats-t.h> #include <haproxy/stats-t.h>
#include <haproxy/tools-t.h> #include <haproxy/tools-t.h>
@ -167,7 +168,8 @@ static inline enum stats_domain_px_cap stats_px_get_cap(uint32_t domain)
} }
int stats_allocate_proxy_counters_internal(struct extra_counters **counters, int stats_allocate_proxy_counters_internal(struct extra_counters **counters,
int type, int px_cap); int type, int px_cap,
char **storage, size_t step);
int stats_allocate_proxy_counters(struct proxy *px); int stats_allocate_proxy_counters(struct proxy *px);
void stats_register_module(struct stats_module *m); void stats_register_module(struct stats_module *m);

View file

@ -349,19 +349,6 @@ struct sedesc {
unsigned long long kop; /* Known outgoing payload length (see above) */ unsigned long long kop; /* Known outgoing payload length (see above) */
}; };
/* sc_app_ops describes the application layer's operations and notification
* callbacks when I/O activity is reported and to use to perform shutr/shutw.
* There are very few combinations in practice (strm/chk <-> none/mux/applet).
*/
struct sc_app_ops {
void (*chk_rcv)(struct stconn *); /* chk_rcv function, may not be null */
void (*chk_snd)(struct stconn *); /* chk_snd function, may not be null */
void (*abort)(struct stconn *); /* abort function, may not be null */
void (*shutdown)(struct stconn *); /* shutdown function, may not be null */
int (*wake)(struct stconn *); /* data-layer callback to report activity */
char name[8]; /* data layer name, zero-terminated */
};
/* /*
* This structure describes the elements of a connection relevant to a stream * This structure describes the elements of a connection relevant to a stream
*/ */
@ -383,7 +370,6 @@ struct stconn {
struct wait_event wait_event; /* We're in a wait list */ struct wait_event wait_event; /* We're in a wait list */
struct sedesc *sedesc; /* points to the stream endpoint descriptor */ struct sedesc *sedesc; /* points to the stream endpoint descriptor */
enum obj_type *app; /* points to the applicative point (stream or check) */ enum obj_type *app; /* points to the applicative point (stream or check) */
const struct sc_app_ops *app_ops; /* general operations used at the app layer */
struct sockaddr_storage *src; /* source address (pool), when known, otherwise NULL */ struct sockaddr_storage *src; /* source address (pool), when known, otherwise NULL */
struct sockaddr_storage *dst; /* destination address (pool), when known, otherwise NULL */ struct sockaddr_storage *dst; /* destination address (pool), when known, otherwise NULL */
}; };

View file

@ -57,6 +57,8 @@ void sc_destroy(struct stconn *sc);
int sc_reset_endp(struct stconn *sc); int sc_reset_endp(struct stconn *sc);
struct appctx *sc_applet_create(struct stconn *sc, struct applet *app); struct appctx *sc_applet_create(struct stconn *sc, struct applet *app);
int sc_applet_process(struct stconn *sc);
int sc_conn_process(struct stconn *sc);
void sc_conn_prepare_endp_upgrade(struct stconn *sc); void sc_conn_prepare_endp_upgrade(struct stconn *sc);
void sc_conn_abort_endp_upgrade(struct stconn *sc); void sc_conn_abort_endp_upgrade(struct stconn *sc);
@ -349,16 +351,6 @@ static inline struct hstream *sc_hstream(const struct stconn *sc)
return NULL; return NULL;
} }
/* Returns the name of the application layer's name for the stconn,
* or "NONE" when none is attached.
*/
static inline const char *sc_get_data_name(const struct stconn *sc)
{
if (!sc->app_ops)
return "NONE";
return sc->app_ops->name;
}
/* Returns non-zero if the stream connector's Rx path is blocked because of /* Returns non-zero if the stream connector's Rx path is blocked because of
* lack of room in the input buffer. This usually happens after applets failed * lack of room in the input buffer. This usually happens after applets failed
* to deliver data into the channel's buffer and reported it via sc_need_room(). * to deliver data into the channel's buffer and reported it via sc_need_room().
@ -460,7 +452,7 @@ static inline size_t se_nego_ff(struct sedesc *se, struct buffer *input, size_t
goto end; goto end;
} }
ret = mux->nego_fastfwd(se->sc, input, count, flags); ret = CALL_MUX_WITH_RET(mux, nego_fastfwd(se->sc, input, count, flags));
if (se->iobuf.flags & IOBUF_FL_FF_BLOCKED) { if (se->iobuf.flags & IOBUF_FL_FF_BLOCKED) {
sc_ep_report_blocked_send(se->sc, 0); sc_ep_report_blocked_send(se->sc, 0);
@ -493,7 +485,7 @@ static inline size_t se_done_ff(struct sedesc *se)
size_t to_send = se_ff_data(se); size_t to_send = se_ff_data(se);
BUG_ON(!mux->done_fastfwd); BUG_ON(!mux->done_fastfwd);
ret = mux->done_fastfwd(se->sc); ret = CALL_MUX_WITH_RET(mux, done_fastfwd(se->sc));
if (ret) { if (ret) {
/* Something was forwarded, unblock the zero-copy forwarding. /* Something was forwarded, unblock the zero-copy forwarding.
* If all data was sent, report and send activity. * If all data was sent, report and send activity.
@ -525,7 +517,7 @@ static inline size_t se_done_ff(struct sedesc *se)
} }
} }
} }
se->sc->bytes_out += ret;
return ret; return ret;
} }

View file

@ -180,6 +180,7 @@ enum {
STRM_EVT_SHUT_SRV_DOWN = 0x00000004, /* Must be shut because the selected server became available */ STRM_EVT_SHUT_SRV_DOWN = 0x00000004, /* Must be shut because the selected server became available */
STRM_EVT_SHUT_SRV_UP = 0x00000008, /* Must be shut because a preferred server became available */ STRM_EVT_SHUT_SRV_UP = 0x00000008, /* Must be shut because a preferred server became available */
STRM_EVT_KILLED = 0x00000010, /* Must be shut for external reason */ STRM_EVT_KILLED = 0x00000010, /* Must be shut for external reason */
STRM_EVT_RES = 0x00000020, /* A requested resource is available (a buffer, a conn_slot...) */
}; };
/* This function is used to report flags in debugging tools. Please reflect /* This function is used to report flags in debugging tools. Please reflect

View file

@ -412,6 +412,7 @@ static inline void stream_shutdown(struct stream *s, int why)
static inline unsigned int stream_map_task_state(unsigned int state) static inline unsigned int stream_map_task_state(unsigned int state)
{ {
return ((state & TASK_WOKEN_TIMER) ? STRM_EVT_TIMER : 0) | return ((state & TASK_WOKEN_TIMER) ? STRM_EVT_TIMER : 0) |
((state & TASK_WOKEN_RES) ? STRM_EVT_RES : 0) |
((state & TASK_WOKEN_MSG) ? STRM_EVT_MSG : 0) | ((state & TASK_WOKEN_MSG) ? STRM_EVT_MSG : 0) |
((state & TASK_F_UEVT1) ? STRM_EVT_SHUT_SRV_DOWN : 0) | ((state & TASK_F_UEVT1) ? STRM_EVT_SHUT_SRV_DOWN : 0) |
((state & TASK_F_UEVT3) ? STRM_EVT_SHUT_SRV_UP : 0) | ((state & TASK_F_UEVT3) ? STRM_EVT_SHUT_SRV_UP : 0) |

View file

@ -130,20 +130,22 @@ struct notification {
* on return. * on return.
*/ */
#define TASK_COMMON \ #define TASK_COMMON \
struct { \ unsigned int state; /* task state : bitfield of TASK_ */ \
unsigned int state; /* task state : bitfield of TASK_ */ \ int tid; /* tid of task/tasklet. <0 = local for tasklet, unbound for task */ \
int tid; /* tid of task/tasklet. <0 = local for tasklet, unbound for task */ \ struct task *(*process)(struct task *t, void *ctx, unsigned int state); /* the function which processes the task */ \
struct task *(*process)(struct task *t, void *ctx, unsigned int state); /* the function which processes the task */ \ void *context; /* the task's context */ \
void *context; /* the task's context */ \ const struct ha_caller *caller; /* call place of last wakeup(); 0 on init, -1 on free */ \
const struct ha_caller *caller; /* call place of last wakeup(); 0 on init, -1 on free */ \ uint32_t wake_date; /* date of the last task wakeup */ \
uint32_t wake_date; /* date of the last task wakeup */ \ unsigned int calls; /* number of times process was called */ \
unsigned int calls; /* number of times process was called */ \ TASK_DEBUG_STORAGE; \
TASK_DEBUG_STORAGE; \ short last_run; /* 16-bit now_ms of last run */
} /* a 16- or 48-bit hole remains here and is used by task */
/* The base for all tasks */ /* The base for all tasks */
struct task { struct task {
TASK_COMMON; /* must be at the beginning! */ TASK_COMMON; /* must be at the beginning! */
short nice; /* task prio from -1024 to +1024 */
int expire; /* next expiration date for this task, in ticks */
struct eb32_node rq; /* ebtree node used to hold the task in the run queue */ struct eb32_node rq; /* ebtree node used to hold the task in the run queue */
/* WARNING: the struct task is often aliased as a struct tasklet when /* WARNING: the struct task is often aliased as a struct tasklet when
* it is NOT in the run queue. The tasklet has its struct list here * it is NOT in the run queue. The tasklet has its struct list here
@ -151,14 +153,12 @@ struct task {
* ever reorder these fields without taking this into account! * ever reorder these fields without taking this into account!
*/ */
struct eb32_node wq; /* ebtree node used to hold the task in the wait queue */ struct eb32_node wq; /* ebtree node used to hold the task in the wait queue */
int expire; /* next expiration date for this task, in ticks */
short nice; /* task prio from -1024 to +1024 */
/* 16-bit hole here */
}; };
/* lightweight tasks, without priority, mainly used for I/Os */ /* lightweight tasks, without priority, mainly used for I/Os */
struct tasklet { struct tasklet {
TASK_COMMON; /* must be at the beginning! */ TASK_COMMON; /* must be at the beginning! */
/* 48-bit hole here */
struct list list; struct list list;
/* WARNING: the struct task is often aliased as a struct tasklet when /* WARNING: the struct task is often aliased as a struct tasklet when
* it is not in the run queue. The task has its struct rq here where * it is not in the run queue. The task has its struct rq here where

View file

@ -121,6 +121,7 @@ enum tcpcheck_rule_type {
/* Unused 0x000000A0..0x00000FF0 (reserved for future proto) */ /* Unused 0x000000A0..0x00000FF0 (reserved for future proto) */
#define TCPCHK_RULES_TCP_CHK 0x00000FF0 #define TCPCHK_RULES_TCP_CHK 0x00000FF0
#define TCPCHK_RULES_PROTO_CHK 0x00000FF0 /* Mask to cover protocol check */ #define TCPCHK_RULES_PROTO_CHK 0x00000FF0 /* Mask to cover protocol check */
#define TCPCHK_RULES_MAY_USE_SBUF 0x00001000 /* checks may try to use small buffers if possible for the request */
struct check; struct check;
struct tcpcheck_connect { struct tcpcheck_connect {

View file

@ -217,6 +217,7 @@ enum lock_label {
QC_CID_LOCK, QC_CID_LOCK,
CACHE_LOCK, CACHE_LOCK,
GUID_LOCK, GUID_LOCK,
PROXIES_DEL_LOCK,
OTHER_LOCK, OTHER_LOCK,
/* WT: make sure never to use these ones outside of development, /* WT: make sure never to use these ones outside of development,
* we need them for lock profiling! * we need them for lock profiling!

View file

@ -75,6 +75,41 @@ enum {
/* we have 4 buffer-wait queues, in highest to lowest emergency order */ /* we have 4 buffer-wait queues, in highest to lowest emergency order */
#define DYNBUF_NBQ 4 #define DYNBUF_NBQ 4
/* execution context, for tracing resource usage or warning origins */
enum thread_exec_ctx_type {
TH_EX_CTX_NONE = 0, /* context not filled */
TH_EX_CTX_OTHER, /* context only known by a generic pointer */
TH_EX_CTX_INITCALL, /* the pointer is an initcall providing file:line */
TH_EX_CTX_CALLER, /* the pointer is an ha_caller of the caller providing file:line etc */
TH_EX_CTX_SMPF, /* directly registered sample fetch function, using .smpf_kwl */
TH_EX_CTX_CONV, /* directly registered converter function, using .conv_kwl */
TH_EX_CTX_FUNC, /* hopefully recognizable function/callback, using .pointer */
TH_EX_CTX_ACTION, /* directly registered action function, using .action_kwl */
TH_EX_CTX_FLT, /* filter whose config is in .flt_conf */
TH_EX_CTX_MUX, /* mux whose mux_ops is in .mux_ops */
TH_EX_CTX_TASK, /* task or tasklet whose function is in .task */
TH_EX_CTX_APPLET, /* applet whose applet is in .applet */
TH_EX_CTX_CLI_KWL, /* CLI keyword list, using .cli_kwl */
};
struct thread_exec_ctx {
enum thread_exec_ctx_type type;
/* 32-bit hole here on 64-bit platforms */
union {
const void *pointer; /* generic pointer (for other) */
const struct initcall *initcall; /* used with TH_EX_CTX_INITCALL */
const struct ha_caller *ha_caller; /* used with TH_EX_CTX_CALLER */
const struct sample_fetch_kw_list *smpf_kwl; /* used with TH_EX_CTX_SMPF */
const struct sample_conv_kw_list *conv_kwl; /* used with TH_EX_CTX_CONV */
const struct action_kw_list *action_kwl; /* used with TH_EX_CTX_ACTION */
const struct flt_conf *flt_conf; /* used with TH_EX_CTX_FLTCONF */
const struct mux_ops *mux_ops; /* used with TH_EX_CTX_MUX */
const struct task *(*task)(struct task *, void *, unsigned int); /* used with TH_EX_CTX_TASK */
const struct applet *applet; /* used with TH_EX_CTX_APPLET */
const struct cli_kw_list *cli_kwl; /* used with TH_EX_CTX_CLI_KWL */
};
};
/* Thread group information. This defines a base and a count of global thread /* Thread group information. This defines a base and a count of global thread
* IDs which belong to it, and which can be looked up into thread_info/ctx. It * IDs which belong to it, and which can be looked up into thread_info/ctx. It
* is set up during parsing and is stable during operation. Thread groups start * is set up during parsing and is stable during operation. Thread groups start
@ -172,8 +207,7 @@ struct thread_ctx {
uint64_t curr_mono_time; /* latest system wide monotonic time (leaving poll) */ uint64_t curr_mono_time; /* latest system wide monotonic time (leaving poll) */
ulong lock_history; /* history of used locks, see thread.h for more details */ ulong lock_history; /* history of used locks, see thread.h for more details */
struct thread_exec_ctx exec_ctx; /* current execution context when known, or NULL */
/* around 56 unused bytes here */
// fourth cache line here on 64 bits: accessed mostly using atomic ops // fourth cache line here on 64 bits: accessed mostly using atomic ops
ALWAYS_ALIGN(64); ALWAYS_ALIGN(64);
@ -199,6 +233,7 @@ struct thread_ctx {
struct buffer *last_dump_buffer; /* Copy of last buffer used for a dump; may be NULL or invalid; for post-mortem only */ struct buffer *last_dump_buffer; /* Copy of last buffer used for a dump; may be NULL or invalid; for post-mortem only */
unsigned long long total_streams; /* Total number of streams created on this thread */ unsigned long long total_streams; /* Total number of streams created on this thread */
unsigned int stream_cnt; /* Number of streams attached to this thread */ unsigned int stream_cnt; /* Number of streams attached to this thread */
unsigned int rq_tot_peak; /* total run queue size last call */
// around 68 bytes here for shared variables // around 68 bytes here for shared variables

View file

@ -117,4 +117,42 @@ static inline void thread_set_pin_grp1(struct thread_set *ts, ulong mask)
ts->rel[i] = 0; ts->rel[i] = 0;
} }
/* switches the current execution context to <ctx> and returns the previous one
* so that this may even be used to save and restore. Setting EXEC_CTX_NONE
* resets it. It's efficient because it uses a pair of registers on input and
* output.
*/
static inline struct thread_exec_ctx switch_exec_ctx(const struct thread_exec_ctx ctx)
{
const struct thread_exec_ctx prev = th_ctx->exec_ctx;
th_ctx->exec_ctx = ctx;
return prev;
}
/* used to reset the execution context */
#define EXEC_CTX_NONE ((struct thread_exec_ctx){ .type = 0, .pointer = NULL })
/* make an execution context from a type and a pointer */
#define EXEC_CTX_MAKE(_type, _pointer) ((struct thread_exec_ctx){ .type = (_type), .pointer = (_pointer) })
/* execute expression <expr> under context <new_ctx> then restore the previous
* one, and return the expression's return value.
*/
#define EXEC_CTX_WITH_RET(new_ctx, expr) ({ \
const struct thread_exec_ctx __prev_ctx = switch_exec_ctx(new_ctx); \
typeof(expr) __ret = (expr); \
switch_exec_ctx(__prev_ctx); \
__ret; \
})
/* execute expression <expr> under context <new_ctx> then restore the previous
* one. This one has no return value.
*/
#define EXEC_CTX_NO_RET(new_ctx, expr) do { \
const struct thread_exec_ctx __prev_ctx = switch_exec_ctx(new_ctx); \
do { expr; } while (0); \
switch_exec_ctx(__prev_ctx); \
} while (0)
#endif /* _HAPROXY_TINFO_H */ #endif /* _HAPROXY_TINFO_H */

View file

@ -1147,10 +1147,13 @@ void dump_hex(struct buffer *out, const char *pfx, const void *buf, int len, int
int may_access(const void *ptr); int may_access(const void *ptr);
const void *resolve_sym_name(struct buffer *buf, const char *pfx, const void *addr); const void *resolve_sym_name(struct buffer *buf, const char *pfx, const void *addr);
const void *resolve_dso_name(struct buffer *buf, const char *pfx, const void *addr); const void *resolve_dso_name(struct buffer *buf, const char *pfx, const void *addr);
void make_tar_header(char *output, const char *pfx, const char *fname, const char *link, size_t size, mode_t mode);
int load_file_into_tar(char **storage, size_t *size, const char *pfx, const char *fname, const char *input, const char *link);
const char *get_exec_path(void); const char *get_exec_path(void);
void *get_sym_curr_addr(const char *name); void *get_sym_curr_addr(const char *name);
void *get_sym_next_addr(const char *name); void *get_sym_next_addr(const char *name);
int dump_libs(struct buffer *output, int with_addr); int dump_libs(struct buffer *output, int with_addr);
void collect_libs(void);
/* Note that this may result in opening libgcc() on first call, so it may need /* Note that this may result in opening libgcc() on first call, so it may need
* to have been called once before chrooting. * to have been called once before chrooting.
@ -1324,6 +1327,62 @@ static inline uint statistical_prng_range(uint range)
return mul32hi(statistical_prng(), range ? range - 1 : 0); return mul32hi(statistical_prng(), range ? range - 1 : 0);
} }
/* The functions below are used to hash one or two pointers together and reduce
* the result to fit into a given number of bits. The first part is made of a
* multiplication (and possibly an addition) by one or two prime numbers giving
* a 64-bit number whose center bits are the most distributed, and the second
* part will reuse this value and return a mix of the most variable bits that
* fits in the requested size. The most convenient approach is to directly
* call ptr_hash() / ptr2_hash(), though for some specific use cases where a
* second value could be useful, one may prefer to call the lower level
* operations instead.
*/
/* reduce a 64-bit pointer hash to <bits> bits */
static forceinline uint _ptr_hash_reduce(unsigned long long x, const int bits)
{
if (!bits)
return 0;
if (sizeof(long) == 4)
x ^= x >> 32;
else
x >>= 31 - (bits + 1) / 2;
return x & (~0U >> (-bits & 31));
}
/* single-pointer version, low-level, use ptr_hash() instead */
static forceinline ullong _ptr_hash(const void *p)
{
unsigned long long x = (unsigned long)p;
x *= 0xacd1be85U;
return x;
}
/* two-pointer version, low-level, use ptr2_hash() instead */
static forceinline ullong _ptr2_hash(const void *p1, const void *p2)
{
unsigned long long x = (unsigned long)p1;
unsigned long long y = (unsigned long)p2;
x *= 0xacd1be85U;
y *= 0x9d28e4e9U;
return x ^ y;
}
/* two-pointer plus arg version, low-level, use ptr2_hash_arg() instead */
static forceinline ullong _ptr2_hash_arg(const void *p1, const void *p2, ulong arg)
{
unsigned long long x = (unsigned long)p1;
unsigned long long y = (unsigned long)p2;
x *= 0xacd1be85U;
x += arg;
y *= 0x9d28e4e9U;
return x ^ y;
}
/* returns a hash on <bits> bits of pointer <p> that is suitable for being used /* returns a hash on <bits> bits of pointer <p> that is suitable for being used
* to compute statistic buckets, in that it's fast and reasonably distributed * to compute statistic buckets, in that it's fast and reasonably distributed
* thanks to mixing the bits via a multiplication by a prime number and using * thanks to mixing the bits via a multiplication by a prime number and using
@ -1337,17 +1396,7 @@ static inline uint statistical_prng_range(uint range)
*/ */
static forceinline uint ptr_hash(const void *p, const int bits) static forceinline uint ptr_hash(const void *p, const int bits)
{ {
unsigned long long x = (unsigned long)p; return _ptr_hash_reduce(_ptr_hash(p), bits);
if (!bits)
return 0;
x *= 0xacd1be85U;
if (sizeof(long) == 4)
x ^= x >> 32;
else
x >>= 31 - (bits + 1) / 2;
return x & (~0U >> (-bits & 31));
} }
/* Same as above but works on two pointers. It will return the same values /* Same as above but works on two pointers. It will return the same values
@ -1355,20 +1404,15 @@ static forceinline uint ptr_hash(const void *p, const int bits)
*/ */
static forceinline uint ptr2_hash(const void *p1, const void *p2, const int bits) static forceinline uint ptr2_hash(const void *p1, const void *p2, const int bits)
{ {
unsigned long long x = (unsigned long)p1; return _ptr_hash_reduce(_ptr2_hash(p1, p2), bits);
unsigned long long y = (unsigned long)p2; }
if (!bits) /* Same as above but works on two pointers and a long argument. It will return
return 0; * the same values if the second pointer is NULL.
*/
x *= 0xacd1be85U; static forceinline uint ptr2_hash_arg(const void *p1, const void *p2, ulong arg, const int bits)
y *= 0x9d28e4e9U; {
x ^= y; return _ptr_hash_reduce(_ptr2_hash_arg(p1, p2, arg), bits);
if (sizeof(long) == 4)
x ^= x >> 32;
else
x >>= 33 - bits / 2;
return x & (~0U >> (-bits & 31));
} }
@ -1499,4 +1543,6 @@ void ha_freearray(char ***array);
void ha_memset_s(void *s, int c, size_t n); void ha_memset_s(void *s, int c, size_t n);
void chunk_append_thread_ctx(struct buffer *output, const struct thread_exec_ctx *ctx, const char *pfx, const char *sfx);
#endif /* _HAPROXY_TOOLS_H */ #endif /* _HAPROXY_TOOLS_H */

View file

@ -192,7 +192,7 @@ struct trace_source {
const void *lockon_ptr; // what to lockon when lockon is set const void *lockon_ptr; // what to lockon when lockon is set
const struct trace_source *follow; // other trace source's tracker to follow const struct trace_source *follow; // other trace source's tracker to follow
int cmdline; // true if source was activated via -dt command line args int cmdline; // true if source was activated via -dt command line args
}; } THREAD_ALIGNED();
#endif /* _HAPROXY_TRACE_T_H */ #endif /* _HAPROXY_TRACE_T_H */

View file

@ -190,7 +190,8 @@ void trace_no_cb(enum trace_level level, uint64_t mask, const struct trace_sourc
void trace_register_source(struct trace_source *source); void trace_register_source(struct trace_source *source);
int trace_parse_cmd(const char *arg_src, char **errmsg); int trace_add_cmd(const char *arg_src, char **errmsg);
void trace_parse_cmds(void);
/* return a single char to describe a trace state */ /* return a single char to describe a trace state */
static inline char trace_state_char(enum trace_state st) static inline char trace_state_char(enum trace_state st)

View file

@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIICBTCCAaugAwIBAgIUN+Ne3W00v5RwrlIBqhub+WHgq3kwCgYIKoZIzj0EAwIw
VzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu
dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEQMA4GA1UEAwwHZm9vLmJhcjAgFw0yNjAy
MjYxMDM5MjhaGA8yMDUzMDcxNDEwMzkyOFowVzELMAkGA1UEBhMCQVUxEzARBgNV
BAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0
ZDEQMA4GA1UEAwwHZm9vLmJhcjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGwE
ope2KbYUXi5bSLGiQmkaxO17SwkVbTqRrXTztIx99xj9qfSrVKFqN3lnaNDXAclG
GnfmU/j7xsEocZdYmPujUzBRMB0GA1UdDgQWBBQZSL9UUhRofXo5X9BoS0XBug4i
DzAfBgNVHSMEGDAWgBQZSL9UUhRofXo5X9BoS0XBug4iDzAPBgNVHRMBAf8EBTAD
AQH/MAoGCCqGSM49BAMCA0gAMEUCIQDFDrvj5p9R7wmMRoJGUuEJu7I2xYtXDcOP
lLE0quJtvwIgWW7vuM3B+ruCslhIrMMqD+DYeguxAxi+aHRVMnBig/c=
-----END CERTIFICATE-----

View file

@ -0,0 +1,5 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg6qbbYYII1zqqmlDH
hTwJt+JYBe+ELI02yAecAx+nD4yhRANCAARsBKKXtim2FF4uW0ixokJpGsTte0sJ
FW06ka1087SMffcY/an0q1Shajd5Z2jQ1wHJRhp35lP4+8bBKHGXWJj7
-----END PRIVATE KEY-----

View file

@ -16,7 +16,7 @@ feature cmd "$HAPROXY_PROGRAM -cc 'feature(OPENSSL)'"
feature cmd "command -v socat" feature cmd "command -v socat"
feature ignore_unknown_macro feature ignore_unknown_macro
server s1 -repeat 27 { server s1 -repeat 40 {
rxreq rxreq
txresp txresp
} -start } -start
@ -542,3 +542,44 @@ client c27 -connect ${h1_mainfe_sock} {
expect resp.http.x-jwt-verify-RS256-var2 == "1" expect resp.http.x-jwt-verify-RS256-var2 == "1"
} -run } -run
client c28 -connect ${h1_mainfe_sock} {
# Token content : {"alg":"none"}
# {"iss":"joe", "exp":1300819380, "http://example.com/is_root":true}
txreq -url "/none" -hdr "Authorization: Bearer eyJhbGciOiJub25lIn0.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ."
rxresp
expect resp.status == 200
expect resp.http.x-jwt-alg == "none"
expect resp.http.x-jwt-verify == "1"
} -run
client c29 -connect ${h1_mainfe_sock} {
# Invalid Token : too many subparts
txreq -url "/errors" -hdr "Authorization: Bearer eyJhbGciOiJub25lIn0.aa.aa.aa"
rxresp
expect resp.status == 200
expect resp.http.x-jwt-alg == "none"
expect resp.http.x-jwt-verify == "-3"
# Invalid Token : too many subparts
txreq -url "/errors" -hdr "Authorization: Bearer eyJhbGciOiJub25lIn0.aa.aa."
rxresp
expect resp.status == 200
expect resp.http.x-jwt-alg == "none"
expect resp.http.x-jwt-verify == "-3"
# Invalid Token : too few subparts
txreq -url "/errors" -hdr "Authorization: Bearer eyJhbGciOiJub25lIn0.aa"
rxresp
expect resp.status == 200
expect resp.http.x-jwt-alg == "none"
expect resp.http.x-jwt-verify == "-3"
# Invalid Token : no signature but alg different than "none"
txreq -url "/errors" -hdr "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ."
rxresp
expect resp.status == 200
expect resp.http.x-jwt-alg == "RS256"
expect resp.http.x-jwt-verify == "-3"
} -run

View file

@ -19,7 +19,7 @@ feature cmd "$HAPROXY_PROGRAM -cc 'version_atleast(3.4-dev2)'"
feature cmd "$HAPROXY_PROGRAM -cc 'feature(OPENSSL) && openssl_version_atleast(1.1.1)'" feature cmd "$HAPROXY_PROGRAM -cc 'feature(OPENSSL) && openssl_version_atleast(1.1.1)'"
feature ignore_unknown_macro feature ignore_unknown_macro
server s1 -repeat 20 { server s1 -repeat 30 {
rxreq rxreq
txresp txresp
} -start } -start
@ -53,6 +53,10 @@ haproxy h1 -conf {
# { "kty": "RSA", "e": "AQAB", "n": "wsqJbopx18NQFYLYOq4ZeMSE89yGiEankUpf25yV8QqroKUGrASj_OeqTWUjwPGKTN1vGFFuHYxiJeAUQH2qQPmg9Oqk6-ATBEKn9COKYniQ5459UxCwmZA2RL6ufhrNyq0JF3GfXkjLDBfhU9zJJEOhknsA0L_c-X4AI3d_NbFdMqxNe1V_UWAlLcbKdwO6iC9fAvwUmDQxgy6R0DC1CMouQpenMRcALaSHar1cm4K-syoNobv3HEuqgZ3s6-hOOSqauqAO0GUozPpaIA7OeruyRl5sTWT0r-iz39bchID2bIKtcqLiFcSYPLBcxmsaQCqRlGhmv6stjTCLV1yT9w", "kid": "ff3c5c96-392e-46ef-a839-6ff16027af78", "d": "b9hXfQ8lOtw8mX1dpqPcoElGhbczz_-xq2znCXQpbBPSZBUddZvchRSH5pSSKPEHlgb3CSGIdpLqsBCv0C_XmCM9ViN8uqsYgDO9uCLIDK5plWttbkqA_EufvW03R9UgIKWmOL3W4g4t-C2mBb8aByaGGVNjLnlb6i186uBsPGkvaeLHbQcRQKAvhOUTeNiyiiCbUGJwCm4avMiZrsz1r81Y1Z5izo0ERxdZymxM3FRZ9vjTB-6DtitvTXXnaAm1JTu6TIpj38u2mnNLkGMbflOpgelMNKBZVxSmfobIbFN8CHVc1UqLK2ElsZ9RCQANgkMHlMkOMj-XT0wHa3VBUQ", "p": "8mgriveKJAp1S7SHqirQAfZafxVuAK_A2QBYPsAUhikfBOvN0HtZjgurPXSJSdgR8KbWV7ZjdJM_eOivIb_XiuAaUdIOXbLRet7t9a_NJtmX9iybhoa9VOJFMBq_rbnbbte2kq0-FnXmv3cukbC2LaEw3aEcDgyURLCgWFqt7M0", "q": "zbbTv5421GowOfKVEuVoA35CEWgl8mdasnEZac2LWxMwKExikKU5LLacLQlcOt7A6n1ZGUC2wyH8mstO5tV34Eug3fnNrbnxFUEE_ZB_njs_rtZnwz57AoUXOXVnd194seIZF9PjdzZcuwXwXbrZ2RSVW8if_ZH5OVYEM1EsA9M", "dp": "1BaIYmIKn1X3InGlcSFcNRtSOnaJdFhRpotCqkRssKUx2qBlxs7ln_5dqLtZkx5VM_UE_GE7yzc6BZOwBxtOftdsr8HVh-14ksSR9rAGEsO2zVBiEuW4qZf_aQM-ScWfU--wcczZ0dT-Ou8P87Bk9K9fjcn0PeaLoz3WTPepzNE", "dq": "kYw2u4_UmWvcXVOeV_VKJ5aQZkJ6_sxTpodRBMPyQmkMHKcW4eKU1mcJju_deqWadw5jGPPpm5yTXm5UkAwfOeookoWpGa7CvVf4kPNI6Aphn3GBjunJHNpPuU6w-wvomGsxd-NqQDGNYKHuFFMcyXO_zWXglQdP_1o1tJ1M-BM", "qi": "j94Ens784M8zsfwWoJhYq9prcSZOGgNbtFWQZO8HP8pcNM9ls7YA4snTtAS_B4peWWFAFZ0LSKPCxAvJnrq69ocmEKEk7ss1Jo062f9pLTQ6cnhMjev3IqLocIFt5Vbsg_PWYpFSR7re6FRbF9EYOM7F2-HRv1idxKCWoyQfBqk" } # { "kty": "RSA", "e": "AQAB", "n": "wsqJbopx18NQFYLYOq4ZeMSE89yGiEankUpf25yV8QqroKUGrASj_OeqTWUjwPGKTN1vGFFuHYxiJeAUQH2qQPmg9Oqk6-ATBEKn9COKYniQ5459UxCwmZA2RL6ufhrNyq0JF3GfXkjLDBfhU9zJJEOhknsA0L_c-X4AI3d_NbFdMqxNe1V_UWAlLcbKdwO6iC9fAvwUmDQxgy6R0DC1CMouQpenMRcALaSHar1cm4K-syoNobv3HEuqgZ3s6-hOOSqauqAO0GUozPpaIA7OeruyRl5sTWT0r-iz39bchID2bIKtcqLiFcSYPLBcxmsaQCqRlGhmv6stjTCLV1yT9w", "kid": "ff3c5c96-392e-46ef-a839-6ff16027af78", "d": "b9hXfQ8lOtw8mX1dpqPcoElGhbczz_-xq2znCXQpbBPSZBUddZvchRSH5pSSKPEHlgb3CSGIdpLqsBCv0C_XmCM9ViN8uqsYgDO9uCLIDK5plWttbkqA_EufvW03R9UgIKWmOL3W4g4t-C2mBb8aByaGGVNjLnlb6i186uBsPGkvaeLHbQcRQKAvhOUTeNiyiiCbUGJwCm4avMiZrsz1r81Y1Z5izo0ERxdZymxM3FRZ9vjTB-6DtitvTXXnaAm1JTu6TIpj38u2mnNLkGMbflOpgelMNKBZVxSmfobIbFN8CHVc1UqLK2ElsZ9RCQANgkMHlMkOMj-XT0wHa3VBUQ", "p": "8mgriveKJAp1S7SHqirQAfZafxVuAK_A2QBYPsAUhikfBOvN0HtZjgurPXSJSdgR8KbWV7ZjdJM_eOivIb_XiuAaUdIOXbLRet7t9a_NJtmX9iybhoa9VOJFMBq_rbnbbte2kq0-FnXmv3cukbC2LaEw3aEcDgyURLCgWFqt7M0", "q": "zbbTv5421GowOfKVEuVoA35CEWgl8mdasnEZac2LWxMwKExikKU5LLacLQlcOt7A6n1ZGUC2wyH8mstO5tV34Eug3fnNrbnxFUEE_ZB_njs_rtZnwz57AoUXOXVnd194seIZF9PjdzZcuwXwXbrZ2RSVW8if_ZH5OVYEM1EsA9M", "dp": "1BaIYmIKn1X3InGlcSFcNRtSOnaJdFhRpotCqkRssKUx2qBlxs7ln_5dqLtZkx5VM_UE_GE7yzc6BZOwBxtOftdsr8HVh-14ksSR9rAGEsO2zVBiEuW4qZf_aQM-ScWfU--wcczZ0dT-Ou8P87Bk9K9fjcn0PeaLoz3WTPepzNE", "dq": "kYw2u4_UmWvcXVOeV_VKJ5aQZkJ6_sxTpodRBMPyQmkMHKcW4eKU1mcJju_deqWadw5jGPPpm5yTXm5UkAwfOeookoWpGa7CvVf4kPNI6Aphn3GBjunJHNpPuU6w-wvomGsxd-NqQDGNYKHuFFMcyXO_zWXglQdP_1o1tJ1M-BM", "qi": "j94Ens784M8zsfwWoJhYq9prcSZOGgNbtFWQZO8HP8pcNM9ls7YA4snTtAS_B4peWWFAFZ0LSKPCxAvJnrq69ocmEKEk7ss1Jo062f9pLTQ6cnhMjev3IqLocIFt5Vbsg_PWYpFSR7re6FRbF9EYOM7F2-HRv1idxKCWoyQfBqk" }
load crt rsa_oeap.pem key rsa_oeap.key jwt on load crt rsa_oeap.pem key rsa_oeap.key jwt on
# Private key built out of the following JWK:
# {"crv":"P-256","d":"6qbbYYII1zqqmlDHhTwJt-JYBe-ELI02yAecAx-nD4w","kty":"EC","x":"bASil7YpthReLltIsaJCaRrE7XtLCRVtOpGtdPO0jH0","y":"9xj9qfSrVKFqN3lnaNDXAclGGnfmU_j7xsEocZdYmPs"}
load crt ec_decrypt.crt key ec_decrypt.key jwt on
listen main-fe listen main-fe
bind "fd@${mainfe}" bind "fd@${mainfe}"
@ -84,6 +88,11 @@ haproxy h1 -conf {
http-request set-var(txn.decrypted) var(txn.jwe),jwt_decrypt_cert(txn.pem) http-request set-var(txn.decrypted) var(txn.jwe),jwt_decrypt_cert(txn.pem)
.if ssllib_name_startswith(AWS-LC)
acl aws_unmanaged var(txn.jwe),jwt_header_query('$.alg') -m end "A128KW" -m end "A192KW"
http-request set-var(txn.decrypted) str("AWS-LC UNMANAGED") if aws_unmanaged
.endif
http-after-response set-header X-Decrypted %[var(txn.decrypted)] http-after-response set-header X-Decrypted %[var(txn.decrypted)]
server s1 ${s1_addr}:${s1_port} server s1 ${s1_addr}:${s1_port}
@ -95,7 +104,7 @@ haproxy h1 -conf {
http-request set-var(txn.decrypted) var(txn.jwe),jwt_decrypt_jwk(txn.jwk) http-request set-var(txn.decrypted) var(txn.jwe),jwt_decrypt_jwk(txn.jwk)
.if ssllib_name_startswith(AWS-LC) .if ssllib_name_startswith(AWS-LC)
acl aws_unmanaged var(txn.jwe),jwt_header_query('$.alg') -m str "A128KW" acl aws_unmanaged var(txn.jwe),jwt_header_query('$.alg') -m end "A128KW" -m end "A192KW"
http-request set-var(txn.decrypted) str("AWS-LC UNMANAGED") if aws_unmanaged http-request set-var(txn.decrypted) str("AWS-LC UNMANAGED") if aws_unmanaged
.endif .endif
@ -262,3 +271,53 @@ client c8 -connect ${h1_mainfe_sock} {
expect resp.http.x-decrypted == "" expect resp.http.x-decrypted == ""
} -run } -run
# ECDH-ES
client c9 -connect ${h1_mainfe_sock} {
txreq -url "/jwk" -hdr "Authorization: Bearer eyJhbGciOiAiRUNESC1FUyIsICJlbmMiOiAiQTI1NkdDTSIsICJlcGsiOiB7Imt0eSI6ICJFQyIsICJjcnYiOiAiUC0yNTYiLCAieCI6ICJZVUg1VlJweURCb3FiUjVCUWlSMGV4anJ0bDdLb24yeTZkejFnM3NuNzZJIiwgInkiOiAiZld2RHFHb3pYNjJnMnRTS19oSkctWkVKNkFCcFhYTS1Tc1hPeE5KUXFtUSJ9fQ..0tN70AQ3P_4uEV4t.zkv7KfnUlDTKjJ82zKCMK_z7OEFk_euXGuJemShf8mnOeEUE4UN8wS5cRJzMQWxcY9d3dIvUCYx0HhzeoXnKqnkEU6be659IVtKpqtceLYKcIkpjj0XiaEalVqIKKXTU2NG2ldNsYwnEDN_XxMnIUPFOy3yJqpOfjf8v98ABYuTWfJVwk3tK9vYCj-ScCf2NK7cEIti_09VCsxMg7z0kvco5UaTXvDjEbPhj_EVfHoPlmDE6EuaO5OX5t3reOoJ1vsM2PEpADiYfmvSZxeWAmmtAH7cvrRIUCcy4Q5pNczh1Pmt0y-uJKtme16YWq8PxVtnb7lY9HDTuPeaMVqvMV6PlQ9vnfsirjpz72qx3ArAeXkIGJsPOGKfgCoW6sAWHQxCzvq8ek7zOaqTAo169PSdtxfBL4MJWxoLg38pODy4cjEGR71YYirthejEMgRs7G1A8ksxgs2bkYGInunUD_iAWkQzxYZhFlLRntWP1ikOKmx9gbqR6K9UiqCK1UG4NXF3o4OV34m-jw-cXMDF2JkekVK2-rhxTbXmqP-VhDrkQ2ANdk7fTW9elFYNisVzE1QjdClMKGhO1fdKiSJ9xSPo3W6pMuquYYN-XT1fLiu3GDtO4ELZWVdwmiucsxv9H2jzPwbhvbvlXwXsmyCBtvumcEUbiYCOIYvlddhTGjZHplvDU73O5SkxUYJTYh7H0DcSiZ-6tcWdRCs605xVZMJ_X91_gZ1tb2_df73lYT_tVo39kw78m3GVFBeK2Zy4JeLheo0fHE7n8lg13uwG77SHwrWSV61KKWhBPZR0bWGi8YvVHnqX0GWklIjpqjbIjYAk4baFv4MO4OvEkPxnGm64NNZWrGEA0U8eEHCgjF1ZagQFNb674Crgd-tRA0QPEAOc9NsnlK1Q-47KIgqNbwoc3VpbpHNLVJT4aKWV5q187YNxarbpeDqguh75M9AgbpT5bSDFhjF83f1kiEDgLdNTkAd-CPAzgtzaEAfxD1K4ViZZZ2DqXgw0PFTFZAWrWqv8Ydi61r5MJ.Srleju8Bifrc_6bqFPUF_w" \
-hdr "X-JWK: {\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"DxaAKzwruXJh4IkdieycIJER6w8M1TYMCV3qOa-l9CM\",\"y\":\"_kRI1aD7-PMFwhUpXmcRzw6hALF_xdKwADuKOM-xsak\",\"d\":\"SOu5eRc40yn5yVrg069VjWNH4wsoErN8_AxmH4cI88s\"}"
rxresp
expect resp.http.x-decrypted == "Sed ut perspiciatis unde omnis iste natus error sit voluptatem doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. porro quisquam est, qui dolorem ipsum quia dolor sit amet, adipisci velit, sed quia non numquam eius modi tempora incidunt ut dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in voluptate velit esse quam nihil molestiae consequatur, vel illum qui eum fugiat quo voluptas nulla pariatur?"
txreq -url "/jwk" -hdr "Authorization: Bearer eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkNCQy1IUzUxMiIsImVwayI6eyJjcnYiOiJQLTUyMSIsImt0eSI6IkVDIiwieCI6IkFYelVIT0hFdXU1RzlxSHBQOGdXelBub3FQNUpSVmNrYWZGb1VxV0FUQVd6b3FUN0tmX3V5WW5HSElaNkVXdUx0U0NrUnREUHE4WDJlcV9lSDc0QVRQZGwiLCJ5IjoiQVRxelFNWV9PUE9lWUZYNlpGN0l4ZkgwQ2x6RlRjZjVhaE1UTERmMHJYRkczNmdHN1lDMjR1Q2hrR2ZoZHlBT1RRY09kN1ZyQlM4clNZeC03R0hLbzNWNSJ9fQ..kTaw9v3MWCN78jq5OXTWZA.w4o_19dlHEFEhQ0GXI08x-vJnImL_mtZ_oHXTCvfCj_aCEDL4UuiaAU7-yvtM60G3HjNO6TTvmCdvHOTz6Ynrg.H-EbBpTyi5YXNT5DHSFiNBeBcdjmClR_LDARvak4qng" \
-hdr "X-JWK: {\"alg\":\"ECDH-ES\",\"crv\":\"P-521\",\"d\":\"AVBp1yn67_t0C8WfJnrhZsgy4TDkA9XktZnwAHcCTUMWTBCURXOjCNCIaCyE65xzIQbZUc9rO-B93XKFO81u8myd\",\"key_ops\":[\"wrapKey\",\"unwrapKey\"],\"kty\":\"EC\",\"x\":\"AByuEl5P9ledNRyj4EjTtQwDcsIYpbNzUqjri5o8GPGLzeTWUzjDBVt1ZyxKfK8VMVQbj8sIrBHncYUqM1Re3pSA\",\"y\":\"AW171IiyQSWx95A9uT1m76XPcAss3jeE7lHgw8mU7yIxSi_SItDYFixJ5Xtf2Vu2BLlmpR0on6VV1UUNIyPk6qwb\"}"
rxresp
expect resp.http.x-decrypted == "Random test message for ECDH-ES encrypted tokens"
txreq -url "/pem" -hdr "Authorization: Bearer eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTEyOENCQy1IUzI1NiIsImVwayI6eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6InhEcUZveF9oR3Q5VjZWSWZjRUpaU1VVTm1uT0V5Qk1BYzZybHlOV09lcjgiLCJ5IjoiUVZmdkstcVJ0V0J1Uk9XVzRnMmlVampqMFN3U1BzYjB6ZE10R0c2czBFUSJ9fQ..0ykoqdP2WMKra2VugMQMzg.dyCI6QGNIf-x4n0DIaXgVnGtoSCOD3sOX7I01djrFdNRRSmPnITcQiJn1lw1LbiZyqZxOLf_mJHw7BRrcgPxBG6gsP3oFBnLeXllcD6kuLtllVofaPDEKdr66W9dp6Cr.002j4NUlGTYz8d_0mTM38A" \
-hdr "X-PEM: ${testdir}/ec_decrypt.crt"
rxresp
expect resp.http.x-decrypted == "Random test message for ECDH-ES encrypted token (with some extra padding for good measure)"
} -run
# ECDH-ES+A___KW
client c10 -connect ${h1_mainfe_sock} {
# ECDH-ES+A128KW
txreq -url "/jwk" -hdr "Authorization: Bearer eyJhbGciOiJFQ0RILUVTK0ExMjhLVyIsImVuYyI6IkExMjhDQkMtSFMyNTYiLCJlcGsiOnsiY3J2IjoiUC0yNTYiLCJrdHkiOiJFQyIsIngiOiJtc2poQktWNW5oNnBjdjhoRnR0UDlFVXRzaURzWG83T3RCekVZYkVJM1EwIiwieSI6IloxQ3FPQlEya1RNR1lENWdMUWJCaHB0MzRKRkR3dW5TX2ZzSmhsMlc1OWcifX0.5l7YaATvAWFJnWK_HsBPmawJ0RMqrkiwyZ9xAuiYCFSiqWWSr8D82A.0sa1s5V2RcDf0FW6hA1lig.z2DVLxtHeY1fPp6dJHiHEuHLVIQHQ10GfYXeFxwNE7JGyto-D3K1elHQn0Yq4Pitaheja21gnXkJajXhOA0rwQ.YmpToFWmj8XQrXMeXTa9eQ" \
-hdr "X-JWK: {\"crv\":\"P-256\",\"d\":\"6qbbYYII1zqqmlDHhTwJt-JYBe-ELI02yAecAx-nD4w\",\"kty\":\"EC\",\"x\":\"bASil7YpthReLltIsaJCaRrE7XtLCRVtOpGtdPO0jH0\",\"y\":\"9xj9qfSrVKFqN3lnaNDXAclGGnfmU_j7xsEocZdYmPs\"}"
rxresp
expect resp.http.x-decrypted ~ "(Random test message for ECDH-ES encrypted tokens|AWS-LC UNMANAGED)"
txreq -url "/pem" -hdr "Authorization: Bearer eyJhbGciOiJFQ0RILUVTK0ExMjhLVyIsImVuYyI6IkExMjhDQkMtSFMyNTYiLCJlcGsiOnsiY3J2IjoiUC0yNTYiLCJrdHkiOiJFQyIsIngiOiJKeUJzcDZtZjVCMWROYUk0ZGppX2pnMm9NdFRRQUd4akxTekdGbGJ0dXZZIiwieSI6IlFfZnlBUmZiMGpjWXFtSTZUNEdXTXA1U2dGYXZiQ3lGUGF3OHhab1BIYzAifX0.JKrKKRF9QdxUyv0KX-MV11eHpP2Vz8Amdh8j3ipd_QP57jkN-OWRCQ.CjpnSVRVV51C10cUwCTaXA.bliaBk7mGYIOGdvgiMg481iC8GiOarRrjIkUgEBuqiSJENmOi90IXgnoVp4qQdi70bJVBNuCYP7Q9sLzZc4X2g.C_TCuAfAH5020v-NdR91BA" \
-hdr "X-PEM: ${testdir}/ec_decrypt.crt"
rxresp
expect resp.http.x-decrypted ~ "(Random test message for ECDH-ES\\+A128KW encrypted token|AWS-LC UNMANAGED)"
# ECDH-ES+A192KW
txreq -url "/jwk" -hdr "Authorization: Bearer eyJhbGciOiJFQ0RILUVTK0ExOTJLVyIsImVuYyI6IkExOTJDQkMtSFMzODQiLCJlcGsiOnsiY3J2IjoiUC0zODQiLCJrdHkiOiJFQyIsIngiOiJDcTd3Y0MzUm92VFRZSTMzLU9DcXBocjFlN1NzeEZWY0dOQXhEOEpWZHBRQmROaGg3Z2dLNTJKVkJ1RF9uZXVHIiwieSI6IjlaLU1MV09TQ3VZd0JZVTEtcTd2YUREWUZ1WFhqc1EwSmxpWllLVmdOU0dqVHVLY3VXQnJHemV2RzZEeGgyRHQifX0.75lt6Ixq6UhlN8uiaEphy8SiqEVsuD4Rc3QbFcmP7MJUTyt15LcZ3y-M7TJeNBh3Ajy_6K2WooU.cO9tUaQ2eVo0tIuOqb5_Bw.HQ6DqnLhW2Ad0c78WFGgwCStefYdL37xmh2Fa2mCsVNW5q0K3-xeDHYuIP9Q5xBYEY70U6wV5a0iVN87ii_iMA.feLteQh1ickYVJ2ZZ2whoVzNGRHgUpjp" \
-hdr "X-JWK: {\"alg\":\"ECDH-ES+A192KW\",\"crv\":\"P-384\",\"d\":\"pj6xIezfwtUakkkLtbRQ9FmN6uN1YJ-TSBkWn4awuDfWiHgqpQHA7_L95Hjks1cK\",\"key_ops\":[\"wrapKey\",\"unwrapKey\"],\"kty\":\"EC\",\"x\":\"JO3ojbUYOzoSb-7lAy-c7VhDIjhEtg4zrPn_NJKuGhat-cuI1c4LvOj3n8p3j4bn\",\"y\":\"CA3i4pN7t6liWxQXyxdDp9t79B8uWuubGADJuGn_2_yl6pufhnQ30OBA590fOtEm\"}"
rxresp
expect resp.http.x-decrypted ~ "(Random test message for ECDH-ES\\+A192KW encrypted tokens|AWS-LC UNMANAGED)"
# ECDH-ES+A256KW
txreq -url "/jwk" -hdr "Authorization: Bearer eyJhbGciOiJFQ0RILUVTK0EyNTZLVyIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJlcGsiOnsiY3J2IjoiUC01MjEiLCJrdHkiOiJFQyIsIngiOiJBTFZuZXN6Tl93WVJSWVYtblp3dy1sSkVDTXB2eE1iSENXX3BjY3EyWlF2eFdsNzVKdm5TM3lKbjgzcTE1MlpnWU4zTTB4SUhzQmw1empWZS02OGR4TThwIiwieSI6IkFUX2pGel94RGt0VFY4WWYzZlo1MnRvbE5QWkwwNXlwa0dVTThPWFRNZTBaaVNfYnIzaS0xNHFlWG1OcjA3TFFjNUZMX1VTQkE5WmlyWGRaZkVLUnFqNmEifX0.MqGFvMzpIlwQHeXgPucBkXmS2BaXr2ByUugzD31XrPtxwlWw96vOmfcjSHvda2FGJ1u6InaMMVZMMp75P6AF0kvk8vuM7QF2.kHYblcqwHgXv0xRQrLHwoA.gwFUyTx3RRHWvmqyUL5N6W8HcwbNc1hPTImQPoCNPv6rkhzV1obikVj7sNuTh3Po0nBu2QCKrt-GjJTlD4Q5kw.Q_YZWSkVVxv1rcpySgENN3ZPp-chIYoCGC070kkqiXc" \
-hdr "X-JWK: {\"alg\":\"ECDH-ES+A256KW\",\"crv\":\"P-521\",\"d\":\"AGGLpIzSL1jE34wGa-owWCVt2rgk8j3jqh33QQFKwYCJ9abp3vROyQ-dNv6j6PjrnF1EFyY9dDzChNpWmzoOZAp3\",\"key_ops\":[\"wrapKey\",\"unwrapKey\"],\"kty\":\"EC\",\"x\":\"AD0EIUE6Bt_TDcyOPM6VchRocp7AFSeVd6XkVALWf8AFebeMgKIvJsCsGeRdPTO3vWWrR5AOvvpiBfurb9M9Tus-\",\"y\":\"AOeI5d0iF463g3DolhmVFn6MWk764ONuXRexLApjN-Q6_RkcnCieRSZzqqSPMYuEn-N3i4aYfiEPZV0jk8oZKQMQ\"}"
rxresp
expect resp.http.x-decrypted == "Random test message for ECDH-ES+A256KW encrypted tokens"
} -run

View file

@ -0,0 +1,88 @@
varnishtest "Delete backend via cli"
feature ignore_unknown_macro
haproxy h1 -conf {
global
.if feature(THREAD)
thread-groups 1
.endif
defaults
mode http
timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
timeout client "${HAPROXY_TEST_TIMEOUT-5s}"
timeout server "${HAPROXY_TEST_TIMEOUT-5s}"
frontend fe
bind "fd@${feS}"
use_backend be_ref
listen li
bind "fd@${feli}"
backend be_ref
backend be
server s1 ${s1_addr}:${s1_port} disabled
# Defaults with tcp-check rules in it
# Currently this is the only case of runtime ref on an unnamed default
defaults
mode http
timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
timeout client "${HAPROXY_TEST_TIMEOUT-5s}"
timeout server "${HAPROXY_TEST_TIMEOUT-5s}"
option httpchk GET / HTTP/1.1
backend be_unnamed_def_ref
backend be_unnamed_def_ref2
} -start
haproxy h1 -cli {
send "experimental-mode on; del backend other"
expect ~ "No such backend."
send "experimental-mode on; del backend li"
expect ~ "Cannot delete a listen section."
send "experimental-mode on; del backend be_ref"
expect ~ "This proxy cannot be removed at runtime due to other configuration elements pointing to it."
send "show stat be 2 -1"
expect ~ "be,BACKEND,"
send "experimental-mode on; del backend be"
expect ~ "Backend must be unpublished prior to its deletion."
send "unpublish backend be;"
expect ~ ".*"
send "experimental-mode on; del backend be"
expect ~ "Only a backend without server can be deleted."
send "del server be/s1"
expect ~ ".*"
send "experimental-mode on; del backend be"
expect ~ "Backend deleted."
send "show stat be 2 -1"
expect !~ "be,BACKEND,"
}
haproxy h1 -cli {
send "show stat be_unnamed_def_ref 2 -1"
expect ~ "be_unnamed_def_ref,BACKEND,"
send "unpublish backend be_unnamed_def_ref;"
expect ~ ".*"
send "experimental-mode on; del backend be_unnamed_def_ref"
expect ~ "Backend deleted."
send "show stat be_unnamed_def_ref 2 -1"
expect !~ "be_unnamed_def_ref,BACKEND,"
send "unpublish backend be_unnamed_def_ref2;"
expect ~ ".*"
send "experimental-mode on; del backend be_unnamed_def_ref2"
expect ~ "Backend deleted."
}

View file

@ -28,7 +28,7 @@
# show-backports -q -m -r hapee-r2 hapee-r1 # show-backports -q -m -r hapee-r2 hapee-r1
USAGE="Usage: ${0##*/} [-q] [-H] [-m] [-u] [-r reference] [-l logexpr] [-s subject] [-b base] {branch|range} [...] [-- file*]" USAGE="Usage: ${0##*/} [-q] [-H] [-m] [-u] [-L] [-r reference] [-l logexpr] [-s subject] [-b base] {branch|range} [...] [-- file*]"
BASES=( ) BASES=( )
BRANCHES=( ) BRANCHES=( )
REF= REF=
@ -39,6 +39,7 @@ SUBJECT=
MISSING= MISSING=
UPSTREAM= UPSTREAM=
BODYHASH= BODYHASH=
SINCELAST=
die() { die() {
[ "$#" -eq 0 ] || echo "$*" >&2 [ "$#" -eq 0 ] || echo "$*" >&2
@ -70,7 +71,7 @@ dump_commit_matrix() {
count=0 count=0
# now look up commits # now look up commits
while read ref subject; do while read ref subject; do
if [ -n "$MISSING" -a "${subject:0:9}" = "[RELEASE]" ]; then if [ -n "$MISSING" -o -n "$SINCELAST" ] && [ "${subject:0:9}" = "[RELEASE]" ]; then
continue continue
fi fi
@ -153,6 +154,7 @@ while [ -n "$1" -a -z "${1##-*}" ]; do
-m) MISSING=1 ; shift ;; -m) MISSING=1 ; shift ;;
-u) UPSTREAM=1 ; shift ;; -u) UPSTREAM=1 ; shift ;;
-H) BODYHASH=1 ; shift ;; -H) BODYHASH=1 ; shift ;;
-L) SINCELAST=1 ; shift ;;
-h|--help) quit "$USAGE" ;; -h|--help) quit "$USAGE" ;;
*) die "$USAGE" ;; *) die "$USAGE" ;;
esac esac
@ -255,7 +257,7 @@ if [ -z "$BASE" -a -n "$MISSING" ]; then
fi fi
if [ -z "$BASE" ]; then if [ -z "$BASE" ]; then
err "Warning! No base specified, looking for common ancestor." [ "$QUIET" != "" ] || err "Warning! No base specified, looking for common ancestor."
BASE=$(git merge-base --all "$REF" "${BRANCHES[@]}") BASE=$(git merge-base --all "$REF" "${BRANCHES[@]}")
if [ -z "$BASE" ]; then if [ -z "$BASE" ]; then
die "Couldn't find a common ancestor between these branches" die "Couldn't find a common ancestor between these branches"
@ -297,9 +299,23 @@ dump_commit_matrix | column -t | \
( (
left_commits=( ) left_commits=( )
right_commits=( ) right_commits=( )
since_last=( )
last_bkp=$BASE
while read line; do while read line; do
# append the subject at the end of the line # append the subject at the end of the line
set -- $line set -- $line
if [ -n "$SINCELAST" ]; then
if [ "${line::1}" = ":" ]; then
continue
fi
if [ "$2" != "-" ]; then
last_bkp="$1"
since_last=( )
else
since_last[${#since_last[@]}]="$1"
fi
continue
fi
echo -n "$line " echo -n "$line "
if [ "${line::1}" = ":" ]; then if [ "${line::1}" = ":" ]; then
echo "---- Subject ----" echo "---- Subject ----"
@ -315,7 +331,14 @@ dump_commit_matrix | column -t | \
right_commits[${#right_commits[@]}]="$comm" right_commits[${#right_commits[@]}]="$comm"
fi fi
done done
if [ -n "$MISSING" -a ${#left_commits[@]} -eq 0 ]; then if [ -n "$SINCELAST" -a ${#since_last[@]} -eq 0 ]; then
echo "No new commit upstream since last commit $last_bkp."
elif [ -n "$SINCELAST" ]; then
echo "Found ${#since_last[@]} commit(s) added to branch $REF since last backported commit $last_bkp:"
echo
echo " git cherry-pick -sx ${since_last[@]}"
echo
elif [ -n "$MISSING" -a ${#left_commits[@]} -eq 0 ]; then
echo "No missing commit to apply." echo "No missing commit to apply."
elif [ -n "$MISSING" ]; then elif [ -n "$MISSING" ]; then
echo echo

View file

@ -14,12 +14,8 @@
#include <haproxy/acme-t.h> #include <haproxy/acme-t.h>
#include <haproxy/cli.h>
#include <haproxy/cfgparse.h>
#include <haproxy/errors.h>
#include <haproxy/jws.h>
#include <haproxy/base64.h> #include <haproxy/base64.h>
#include <haproxy/intops.h>
#include <haproxy/cfgparse.h> #include <haproxy/cfgparse.h>
#include <haproxy/cli.h> #include <haproxy/cli.h>
#include <haproxy/errors.h> #include <haproxy/errors.h>
@ -271,7 +267,6 @@ static int cfg_parse_acme(const char *file, int linenum, char **args, int kwm)
mark_tainted(TAINTED_CONFIG_EXP_KW_DECLARED); mark_tainted(TAINTED_CONFIG_EXP_KW_DECLARED);
if (strcmp(args[0], "acme") == 0) { if (strcmp(args[0], "acme") == 0) {
struct acme_cfg *tmp_acme = acme_cfgs;
if (alertif_too_many_args(1, file, linenum, args, &err_code)) if (alertif_too_many_args(1, file, linenum, args, &err_code))
goto out; goto out;
@ -297,7 +292,7 @@ static int cfg_parse_acme(const char *file, int linenum, char **args, int kwm)
* name */ * name */
err_code |= ERR_ALERT | ERR_FATAL; err_code |= ERR_ALERT | ERR_FATAL;
ha_alert("parsing [%s:%d]: acme section '%s' already exists (%s:%d).\n", ha_alert("parsing [%s:%d]: acme section '%s' already exists (%s:%d).\n",
file, linenum, args[1], tmp_acme->filename, tmp_acme->linenum); file, linenum, args[1], cur_acme->filename, cur_acme->linenum);
goto out; goto out;
} }
@ -398,7 +393,7 @@ static int cfg_parse_acme_kws(char **args, int section_type, struct proxy *curpx
err_code |= ERR_ALERT | ERR_FATAL; err_code |= ERR_ALERT | ERR_FATAL;
goto out; goto out;
} }
if (alertif_too_many_args(2, file, linenum, args, &err_code)) if (alertif_too_many_args(1, file, linenum, args, &err_code))
goto out; goto out;
ha_free(&cur_acme->account.file); ha_free(&cur_acme->account.file);
@ -415,7 +410,7 @@ static int cfg_parse_acme_kws(char **args, int section_type, struct proxy *curpx
goto out; goto out;
} }
if (alertif_too_many_args(2, file, linenum, args, &err_code)) if (alertif_too_many_args(1, file, linenum, args, &err_code))
goto out; goto out;
ha_free(&cur_acme->challenge); ha_free(&cur_acme->challenge);
@ -883,7 +878,7 @@ static void acme_ctx_destroy(struct acme_ctx *ctx)
istfree(&auth->auth); istfree(&auth->auth);
istfree(&auth->chall); istfree(&auth->chall);
istfree(&auth->token); istfree(&auth->token);
istfree(&auth->token); istfree(&auth->dns);
next = auth->next; next = auth->next;
free(auth); free(auth);
auth = next; auth = next;
@ -1193,7 +1188,7 @@ int acme_res_certificate(struct task *task, struct acme_ctx *ctx, char **errmsg)
} }
/* get the next retry timing */ /* get the next retry timing */
if (isteqi(hdr->n, ist("Retry-After"))) { if (isteqi(hdr->n, ist("Retry-After"))) {
ctx->retryafter = atol(hdr->v.ptr); ctx->retryafter = __strl2uic(hdr->v.ptr, hdr->v.len);
} }
} }
@ -1266,7 +1261,7 @@ int acme_res_chkorder(struct task *task, struct acme_ctx *ctx, char **errmsg)
} }
/* get the next retry timing */ /* get the next retry timing */
if (isteqi(hdr->n, ist("Retry-After"))) { if (isteqi(hdr->n, ist("Retry-After"))) {
ctx->retryafter = atol(hdr->v.ptr); ctx->retryafter = __strl2uic(hdr->v.ptr, hdr->v.len);
} }
} }
@ -1306,7 +1301,6 @@ int acme_res_chkorder(struct task *task, struct acme_ctx *ctx, char **errmsg)
goto error; goto error;
}; };
out:
ret = 0; ret = 0;
error: error:
@ -1350,7 +1344,6 @@ int acme_req_finalize(struct task *task, struct acme_ctx *ctx, char **errmsg)
csr->data = ret; csr->data = ret;
chunk_printf(req_in, "{ \"csr\": \"%.*s\" }", (int)csr->data, csr->area); chunk_printf(req_in, "{ \"csr\": \"%.*s\" }", (int)csr->data, csr->area);
OPENSSL_free(data);
if (acme_jws_payload(req_in, ctx->nonce, ctx->finalize, ctx->cfg->account.pkey, ctx->kid, req_out, errmsg) != 0) if (acme_jws_payload(req_in, ctx->nonce, ctx->finalize, ctx->cfg->account.pkey, ctx->kid, req_out, errmsg) != 0)
@ -1359,11 +1352,12 @@ int acme_req_finalize(struct task *task, struct acme_ctx *ctx, char **errmsg)
if (acme_http_req(task, ctx, ctx->finalize, HTTP_METH_POST, hdrs, ist2(req_out->area, req_out->data))) if (acme_http_req(task, ctx, ctx->finalize, HTTP_METH_POST, hdrs, ist2(req_out->area, req_out->data)))
goto error; goto error;
ret = 0; ret = 0;
goto out;
error: error:
memprintf(errmsg, "couldn't request the finalize URL"); memprintf(errmsg, "couldn't request the finalize URL");
out:
OPENSSL_free(data);
free_trash_chunk(req_in); free_trash_chunk(req_in);
free_trash_chunk(req_out); free_trash_chunk(req_out);
free_trash_chunk(csr); free_trash_chunk(csr);
@ -1397,7 +1391,7 @@ int acme_res_finalize(struct task *task, struct acme_ctx *ctx, char **errmsg)
} }
/* get the next retry timing */ /* get the next retry timing */
if (isteqi(hdr->n, ist("Retry-After"))) { if (isteqi(hdr->n, ist("Retry-After"))) {
ctx->retryafter = atol(hdr->v.ptr); ctx->retryafter = __strl2uic(hdr->v.ptr, hdr->v.len);
} }
} }
@ -1415,7 +1409,7 @@ int acme_res_finalize(struct task *task, struct acme_ctx *ctx, char **errmsg)
memprintf(errmsg, "invalid HTTP status code %d when getting Finalize URL", hc->res.status); memprintf(errmsg, "invalid HTTP status code %d when getting Finalize URL", hc->res.status);
goto error; goto error;
} }
out:
ret = 0; ret = 0;
error: error:
@ -1458,9 +1452,10 @@ int acme_req_challenge(struct task *task, struct acme_ctx *ctx, struct acme_auth
goto error; goto error;
ret = 0; ret = 0;
goto out;
error: error:
memprintf(errmsg, "couldn't generate the Challenge request"); memprintf(errmsg, "couldn't generate the Challenge request");
out:
free_trash_chunk(req_in); free_trash_chunk(req_in);
free_trash_chunk(req_out); free_trash_chunk(req_out);
@ -1497,7 +1492,7 @@ enum acme_ret acme_res_challenge(struct task *task, struct acme_ctx *ctx, struct
} }
/* get the next retry timing */ /* get the next retry timing */
if (isteqi(hdr->n, ist("Retry-After"))) { if (isteqi(hdr->n, ist("Retry-After"))) {
ctx->retryafter = atol(hdr->v.ptr); ctx->retryafter = __strl2uic(hdr->v.ptr, hdr->v.len);
} }
} }
@ -1576,6 +1571,8 @@ int acme_post_as_get(struct task *task, struct acme_ctx *ctx, struct ist url, ch
ret = 0; ret = 0;
goto end;
error_jws: error_jws:
memprintf(errmsg, "couldn't generate the JWS token: %s", errmsg ? *errmsg : ""); memprintf(errmsg, "couldn't generate the JWS token: %s", errmsg ? *errmsg : "");
goto end; goto end;
@ -1621,7 +1618,7 @@ int acme_res_auth(struct task *task, struct acme_ctx *ctx, struct acme_auth *aut
} }
/* get the next retry timing */ /* get the next retry timing */
if (isteqi(hdr->n, ist("Retry-After"))) { if (isteqi(hdr->n, ist("Retry-After"))) {
ctx->retryafter = atol(hdr->v.ptr); ctx->retryafter = __strl2uic(hdr->v.ptr, hdr->v.len);
} }
} }
@ -1657,6 +1654,19 @@ int acme_res_auth(struct task *task, struct acme_ctx *ctx, struct acme_auth *aut
auth->dns = istdup(ist2(t2->area, t2->data)); auth->dns = istdup(ist2(t2->area, t2->data));
ret = mjson_get_string(hc->res.buf.area, hc->res.buf.data, "$.status", trash.area, trash.size);
if (ret == -1) {
memprintf(errmsg, "couldn't get a \"status\" from Authorization URL \"%s\"", auth->auth.ptr);
goto error;
}
trash.data = ret;
/* if auth is already valid we need to skip solving challenges */
if (strncasecmp("valid", trash.area, trash.data) == 0) {
auth->validated = 1;
goto out;
}
/* get the multiple challenges and select the one from the configuration */ /* get the multiple challenges and select the one from the configuration */
for (i = 0; ; i++) { for (i = 0; ; i++) {
int ret; int ret;
@ -1816,9 +1826,10 @@ int acme_req_neworder(struct task *task, struct acme_ctx *ctx, char **errmsg)
goto error; goto error;
ret = 0; ret = 0;
goto out;
error: error:
memprintf(errmsg, "couldn't generate the newOrder request"); memprintf(errmsg, "couldn't generate the newOrder request");
out:
free_trash_chunk(req_in); free_trash_chunk(req_in);
free_trash_chunk(req_out); free_trash_chunk(req_out);
@ -1852,7 +1863,7 @@ int acme_res_neworder(struct task *task, struct acme_ctx *ctx, char **errmsg)
} }
/* get the next retry timing */ /* get the next retry timing */
if (isteqi(hdr->n, ist("Retry-After"))) { if (isteqi(hdr->n, ist("Retry-After"))) {
ctx->retryafter = atol(hdr->v.ptr); ctx->retryafter = __strl2uic(hdr->v.ptr, hdr->v.len);
} }
/* get the order URL */ /* get the order URL */
if (isteqi(hdr->n, ist("Location"))) { if (isteqi(hdr->n, ist("Location"))) {
@ -1926,7 +1937,6 @@ int acme_res_neworder(struct task *task, struct acme_ctx *ctx, char **errmsg)
goto error; goto error;
} }
out:
ret = 0; ret = 0;
error: error:
@ -1978,9 +1988,10 @@ int acme_req_account(struct task *task, struct acme_ctx *ctx, int newaccount, ch
goto error; goto error;
ret = 0; ret = 0;
goto out;
error: error:
memprintf(errmsg, "couldn't generate the newAccount request"); memprintf(errmsg, "couldn't generate the newAccount request");
out:
free_trash_chunk(req_in); free_trash_chunk(req_in);
free_trash_chunk(req_out); free_trash_chunk(req_out);
@ -2012,7 +2023,7 @@ int acme_res_account(struct task *task, struct acme_ctx *ctx, int newaccount, ch
} }
/* get the next retry timing */ /* get the next retry timing */
if (isteqi(hdr->n, ist("Retry-After"))) { if (isteqi(hdr->n, ist("Retry-After"))) {
ctx->retryafter = atol(hdr->v.ptr); ctx->retryafter = __strl2uic(hdr->v.ptr, hdr->v.len);
} }
if (isteqi(hdr->n, ist("Replay-Nonce"))) { if (isteqi(hdr->n, ist("Replay-Nonce"))) {
istfree(&ctx->nonce); istfree(&ctx->nonce);
@ -2266,6 +2277,14 @@ re:
break; break;
case ACME_CHALLENGE: case ACME_CHALLENGE:
if (http_st == ACME_HTTP_REQ) { if (http_st == ACME_HTTP_REQ) {
/* if challenge is already validated we skip this stage */
if (ctx->next_auth->validated) {
if ((ctx->next_auth = ctx->next_auth->next) == NULL) {
st = ACME_CHKCHALLENGE;
ctx->next_auth = ctx->auths;
}
goto nextreq;
}
/* if the challenge is not ready, wait to be wakeup */ /* if the challenge is not ready, wait to be wakeup */
if (!ctx->next_auth->ready) if (!ctx->next_auth->ready)
@ -2295,6 +2314,14 @@ re:
break; break;
case ACME_CHKCHALLENGE: case ACME_CHKCHALLENGE:
if (http_st == ACME_HTTP_REQ) { if (http_st == ACME_HTTP_REQ) {
/* if challenge is already validated we skip this stage */
if (ctx->next_auth->validated) {
if ((ctx->next_auth = ctx->next_auth->next) == NULL)
st = ACME_FINALIZE;
goto nextreq;
}
if (acme_post_as_get(task, ctx, ctx->next_auth->chall, &errmsg) != 0) if (acme_post_as_get(task, ctx, ctx->next_auth->chall, &errmsg) != 0)
goto retry; goto retry;
} }
@ -2529,9 +2556,9 @@ X509_REQ *acme_x509_req(EVP_PKEY *pkey, char **san)
{ {
struct buffer *san_trash = NULL; struct buffer *san_trash = NULL;
X509_REQ *x = NULL; X509_REQ *x = NULL;
X509_NAME *nm; X509_NAME *nm = NULL;
STACK_OF(X509_EXTENSION) *exts = NULL; STACK_OF(X509_EXTENSION) *exts = NULL;
X509_EXTENSION *ext_san; X509_EXTENSION *ext_san = NULL;
char *str_san = NULL; char *str_san = NULL;
int i = 0; int i = 0;
@ -2562,26 +2589,36 @@ X509_REQ *acme_x509_req(EVP_PKEY *pkey, char **san)
for (i = 0; san[i]; i++) { for (i = 0; san[i]; i++) {
chunk_appendf(san_trash, "%sDNS:%s", i ? "," : "", san[i]); chunk_appendf(san_trash, "%sDNS:%s", i ? "," : "", san[i]);
} }
str_san = my_strndup(san_trash->area, san_trash->data); if ((str_san = my_strndup(san_trash->area, san_trash->data)) == NULL)
goto error;
if ((ext_san = X509V3_EXT_conf_nid(NULL, NULL, NID_subject_alt_name, str_san)) == NULL) if ((ext_san = X509V3_EXT_conf_nid(NULL, NULL, NID_subject_alt_name, str_san)) == NULL)
goto error; goto error;
if (!sk_X509_EXTENSION_push(exts, ext_san)) if (!sk_X509_EXTENSION_push(exts, ext_san))
goto error; goto error;
ext_san = NULL; /* handle double-free upon error */
if (!X509_REQ_add_extensions(x, exts)) if (!X509_REQ_add_extensions(x, exts))
goto error; goto error;
sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free);
if (!X509_REQ_sign(x, pkey, EVP_sha256())) if (!X509_REQ_sign(x, pkey, EVP_sha256()))
goto error; goto error;
sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free);
X509_NAME_free(nm);
free(str_san);
free_trash_chunk(san_trash); free_trash_chunk(san_trash);
return x; return x;
error: error:
X509_EXTENSION_free(ext_san);
sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free);
X509_REQ_free(x);
X509_NAME_free(nm);
free(str_san);
free_trash_chunk(san_trash); free_trash_chunk(san_trash);
return NULL; return NULL;
@ -2630,7 +2667,7 @@ EVP_PKEY *acme_gen_tmp_pkey()
/* start an ACME task */ /* start an ACME task */
static int acme_start_task(struct ckch_store *store, char **errmsg) static int acme_start_task(struct ckch_store *store, char **errmsg)
{ {
struct task *task; struct task *task = NULL;
struct acme_ctx *ctx = NULL; struct acme_ctx *ctx = NULL;
struct acme_cfg *cfg; struct acme_cfg *cfg;
struct ckch_store *newstore = NULL; struct ckch_store *newstore = NULL;
@ -2715,6 +2752,8 @@ err:
HA_RWLOCK_WRUNLOCK(OTHER_LOCK, &acme_lock); HA_RWLOCK_WRUNLOCK(OTHER_LOCK, &acme_lock);
acme_ctx_destroy(ctx); acme_ctx_destroy(ctx);
} }
if (task)
task_destroy(task);
memprintf(errmsg, "%sCan't start the ACME client.", *errmsg ? *errmsg : ""); memprintf(errmsg, "%sCan't start the ACME client.", *errmsg ? *errmsg : "");
return 1; return 1;
} }
@ -2724,7 +2763,10 @@ static int cli_acme_renew_parse(char **args, char *payload, struct appctx *appct
struct ckch_store *store = NULL; struct ckch_store *store = NULL;
char *errmsg = NULL; char *errmsg = NULL;
if (!*args[1]) { if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
return 1;
if (!*args[2]) {
memprintf(&errmsg, ": not enough parameters\n"); memprintf(&errmsg, ": not enough parameters\n");
goto err; goto err;
} }
@ -2763,8 +2805,11 @@ static int cli_acme_chall_ready_parse(char **args, char *payload, struct appctx
int remain = 0; int remain = 0;
struct ebmb_node *node = NULL; struct ebmb_node *node = NULL;
if (!*args[2] && !*args[3] && !*args[4]) { if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
memprintf(&msg, ": not enough parameters\n"); return 1;
if (!*args[2] || !*args[3] || !*args[4]) {
memprintf(&msg, "Not enough parameters: \"acme challenge_ready <certfile> domain <domain>\"\n");
goto err; goto err;
} }
@ -2885,8 +2930,12 @@ end:
return 1; return 1;
} }
static int cli_acme_ps(char **args, char *payload, struct appctx *appctx, void *private) static int cli_acme_parse_status(char **args, char *payload, struct appctx *appctx, void *private)
{ {
if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
return 1;
return 0; return 0;
} }
@ -2894,7 +2943,7 @@ static int cli_acme_ps(char **args, char *payload, struct appctx *appctx, void *
static struct cli_kw_list cli_kws = {{ },{ static struct cli_kw_list cli_kws = {{ },{
{ { "acme", "renew", NULL }, "acme renew <certfile> : renew a certificate using the ACME protocol", cli_acme_renew_parse, NULL, NULL, NULL, 0 }, { { "acme", "renew", NULL }, "acme renew <certfile> : renew a certificate using the ACME protocol", cli_acme_renew_parse, NULL, NULL, NULL, 0 },
{ { "acme", "status", NULL }, "acme status : show status of certificates configured with ACME", cli_acme_ps, cli_acme_status_io_handler, NULL, NULL, 0 }, { { "acme", "status", NULL }, "acme status : show status of certificates configured with ACME", cli_acme_parse_status, cli_acme_status_io_handler, NULL, NULL, 0 },
{ { "acme", "challenge_ready", NULL }, "acme challenge_ready <certfile> domain <domain> : notify HAProxy that the ACME challenge is ready", cli_acme_chall_ready_parse, NULL, NULL, NULL, 0 }, { { "acme", "challenge_ready", NULL }, "acme challenge_ready <certfile> domain <domain> : notify HAProxy that the ACME challenge is ready", cli_acme_chall_ready_parse, NULL, NULL, NULL, 0 },
{ { NULL }, NULL, NULL, NULL } { { NULL }, NULL, NULL, NULL }
}}; }};

View file

@ -25,7 +25,8 @@
/* Check an action ruleset validity. It returns the number of error encountered /* Check an action ruleset validity. It returns the number of error encountered
* and err_code is updated if a warning is emitted. * and err_code is updated if a warning is emitted. It also takes this
* opportunity for filling the execution context based on available info.
*/ */
int check_action_rules(struct list *rules, struct proxy *px, int *err_code) int check_action_rules(struct list *rules, struct proxy *px, int *err_code)
{ {
@ -40,6 +41,13 @@ int check_action_rules(struct list *rules, struct proxy *px, int *err_code)
} }
*err_code |= warnif_tcp_http_cond(px, rule->cond); *err_code |= warnif_tcp_http_cond(px, rule->cond);
ha_free(&errmsg); ha_free(&errmsg);
if (!rule->exec_ctx.type) {
if (rule->kw && rule->kw->exec_ctx.type)
rule->exec_ctx = rule->kw->exec_ctx;
else if (rule->action_ptr)
rule->exec_ctx = EXEC_CTX_MAKE(TH_EX_CTX_FUNC, rule->action_ptr);
}
} }
return err; return err;
@ -378,3 +386,24 @@ void dump_act_rules(const struct list *rules, const char *pfx)
(akwn->flags & KWF_MATCH_PREFIX) ? "*" : ""); (akwn->flags & KWF_MATCH_PREFIX) ? "*" : "");
} }
} }
/* adds the keyword list kw_list to the head <head> */
void act_add_list(struct list *head, struct action_kw_list *kw_list)
{
int i;
for (i = 0; kw_list->kw[i].kw != NULL; i++) {
/* store declaration file/line if known */
if (kw_list->kw[i].exec_ctx.type)
continue;
if (caller_initcall) {
kw_list->kw[i].exec_ctx.type = TH_EX_CTX_INITCALL;
kw_list->kw[i].exec_ctx.initcall = caller_initcall;
} else {
kw_list->kw[i].exec_ctx.type = TH_EX_CTX_ACTION;
kw_list->kw[i].exec_ctx.action_kwl = kw_list;
}
}
LIST_APPEND(head, &kw_list->list);
}

View file

@ -29,8 +29,11 @@ struct show_prof_ctx {
int dump_step; /* 0,1,2,4,5,6; see cli_iohandler_show_profiling() */ int dump_step; /* 0,1,2,4,5,6; see cli_iohandler_show_profiling() */
int linenum; /* next line to be dumped (starts at 0) */ int linenum; /* next line to be dumped (starts at 0) */
int maxcnt; /* max line count per step (0=not set) */ int maxcnt; /* max line count per step (0=not set) */
int by_what; /* 0=sort by usage, 1=sort by address, 2=sort by time */ int by_what; /* 0=sort by usage, 1=sort by address, 2=sort by time, 3=sort by ctx */
int aggr; /* 0=dump raw, 1=aggregate on callee */ int aggr; /* 0=dump raw, 1=aggregate on callee */
/* 4-byte hole here */
struct sched_activity *tmp_activity; /* dynamically allocated during dumps */
struct memprof_stats *tmp_memstats; /* dynamically allocated during dumps */
}; };
/* CLI context for the "show activity" command */ /* CLI context for the "show activity" command */
@ -299,13 +302,18 @@ struct memprof_stats *memprof_get_bin(const void *ra, enum memprof_method meth)
int retries = 16; // up to 16 consecutive entries may be tested. int retries = 16; // up to 16 consecutive entries may be tested.
const void *old; const void *old;
unsigned int bin; unsigned int bin;
ullong hash;
if (unlikely(!ra)) { if (unlikely(!ra)) {
bin = MEMPROF_HASH_BUCKETS; bin = MEMPROF_HASH_BUCKETS;
goto leave; goto leave;
} }
bin = ptr_hash(ra, MEMPROF_HASH_BITS); hash = _ptr2_hash_arg(ra, th_ctx->exec_ctx.pointer, th_ctx->exec_ctx.type);
for (; memprof_stats[bin].caller != ra; bin = (bin + 1) & (MEMPROF_HASH_BUCKETS - 1)) { for (bin = _ptr_hash_reduce(hash, MEMPROF_HASH_BITS);
memprof_stats[bin].caller != ra ||
memprof_stats[bin].exec_ctx.type != th_ctx->exec_ctx.type ||
memprof_stats[bin].exec_ctx.pointer != th_ctx->exec_ctx.pointer;
bin = (bin + (hash | 1)) & (MEMPROF_HASH_BUCKETS - 1)) {
if (!--retries) { if (!--retries) {
bin = MEMPROF_HASH_BUCKETS; bin = MEMPROF_HASH_BUCKETS;
break; break;
@ -314,6 +322,7 @@ struct memprof_stats *memprof_get_bin(const void *ra, enum memprof_method meth)
old = NULL; old = NULL;
if (!memprof_stats[bin].caller && if (!memprof_stats[bin].caller &&
HA_ATOMIC_CAS(&memprof_stats[bin].caller, &old, ra)) { HA_ATOMIC_CAS(&memprof_stats[bin].caller, &old, ra)) {
memprof_stats[bin].exec_ctx = th_ctx->exec_ctx;
memprof_stats[bin].method = meth; memprof_stats[bin].method = meth;
break; break;
} }
@ -918,6 +927,14 @@ static int cmp_memprof_stats(const void *a, const void *b)
return -1; return -1;
else if (l->alloc_tot + l->free_tot < r->alloc_tot + r->free_tot) else if (l->alloc_tot + l->free_tot < r->alloc_tot + r->free_tot)
return 1; return 1;
else if (l->exec_ctx.type > r->exec_ctx.type)
return -1;
else if (l->exec_ctx.type < r->exec_ctx.type)
return 1;
else if (l->exec_ctx.pointer > r->exec_ctx.pointer)
return -1;
else if (l->exec_ctx.pointer < r->exec_ctx.pointer)
return 1;
else else
return 0; return 0;
} }
@ -931,6 +948,47 @@ static int cmp_memprof_addr(const void *a, const void *b)
return -1; return -1;
else if (l->caller < r->caller) else if (l->caller < r->caller)
return 1; return 1;
else if (l->exec_ctx.type > r->exec_ctx.type)
return -1;
else if (l->exec_ctx.type < r->exec_ctx.type)
return 1;
else if (l->exec_ctx.pointer > r->exec_ctx.pointer)
return -1;
else if (l->exec_ctx.pointer < r->exec_ctx.pointer)
return 1;
else
return 0;
}
static int cmp_memprof_ctx(const void *a, const void *b)
{
const struct memprof_stats *l = (const struct memprof_stats *)a;
const struct memprof_stats *r = (const struct memprof_stats *)b;
const void *ptrl = l->exec_ctx.pointer;
const void *ptrr = r->exec_ctx.pointer;
/* in case of a mux, we'll use the always-present ->subscribe()
* function as a sorting key so that mux-ops and other mux functions
* appear grouped together.
*/
if (l->exec_ctx.type == TH_EX_CTX_MUX)
ptrl = l->exec_ctx.mux_ops->subscribe;
if (r->exec_ctx.type == TH_EX_CTX_MUX)
ptrr = r->exec_ctx.mux_ops->subscribe;
if (ptrl > ptrr)
return -1;
else if (ptrl < ptrr)
return 1;
else if (l->exec_ctx.type > r->exec_ctx.type)
return -1;
else if (l->exec_ctx.type < r->exec_ctx.type)
return 1;
else if (l->caller > r->caller)
return -1;
else if (l->caller < r->caller)
return 1;
else else
return 0; return 0;
} }
@ -992,9 +1050,9 @@ struct sched_activity *sched_activity_entry(struct sched_activity *array, const
static int cli_io_handler_show_profiling(struct appctx *appctx) static int cli_io_handler_show_profiling(struct appctx *appctx)
{ {
struct show_prof_ctx *ctx = appctx->svcctx; struct show_prof_ctx *ctx = appctx->svcctx;
struct sched_activity tmp_activity[SCHED_ACT_HASH_BUCKETS]; struct sched_activity *tmp_activity = ctx->tmp_activity;
#ifdef USE_MEMORY_PROFILING #ifdef USE_MEMORY_PROFILING
struct memprof_stats tmp_memstats[MEMPROF_HASH_BUCKETS + 1]; struct memprof_stats *tmp_memstats = ctx->tmp_memstats;
unsigned long long tot_alloc_calls, tot_free_calls; unsigned long long tot_alloc_calls, tot_free_calls;
unsigned long long tot_alloc_bytes, tot_free_bytes; unsigned long long tot_alloc_bytes, tot_free_bytes;
#endif #endif
@ -1035,7 +1093,20 @@ static int cli_io_handler_show_profiling(struct appctx *appctx)
if ((ctx->dump_step & 3) != 1) if ((ctx->dump_step & 3) != 1)
goto skip_tasks; goto skip_tasks;
memcpy(tmp_activity, sched_activity, sizeof(tmp_activity)); if (tmp_activity)
goto tasks_resume;
/* first call for show profiling tasks: we have to allocate a tmp
* array for sorting and processing, and possibly perform some
* sorting and aggregation.
*/
tmp_activity = ha_aligned_alloc(__alignof__(*tmp_activity), sizeof(sched_activity));
if (!tmp_activity)
goto end_tasks;
ctx->tmp_activity = tmp_activity;
memcpy(tmp_activity, sched_activity, sizeof(sched_activity));
/* for addr sort and for callee aggregation we have to first sort by address */ /* for addr sort and for callee aggregation we have to first sort by address */
if (ctx->aggr || ctx->by_what == 1) // sort by addr if (ctx->aggr || ctx->by_what == 1) // sort by addr
qsort(tmp_activity, SCHED_ACT_HASH_BUCKETS, sizeof(tmp_activity[0]), cmp_sched_activity_addr); qsort(tmp_activity, SCHED_ACT_HASH_BUCKETS, sizeof(tmp_activity[0]), cmp_sched_activity_addr);
@ -1060,6 +1131,7 @@ static int cli_io_handler_show_profiling(struct appctx *appctx)
else if (ctx->by_what == 2) // by cpu_tot else if (ctx->by_what == 2) // by cpu_tot
qsort(tmp_activity, SCHED_ACT_HASH_BUCKETS, sizeof(tmp_activity[0]), cmp_sched_activity_cpu); qsort(tmp_activity, SCHED_ACT_HASH_BUCKETS, sizeof(tmp_activity[0]), cmp_sched_activity_cpu);
tasks_resume:
if (!ctx->linenum) if (!ctx->linenum)
chunk_appendf(&trash, "Tasks activity over %.3f sec till %.3f sec ago:\n" chunk_appendf(&trash, "Tasks activity over %.3f sec till %.3f sec ago:\n"
" function calls cpu_tot cpu_avg lkw_avg lkd_avg mem_avg lat_avg\n", " function calls cpu_tot cpu_avg lkw_avg lkd_avg mem_avg lat_avg\n",
@ -1123,6 +1195,8 @@ static int cli_io_handler_show_profiling(struct appctx *appctx)
return 0; return 0;
} }
end_tasks:
ha_free(&ctx->tmp_activity);
ctx->linenum = 0; // reset first line to dump ctx->linenum = 0; // reset first line to dump
if ((ctx->dump_step & 4) == 0) if ((ctx->dump_step & 4) == 0)
ctx->dump_step++; // next step ctx->dump_step++; // next step
@ -1133,16 +1207,57 @@ static int cli_io_handler_show_profiling(struct appctx *appctx)
if ((ctx->dump_step & 3) != 2) if ((ctx->dump_step & 3) != 2)
goto skip_mem; goto skip_mem;
memcpy(tmp_memstats, memprof_stats, sizeof(tmp_memstats)); if (tmp_memstats)
if (ctx->by_what) goto memstats_resume;
/* first call for show profiling memory: we have to allocate a tmp
* array for sorting and processing, and possibly perform some sorting
* and aggregation.
*/
tmp_memstats = ha_aligned_alloc(__alignof__(*tmp_memstats), sizeof(memprof_stats));
if (!tmp_memstats)
goto end_memstats;
ctx->tmp_memstats = tmp_memstats;
memcpy(tmp_memstats, memprof_stats, sizeof(memprof_stats));
if (ctx->by_what == 1)
qsort(tmp_memstats, MEMPROF_HASH_BUCKETS+1, sizeof(tmp_memstats[0]), cmp_memprof_addr); qsort(tmp_memstats, MEMPROF_HASH_BUCKETS+1, sizeof(tmp_memstats[0]), cmp_memprof_addr);
else if (ctx->by_what == 3)
qsort(tmp_memstats, MEMPROF_HASH_BUCKETS+1, sizeof(tmp_memstats[0]), cmp_memprof_ctx);
else else
qsort(tmp_memstats, MEMPROF_HASH_BUCKETS+1, sizeof(tmp_memstats[0]), cmp_memprof_stats); qsort(tmp_memstats, MEMPROF_HASH_BUCKETS+1, sizeof(tmp_memstats[0]), cmp_memprof_stats);
if (ctx->aggr) {
/* merge entries for the same caller and reset the exec_ctx */
for (i = j = 0; i < MEMPROF_HASH_BUCKETS; i++) {
if ((tmp_memstats[i].alloc_calls | tmp_memstats[i].free_calls) == 0)
continue;
for (j = i + 1; j < MEMPROF_HASH_BUCKETS; j++) {
if ((tmp_memstats[j].alloc_calls | tmp_memstats[j].free_calls) == 0)
continue;
if (tmp_memstats[j].caller != tmp_memstats[i].caller ||
tmp_memstats[j].method != tmp_memstats[i].method ||
tmp_memstats[j].info != tmp_memstats[i].info)
continue;
tmp_memstats[i].locked_calls += tmp_memstats[j].locked_calls;
tmp_memstats[i].alloc_calls += tmp_memstats[j].alloc_calls;
tmp_memstats[i].free_calls += tmp_memstats[j].free_calls;
tmp_memstats[i].alloc_tot += tmp_memstats[j].alloc_tot;
tmp_memstats[i].free_tot += tmp_memstats[j].free_tot;
/* don't dump the ctx */
tmp_memstats[i].exec_ctx.type = 0;
/* don't dump the merged entry */
tmp_memstats[j].alloc_calls = tmp_memstats[j].free_calls = 0;
}
}
}
memstats_resume:
if (!ctx->linenum) if (!ctx->linenum)
chunk_appendf(&trash, chunk_appendf(&trash,
"Alloc/Free statistics by call place over %.3f sec till %.3f sec ago:\n" "Alloc/Free statistics by call place over %.3f sec till %.3f sec ago:\n"
" Calls | Tot Bytes | Caller and method\n" " Calls | Tot Bytes | Caller, method, extra info\n"
"<- alloc -> <- free ->|<-- alloc ---> <-- free ---->|\n", "<- alloc -> <- free ->|<-- alloc ---> <-- free ---->|\n",
(prof_mem_start_ns ? (prof_mem_stop_ns ? prof_mem_stop_ns : now_ns) - prof_mem_start_ns : 0) / 1000000000.0, (prof_mem_start_ns ? (prof_mem_stop_ns ? prof_mem_stop_ns : now_ns) - prof_mem_start_ns : 0) / 1000000000.0,
(prof_mem_stop_ns ? now_ns - prof_mem_stop_ns : 0) / 1000000000.0); (prof_mem_stop_ns ? now_ns - prof_mem_stop_ns : 0) / 1000000000.0);
@ -1200,6 +1315,7 @@ static int cli_io_handler_show_profiling(struct appctx *appctx)
(int)((1000ULL * entry->locked_calls / tot_calls) % 10)); (int)((1000ULL * entry->locked_calls / tot_calls) % 10));
} }
chunk_append_thread_ctx(&trash, &entry->exec_ctx, " [via ", "]");
chunk_appendf(&trash, "\n"); chunk_appendf(&trash, "\n");
if (applet_putchk(appctx, &trash) == -1) if (applet_putchk(appctx, &trash) == -1)
@ -1309,9 +1425,15 @@ static int cli_io_handler_show_profiling(struct appctx *appctx)
tot_alloc_calls - tot_free_calls, tot_alloc_calls - tot_free_calls,
tot_alloc_bytes - tot_free_bytes); tot_alloc_bytes - tot_free_bytes);
/* release optional buffer name */
for (i = 0; i < max; i++)
ha_free(&tmp_memstats[i].info);
if (applet_putchk(appctx, &trash) == -1) if (applet_putchk(appctx, &trash) == -1)
return 0; return 0;
end_memstats:
ha_free(&ctx->tmp_memstats);
ctx->linenum = 0; // reset first line to dump ctx->linenum = 0; // reset first line to dump
if ((ctx->dump_step & 4) == 0) if ((ctx->dump_step & 4) == 0)
ctx->dump_step++; // next step ctx->dump_step++; // next step
@ -1322,6 +1444,15 @@ static int cli_io_handler_show_profiling(struct appctx *appctx)
return 1; return 1;
} }
/* release structs allocated by "show profiling" */
static void cli_release_show_profiling(struct appctx *appctx)
{
struct show_prof_ctx *ctx = appctx->svcctx;
ha_free(&ctx->tmp_activity);
ha_free(&ctx->tmp_memstats);
}
/* parse a "show profiling" command. It returns 1 on failure, 0 if it starts to dump. /* parse a "show profiling" command. It returns 1 on failure, 0 if it starts to dump.
* - cli.i0 is set to the first state (0=all, 4=status, 5=tasks, 6=memory) * - cli.i0 is set to the first state (0=all, 4=status, 5=tasks, 6=memory)
* - cli.o1 is set to 1 if the output must be sorted by addr instead of usage * - cli.o1 is set to 1 if the output must be sorted by addr instead of usage
@ -1354,6 +1485,9 @@ static int cli_parse_show_profiling(char **args, char *payload, struct appctx *a
else if (strcmp(args[arg], "bytime") == 0) { else if (strcmp(args[arg], "bytime") == 0) {
ctx->by_what = 2; // sort output by total time instead of usage ctx->by_what = 2; // sort output by total time instead of usage
} }
else if (strcmp(args[arg], "byctx") == 0) {
ctx->by_what = 3; // sort output by caller context instead of usage
}
else if (strcmp(args[arg], "aggr") == 0) { else if (strcmp(args[arg], "aggr") == 0) {
ctx->aggr = 1; // aggregate output by callee ctx->aggr = 1; // aggregate output by callee
} }
@ -1361,7 +1495,7 @@ static int cli_parse_show_profiling(char **args, char *payload, struct appctx *a
ctx->maxcnt = atoi(args[arg]); // number of entries to dump ctx->maxcnt = atoi(args[arg]); // number of entries to dump
} }
else else
return cli_err(appctx, "Expects either 'all', 'status', 'tasks', 'memory', 'byaddr', 'bytime', 'aggr' or a max number of output lines.\n"); return cli_err(appctx, "Expects either 'all', 'status', 'tasks', 'memory', 'byaddr', 'bytime', 'byctx', 'aggr' or a max number of output lines.\n");
} }
return 0; return 0;
} }
@ -1705,7 +1839,7 @@ INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
static struct cli_kw_list cli_kws = {{ },{ static struct cli_kw_list cli_kws = {{ },{
{ { "set", "profiling", NULL }, "set profiling <what> {auto|on|off} : enable/disable resource profiling (tasks,memory)", cli_parse_set_profiling, NULL }, { { "set", "profiling", NULL }, "set profiling <what> {auto|on|off} : enable/disable resource profiling (tasks,memory)", cli_parse_set_profiling, NULL },
{ { "show", "activity", NULL }, "show activity [-1|0|thread_num] : show per-thread activity stats (for support/developers)", cli_parse_show_activity, cli_io_handler_show_activity, NULL }, { { "show", "activity", NULL }, "show activity [-1|0|thread_num] : show per-thread activity stats (for support/developers)", cli_parse_show_activity, cli_io_handler_show_activity, NULL },
{ { "show", "profiling", NULL }, "show profiling [<what>|<#lines>|<opts>]*: show profiling state (all,status,tasks,memory)", cli_parse_show_profiling, cli_io_handler_show_profiling, NULL }, { { "show", "profiling", NULL }, "show profiling [<what>|<#lines>|<opts>]*: show profiling state (all,status,tasks,memory)", cli_parse_show_profiling, cli_io_handler_show_profiling, cli_release_show_profiling },
{ { "show", "tasks", NULL }, "show tasks : show running tasks", NULL, cli_io_handler_show_tasks, NULL }, { { "show", "tasks", NULL }, "show tasks : show running tasks", NULL, cli_io_handler_show_tasks, NULL },
{{},} {{},}
}}; }};

View file

@ -31,7 +31,6 @@ unsigned int nb_applets = 0;
DECLARE_TYPED_POOL(pool_head_appctx, "appctx", struct appctx); DECLARE_TYPED_POOL(pool_head_appctx, "appctx", struct appctx);
/* trace source and events */ /* trace source and events */
static void applet_trace(enum trace_level level, uint64_t mask, static void applet_trace(enum trace_level level, uint64_t mask,
const struct trace_source *src, const struct trace_source *src,
@ -417,7 +416,7 @@ void appctx_shut(struct appctx *appctx)
TRACE_ENTER(APPLET_EV_RELEASE, appctx); TRACE_ENTER(APPLET_EV_RELEASE, appctx);
if (appctx->applet->release) if (appctx->applet->release)
appctx->applet->release(appctx); CALL_APPLET_NO_RET(appctx->applet, release(appctx));
applet_fl_set(appctx, APPCTX_FL_SHUTDOWN); applet_fl_set(appctx, APPCTX_FL_SHUTDOWN);
b_dequeue(&appctx->buffer_wait); b_dequeue(&appctx->buffer_wait);
@ -512,7 +511,7 @@ size_t appctx_htx_rcv_buf(struct appctx *appctx, struct buffer *buf, size_t coun
goto out; goto out;
} }
htx_xfer_blks(buf_htx, appctx_htx, count, HTX_BLK_UNUSED); htx_xfer(buf_htx, appctx_htx, count, HTX_XFER_DEFAULT);
buf_htx->flags |= (appctx_htx->flags & (HTX_FL_PARSING_ERROR|HTX_FL_PROCESSING_ERROR)); buf_htx->flags |= (appctx_htx->flags & (HTX_FL_PARSING_ERROR|HTX_FL_PROCESSING_ERROR));
if (htx_is_empty(appctx_htx)) { if (htx_is_empty(appctx_htx)) {
buf_htx->flags |= (appctx_htx->flags & HTX_FL_EOM); buf_htx->flags |= (appctx_htx->flags & HTX_FL_EOM);
@ -551,7 +550,7 @@ size_t appctx_rcv_buf(struct stconn *sc, struct buffer *buf, size_t count, unsig
if (flags & CO_RFL_BUF_FLUSH) if (flags & CO_RFL_BUF_FLUSH)
applet_fl_set(appctx, APPCTX_FL_FASTFWD); applet_fl_set(appctx, APPCTX_FL_FASTFWD);
ret = appctx->applet->rcv_buf(appctx, buf, count, flags); ret = CALL_APPLET_WITH_RET(appctx->applet, rcv_buf(appctx, buf, count, flags));
if (ret) if (ret)
applet_fl_clr(appctx, APPCTX_FL_OUTBLK_FULL); applet_fl_clr(appctx, APPCTX_FL_OUTBLK_FULL);
@ -609,7 +608,7 @@ size_t appctx_htx_snd_buf(struct appctx *appctx, struct buffer *buf, size_t coun
goto end; goto end;
} }
htx_xfer_blks(appctx_htx, buf_htx, count, HTX_BLK_UNUSED); htx_xfer(appctx_htx, buf_htx, count, HTX_XFER_DEFAULT);
if (htx_is_empty(buf_htx)) { if (htx_is_empty(buf_htx)) {
appctx_htx->flags |= (buf_htx->flags & HTX_FL_EOM); appctx_htx->flags |= (buf_htx->flags & HTX_FL_EOM);
} }
@ -659,7 +658,7 @@ size_t appctx_snd_buf(struct stconn *sc, struct buffer *buf, size_t count, unsig
goto end; goto end;
} }
ret = appctx->applet->snd_buf(appctx, buf, count, flags); ret = CALL_APPLET_WITH_RET(appctx->applet, snd_buf(appctx, buf, count, flags));
if (applet_fl_test(appctx, (APPCTX_FL_ERROR|APPCTX_FL_ERR_PENDING))) if (applet_fl_test(appctx, (APPCTX_FL_ERROR|APPCTX_FL_ERR_PENDING)))
se_report_term_evt(appctx->sedesc, se_tevt_type_snd_err); se_report_term_evt(appctx->sedesc, se_tevt_type_snd_err);
@ -716,7 +715,7 @@ int appctx_fastfwd(struct stconn *sc, unsigned int count, unsigned int flags)
} }
b_add(sdo->iobuf.buf, sdo->iobuf.offset); b_add(sdo->iobuf.buf, sdo->iobuf.offset);
ret = appctx->applet->fastfwd(appctx, sdo->iobuf.buf, len, 0); ret = CALL_APPLET_WITH_RET(appctx->applet, fastfwd(appctx, sdo->iobuf.buf, len, 0));
b_sub(sdo->iobuf.buf, sdo->iobuf.offset); b_sub(sdo->iobuf.buf, sdo->iobuf.offset);
sdo->iobuf.data += ret; sdo->iobuf.data += ret;
@ -853,7 +852,7 @@ struct task *task_run_applet(struct task *t, void *context, unsigned int state)
* already called) * already called)
*/ */
if (!se_fl_test(app->sedesc, SE_FL_SHR) || !se_fl_test(app->sedesc, SE_FL_SHW)) if (!se_fl_test(app->sedesc, SE_FL_SHR) || !se_fl_test(app->sedesc, SE_FL_SHW))
app->applet->fct(app); CALL_APPLET_NO_RET(app->applet, fct(app));
TRACE_POINT(APPLET_EV_PROCESS, app); TRACE_POINT(APPLET_EV_PROCESS, app);
@ -900,7 +899,7 @@ struct task *task_run_applet(struct task *t, void *context, unsigned int state)
stream_dump_and_crash(&app->obj_type, read_freq_ctr(&app->call_rate)); stream_dump_and_crash(&app->obj_type, read_freq_ctr(&app->call_rate));
} }
sc->app_ops->wake(sc); sc_applet_process(sc);
channel_release_buffer(ic, &app->buffer_wait); channel_release_buffer(ic, &app->buffer_wait);
TRACE_LEAVE(APPLET_EV_PROCESS, app); TRACE_LEAVE(APPLET_EV_PROCESS, app);
return t; return t;
@ -954,7 +953,7 @@ struct task *task_process_applet(struct task *t, void *context, unsigned int sta
* already called) * already called)
*/ */
if (!applet_fl_test(app, APPCTX_FL_SHUTDOWN)) if (!applet_fl_test(app, APPCTX_FL_SHUTDOWN))
app->applet->fct(app); CALL_APPLET_NO_RET(app->applet, fct(app));
TRACE_POINT(APPLET_EV_PROCESS, app); TRACE_POINT(APPLET_EV_PROCESS, app);
@ -993,7 +992,7 @@ struct task *task_process_applet(struct task *t, void *context, unsigned int sta
stream_dump_and_crash(&app->obj_type, read_freq_ctr(&app->call_rate)); stream_dump_and_crash(&app->obj_type, read_freq_ctr(&app->call_rate));
} }
sc->app_ops->wake(sc); sc_applet_process(sc);
appctx_release_buffers(app); appctx_release_buffers(app);
TRACE_LEAVE(APPLET_EV_PROCESS, app); TRACE_LEAVE(APPLET_EV_PROCESS, app);
return t; return t;

View file

@ -1396,7 +1396,7 @@ check_tgid:
tree = search_tree ? &srv->per_thr[i].safe_conns : &srv->per_thr[i].idle_conns; tree = search_tree ? &srv->per_thr[i].safe_conns : &srv->per_thr[i].idle_conns;
conn = srv_lookup_conn(tree, hash); conn = srv_lookup_conn(tree, hash);
while (conn) { while (conn) {
if (conn->mux->takeover && conn->mux->takeover(conn, i, 0) == 0) { if (conn->mux->takeover && CALL_MUX_WITH_RET(conn->mux, takeover(conn, i, 0)) == 0) {
conn_delete_from_tree(conn, i); conn_delete_from_tree(conn, i);
_HA_ATOMIC_INC(&activity[tid].fd_takeover); _HA_ATOMIC_INC(&activity[tid].fd_takeover);
found = 1; found = 1;
@ -1498,7 +1498,7 @@ takeover_random_idle_conn(struct ceb_root **root, int curtid)
conn = ceb64_item_first(root, hash_node.node, hash_node.key, struct connection); conn = ceb64_item_first(root, hash_node.node, hash_node.key, struct connection);
while (conn) { while (conn) {
if (conn->mux->takeover && conn->mux->takeover(conn, curtid, 1) == 0) { if (conn->mux->takeover && CALL_MUX_WITH_RET(conn->mux, takeover(conn, curtid, 1)) == 0) {
conn_delete_from_tree(conn, curtid); conn_delete_from_tree(conn, curtid);
return conn; return conn;
} }
@ -1555,7 +1555,7 @@ kill_random_idle_conn(struct server *srv)
*/ */
_HA_ATOMIC_INC(&srv->curr_used_conns); _HA_ATOMIC_INC(&srv->curr_used_conns);
} }
conn->mux->destroy(conn->ctx); CALL_MUX_NO_RET(conn->mux, destroy(conn->ctx));
return 1; return 1;
} }
return 0; return 0;
@ -1765,7 +1765,7 @@ int be_reuse_connection(int64_t hash, struct session *sess,
} }
if (avail >= 1) { if (avail >= 1) {
if (srv_conn->mux->attach(srv_conn, sc->sedesc, sess) == -1) { if (CALL_MUX_WITH_RET(srv_conn->mux, attach(srv_conn, sc->sedesc, sess)) == -1) {
if (sc_reset_endp(sc) < 0) if (sc_reset_endp(sc) < 0)
goto err; goto err;
sc_ep_clr(sc, ~SE_FL_DETACHED); sc_ep_clr(sc, ~SE_FL_DETACHED);
@ -1879,7 +1879,7 @@ int connect_server(struct stream *s)
* It will in turn call srv_release_conn through * It will in turn call srv_release_conn through
* conn_free which also uses it. * conn_free which also uses it.
*/ */
tokill_conn->mux->destroy(tokill_conn->ctx); CALL_MUX_NO_RET(tokill_conn->mux, destroy(tokill_conn->ctx));
} }
else { else {
HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock); HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock);
@ -2204,7 +2204,7 @@ int connect_server(struct stream *s)
*/ */
if (may_start_mux_now) { if (may_start_mux_now) {
const struct mux_ops *alt_mux = const struct mux_ops *alt_mux =
likely(!(s->flags & SF_WEBSOCKET)) ? NULL : srv_get_ws_proto(srv); likely(!(s->flags & SF_WEBSOCKET) || !srv) ? NULL : srv_get_ws_proto(srv);
if (conn_install_mux_be(srv_conn, s->scb, s->sess, alt_mux) < 0) { if (conn_install_mux_be(srv_conn, s->scb, s->sess, alt_mux) < 0) {
conn_full_close(srv_conn); conn_full_close(srv_conn);
return SF_ERR_INTERNAL; return SF_ERR_INTERNAL;
@ -2266,7 +2266,7 @@ int connect_server(struct stream *s)
s->flags |= SF_CURR_SESS; s->flags |= SF_CURR_SESS;
count = _HA_ATOMIC_ADD_FETCH(&srv->cur_sess, 1); count = _HA_ATOMIC_ADD_FETCH(&srv->cur_sess, 1);
HA_ATOMIC_UPDATE_MAX(&srv->counters.cur_sess_max, count); COUNTERS_UPDATE_MAX(&srv->counters.cur_sess_max, count);
if (s->be->lbprm.server_take_conn) if (s->be->lbprm.server_take_conn)
s->be->lbprm.server_take_conn(srv); s->be->lbprm.server_take_conn(srv);
} }

View file

@ -626,7 +626,7 @@ cache_store_check(struct proxy *px, struct flt_conf *fconf)
return 1; return 1;
} }
} }
else if (f->id == http_comp_flt_id) else if (f->id == http_comp_req_flt_id || f->id == http_comp_res_flt_id)
comp = 1; comp = 1;
else if (f->id == fcgi_flt_id) else if (f->id == fcgi_flt_id)
continue; continue;

View file

@ -89,12 +89,23 @@ int cfg_parse_global(const char *file, int linenum, char **args, int kwm)
global.tune.options |= GTUNE_BUSY_POLLING; global.tune.options |= GTUNE_BUSY_POLLING;
} }
else if (strcmp(args[0], "set-dumpable") == 0) { /* "no set-dumpable" or "set-dumpable" */ else if (strcmp(args[0], "set-dumpable") == 0) { /* "no set-dumpable" or "set-dumpable" */
if (alertif_too_many_args(0, file, linenum, args, &err_code)) if (alertif_too_many_args(1, file, linenum, args, &err_code))
goto out; goto out;
if (kwm == KWM_NO) if (kwm == KWM_NO) {
global.tune.options &= ~GTUNE_SET_DUMPABLE; global.tune.options &= ~GTUNE_SET_DUMPABLE;
else goto out;
global.tune.options |= GTUNE_SET_DUMPABLE; }
if (!*args[1] || strcmp(args[1], "on") == 0)
global.tune.options |= GTUNE_SET_DUMPABLE;
else if (strcmp(args[1], "libs") == 0)
global.tune.options |= GTUNE_SET_DUMPABLE | GTUNE_COLLECT_LIBS;
else if (strcmp(args[1], "off") == 0)
global.tune.options &= ~GTUNE_SET_DUMPABLE;
else {
ha_alert("parsing [%s:%d] : '%s' only supports 'on' and 'off' as an argument, found '%s'.\n", file, linenum, args[0], args[1]);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
} }
else if (strcmp(args[0], "h2-workaround-bogus-websocket-clients") == 0) { /* "no h2-workaround-bogus-websocket-clients" or "h2-workaround-bogus-websocket-clients" */ else if (strcmp(args[0], "h2-workaround-bogus-websocket-clients") == 0) { /* "no h2-workaround-bogus-websocket-clients" or "h2-workaround-bogus-websocket-clients" */
if (alertif_too_many_args(0, file, linenum, args, &err_code)) if (alertif_too_many_args(0, file, linenum, args, &err_code))
@ -456,9 +467,7 @@ int cfg_parse_global(const char *file, int linenum, char **args, int kwm)
for (i = 1; *args[i]; i++) for (i = 1; *args[i]; i++)
len += strlen(args[i]) + 1; len += strlen(args[i]) + 1;
if (global.desc) free(global.desc);
free(global.desc);
global.desc = d = calloc(1, len); global.desc = d = calloc(1, len);
d += snprintf(d, global.desc + len - d, "%s", args[1]); d += snprintf(d, global.desc + len - d, "%s", args[1]);
@ -487,9 +496,7 @@ int cfg_parse_global(const char *file, int linenum, char **args, int kwm)
goto out; goto out;
} }
if (global.node) free(global.node);
free(global.node);
global.node = strdup(args[1]); global.node = strdup(args[1]);
} }
else if (strcmp(args[0], "unix-bind") == 0) { else if (strcmp(args[0], "unix-bind") == 0) {

View file

@ -31,7 +31,6 @@
#include <haproxy/proxy.h> #include <haproxy/proxy.h>
#include <haproxy/sample.h> #include <haproxy/sample.h>
#include <haproxy/server.h> #include <haproxy/server.h>
#include <haproxy/stats-t.h>
#include <haproxy/stick_table.h> #include <haproxy/stick_table.h>
#include <haproxy/tcpcheck.h> #include <haproxy/tcpcheck.h>
#include <haproxy/tools.h> #include <haproxy/tools.h>
@ -394,7 +393,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
* freed unless it is still referenced by proxies. * freed unless it is still referenced by proxies.
*/ */
if (last_defproxy && last_defproxy->id[0] == '\0' && if (last_defproxy && last_defproxy->id[0] == '\0' &&
!last_defproxy->conf.refcount) { !last_defproxy->conf.def_ref) {
defaults_px_destroy(last_defproxy); defaults_px_destroy(last_defproxy);
} }
last_defproxy = NULL; last_defproxy = NULL;
@ -669,6 +668,13 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
err_code |= ERR_ALERT | ERR_FATAL; err_code |= ERR_ALERT | ERR_FATAL;
goto out; goto out;
} }
else {
/* valid mode, non "haterm" mode.
* Possibly restore the ->stream_new_from_sc() callback
* if set by default for "haterm" mode.
*/
curproxy->stream_new_from_sc = stream_new;
}
curproxy->mode = mode; curproxy->mode = mode;
if (curproxy->cap & PR_CAP_DEF) if (curproxy->cap & PR_CAP_DEF)
@ -1352,14 +1358,15 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
goto out; goto out;
} }
err_code |= warnif_misplaced_http_req(curproxy, file, linenum, args[0], NULL); if (warnif_misplaced_http_req(curproxy, file, linenum, args[0], NULL))
err_code |= ERR_WARN;
if (curproxy->cap & PR_CAP_FE) if (curproxy->cap & PR_CAP_FE)
where |= SMP_VAL_FE_HRQ_HDR; where |= SMP_VAL_FE_HRQ_HDR;
if (curproxy->cap & PR_CAP_BE) if (curproxy->cap & PR_CAP_BE)
where |= SMP_VAL_BE_HRQ_HDR; where |= SMP_VAL_BE_HRQ_HDR;
err_code |= warnif_cond_conflicts(rule->cond, where, &errmsg); err_code |= warnif_cond_conflicts(rule->cond, where, &errmsg);
if (err_code) if (errmsg && *errmsg)
ha_warning("parsing [%s:%d] : '%s.\n'", file, linenum, errmsg); ha_warning("parsing [%s:%d] : '%s.\n'", file, linenum, errmsg);
LIST_APPEND(&curproxy->http_req_rules, &rule->list); LIST_APPEND(&curproxy->http_req_rules, &rule->list);
@ -1394,7 +1401,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
if (curproxy->cap & PR_CAP_BE) if (curproxy->cap & PR_CAP_BE)
where |= SMP_VAL_BE_HRS_HDR; where |= SMP_VAL_BE_HRS_HDR;
err_code |= warnif_cond_conflicts(rule->cond, where, &errmsg); err_code |= warnif_cond_conflicts(rule->cond, where, &errmsg);
if (err_code) if (errmsg && *errmsg)
ha_warning("parsing [%s:%d] : '%s.\n'", file, linenum, errmsg); ha_warning("parsing [%s:%d] : '%s.\n'", file, linenum, errmsg);
LIST_APPEND(&curproxy->http_res_rules, &rule->list); LIST_APPEND(&curproxy->http_res_rules, &rule->list);
@ -1428,7 +1435,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
if (curproxy->cap & PR_CAP_BE) if (curproxy->cap & PR_CAP_BE)
where |= SMP_VAL_BE_HRS_HDR; where |= SMP_VAL_BE_HRS_HDR;
err_code |= warnif_cond_conflicts(rule->cond, where, &errmsg); err_code |= warnif_cond_conflicts(rule->cond, where, &errmsg);
if (err_code) if (errmsg && *errmsg)
ha_warning("parsing [%s:%d] : '%s.\n'", file, linenum, errmsg); ha_warning("parsing [%s:%d] : '%s.\n'", file, linenum, errmsg);
LIST_APPEND(&curproxy->http_after_res_rules, &rule->list); LIST_APPEND(&curproxy->http_after_res_rules, &rule->list);
@ -1485,14 +1492,15 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
} }
LIST_APPEND(&curproxy->redirect_rules, &rule->list); LIST_APPEND(&curproxy->redirect_rules, &rule->list);
err_code |= warnif_misplaced_redirect(curproxy, file, linenum, args[0], NULL); if (warnif_misplaced_redirect(curproxy, file, linenum, args[0], NULL))
err_code |= ERR_WARN;
if (curproxy->cap & PR_CAP_FE) if (curproxy->cap & PR_CAP_FE)
where |= SMP_VAL_FE_HRQ_HDR; where |= SMP_VAL_FE_HRQ_HDR;
if (curproxy->cap & PR_CAP_BE) if (curproxy->cap & PR_CAP_BE)
where |= SMP_VAL_BE_HRQ_HDR; where |= SMP_VAL_BE_HRQ_HDR;
err_code |= warnif_cond_conflicts(rule->cond, where, &errmsg); err_code |= warnif_cond_conflicts(rule->cond, where, &errmsg);
if (err_code) if (errmsg && *errmsg)
ha_warning("parsing [%s:%d] : '%s.\n'", file, linenum, errmsg); ha_warning("parsing [%s:%d] : '%s.\n'", file, linenum, errmsg);
} }
else if (strcmp(args[0], "use_backend") == 0) { else if (strcmp(args[0], "use_backend") == 0) {
@ -1522,7 +1530,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
} }
err_code |= warnif_cond_conflicts(cond, SMP_VAL_FE_SET_BCK, &errmsg); err_code |= warnif_cond_conflicts(cond, SMP_VAL_FE_SET_BCK, &errmsg);
if (err_code) if (errmsg && *errmsg)
ha_warning("parsing [%s:%d] : '%s.\n'", file, linenum, errmsg); ha_warning("parsing [%s:%d] : '%s.\n'", file, linenum, errmsg);
} }
else if (*args[2]) { else if (*args[2]) {
@ -1585,7 +1593,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
} }
err_code |= warnif_cond_conflicts(cond, SMP_VAL_BE_SET_SRV, &errmsg); err_code |= warnif_cond_conflicts(cond, SMP_VAL_BE_SET_SRV, &errmsg);
if (err_code) if (errmsg && *errmsg)
ha_warning("parsing [%s:%d] : '%s.\n'", file, linenum, errmsg); ha_warning("parsing [%s:%d] : '%s.\n'", file, linenum, errmsg);
rule = calloc(1, sizeof(*rule)); rule = calloc(1, sizeof(*rule));
@ -1640,7 +1648,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
* where force-persist is applied. * where force-persist is applied.
*/ */
err_code |= warnif_cond_conflicts(cond, SMP_VAL_BE_REQ_CNT, &errmsg); err_code |= warnif_cond_conflicts(cond, SMP_VAL_BE_REQ_CNT, &errmsg);
if (err_code) if (errmsg && *errmsg)
ha_warning("parsing [%s:%d] : '%s.\n'", file, linenum, errmsg); ha_warning("parsing [%s:%d] : '%s.\n'", file, linenum, errmsg);
rule = calloc(1, sizeof(*rule)); rule = calloc(1, sizeof(*rule));
@ -1808,7 +1816,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
err_code |= warnif_cond_conflicts(cond, SMP_VAL_BE_STO_RUL, &errmsg); err_code |= warnif_cond_conflicts(cond, SMP_VAL_BE_STO_RUL, &errmsg);
else else
err_code |= warnif_cond_conflicts(cond, SMP_VAL_BE_SET_SRV, &errmsg); err_code |= warnif_cond_conflicts(cond, SMP_VAL_BE_SET_SRV, &errmsg);
if (err_code) if (errmsg && *errmsg)
ha_warning("parsing [%s:%d] : '%s.\n'", file, linenum, errmsg); ha_warning("parsing [%s:%d] : '%s.\n'", file, linenum, errmsg);
rule = calloc(1, sizeof(*rule)); rule = calloc(1, sizeof(*rule));
@ -1866,7 +1874,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
if (curproxy->cap & PR_CAP_BE) if (curproxy->cap & PR_CAP_BE)
where |= SMP_VAL_BE_HRQ_HDR; where |= SMP_VAL_BE_HRQ_HDR;
err_code |= warnif_cond_conflicts(cond, where, &errmsg); err_code |= warnif_cond_conflicts(cond, where, &errmsg);
if (err_code) if (errmsg && *errmsg)
ha_warning("parsing [%s:%d] : '%s.\n'", file, linenum, errmsg); ha_warning("parsing [%s:%d] : '%s.\n'", file, linenum, errmsg);
rule = calloc(1, sizeof(*rule)); rule = calloc(1, sizeof(*rule));
@ -1946,7 +1954,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
if (curproxy->cap & PR_CAP_BE) if (curproxy->cap & PR_CAP_BE)
where |= SMP_VAL_BE_HRQ_HDR; where |= SMP_VAL_BE_HRQ_HDR;
err_code |= warnif_cond_conflicts(rule->cond, where, &errmsg); err_code |= warnif_cond_conflicts(rule->cond, where, &errmsg);
if (err_code) if (errmsg && *errmsg)
ha_warning("parsing [%s:%d] : '%s.\n'", file, linenum, errmsg); ha_warning("parsing [%s:%d] : '%s.\n'", file, linenum, errmsg);
LIST_APPEND(&curproxy->uri_auth->http_req_rules, &rule->list); LIST_APPEND(&curproxy->uri_auth->http_req_rules, &rule->list);
@ -2194,6 +2202,42 @@ stats_error_parsing:
err_code |= ERR_ALERT | ERR_FATAL; err_code |= ERR_ALERT | ERR_FATAL;
goto out; goto out;
} }
else if (strcmp(args[1], "use-small-buffers") == 0) {
unsigned int flags = PR_O2_USE_SBUF_ALL;
if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[1], NULL)) {
err_code |= ERR_WARN;
goto out;
}
if (*(args[2])) {
int cur_arg;
flags = 0;
for (cur_arg = 2; *(args[cur_arg]); cur_arg++) {
if (strcmp(args[cur_arg], "queue") == 0)
flags |= PR_O2_USE_SBUF_QUEUE;
else if (strcmp(args[cur_arg], "l7-retries") == 0)
flags |= PR_O2_USE_SBUF_L7_RETRY;
else if (strcmp(args[cur_arg], "check") == 0)
flags |= PR_O2_USE_SBUF_CHECK;
else {
ha_alert("parsing [%s:%d] : invalid parameter '%s'. option '%s' expects 'queue', 'l7-retries' or 'check' value.\n",
file, linenum, args[cur_arg], args[1]);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
}
}
if (kwm == KWM_STD) {
curproxy->options2 &= ~PR_O2_USE_SBUF_ALL;
curproxy->options2 |= flags;
}
else if (kwm == KWM_NO) {
curproxy->options2 &= ~flags;
}
goto out;
}
if (kwm != KWM_STD) { if (kwm != KWM_STD) {
ha_alert("parsing [%s:%d]: negation/default is not supported for option '%s'.\n", ha_alert("parsing [%s:%d]: negation/default is not supported for option '%s'.\n",
@ -2454,18 +2498,7 @@ stats_error_parsing:
} }
curproxy->no_options2 &= ~val; curproxy->no_options2 &= ~val;
curproxy->options2 &= ~val; curproxy->options2 |= val;
switch (kwm) {
case KWM_STD:
curproxy->options2 |= val;
break;
case KWM_NO:
curproxy->no_options2 |= val;
break;
case KWM_DEF: /* already cleared */
break;
}
err_code |= ERR_WARN; err_code |= ERR_WARN;
goto out; goto out;
@ -2562,7 +2595,8 @@ stats_error_parsing:
goto out; goto out;
} }
err_code |= warnif_misplaced_monitor(curproxy, file, linenum, args[0], args[1]); if (warnif_misplaced_monitor(curproxy, file, linenum, args[0], args[1]))
err_code |= ERR_WARN;
if ((cond = build_acl_cond(file, linenum, &curproxy->acl, curproxy, (const char **)args + 2, &errmsg)) == NULL) { if ((cond = build_acl_cond(file, linenum, &curproxy->acl, curproxy, (const char **)args + 2, &errmsg)) == NULL) {
ha_alert("parsing [%s:%d] : error detected while parsing a '%s %s' condition : %s.\n", ha_alert("parsing [%s:%d] : error detected while parsing a '%s %s' condition : %s.\n",
file, linenum, args[0], args[1], errmsg); file, linenum, args[0], args[1], errmsg);

View file

@ -143,7 +143,6 @@ static int ssl_parse_global_ssl_async(char **args, int section_type, struct prox
#endif #endif
} }
#if defined(USE_ENGINE) && !defined(OPENSSL_NO_ENGINE)
/* parse the "ssl-engine" keyword in global section. /* parse the "ssl-engine" keyword in global section.
* Returns <0 on alert, >0 on warning, 0 on success. * Returns <0 on alert, >0 on warning, 0 on success.
*/ */
@ -151,6 +150,7 @@ static int ssl_parse_global_ssl_engine(char **args, int section_type, struct pro
const struct proxy *defpx, const char *file, int line, const struct proxy *defpx, const char *file, int line,
char **err) char **err)
{ {
#if defined(USE_ENGINE) && !defined(OPENSSL_NO_ENGINE)
char *algo; char *algo;
int ret = -1; int ret = -1;
@ -184,10 +184,12 @@ add_engine:
} }
free(algo); free(algo);
return ret; return ret;
} #else
memprintf(err, "'%s' is not supported (built without USE_ENGINE or with -DOPENSSL_NO_ENGINE).", args[0]);
return -1;
#endif #endif
}
#ifdef HAVE_SSL_PROVIDERS
/* parse the "ssl-propquery" keyword in global section. /* parse the "ssl-propquery" keyword in global section.
* Returns <0 on alert, >0 on warning, 0 on success. * Returns <0 on alert, >0 on warning, 0 on success.
*/ */
@ -195,6 +197,7 @@ static int ssl_parse_global_ssl_propquery(char **args, int section_type, struct
const struct proxy *defpx, const char *file, int line, const struct proxy *defpx, const char *file, int line,
char **err) char **err)
{ {
#ifdef HAVE_SSL_PROVIDERS
int ret = -1; int ret = -1;
if (*(args[1]) == 0) { if (*(args[1]) == 0) {
@ -206,6 +209,10 @@ static int ssl_parse_global_ssl_propquery(char **args, int section_type, struct
ret = 0; ret = 0;
return ret; return ret;
#else
memprintf(err, "'%s' is not supported by %s.", args[0], OpenSSL_version(OPENSSL_VERSION));
return -1;
#endif
} }
/* parse the "ssl-provider" keyword in global section. /* parse the "ssl-provider" keyword in global section.
@ -215,6 +222,7 @@ static int ssl_parse_global_ssl_provider(char **args, int section_type, struct p
const struct proxy *defpx, const char *file, int line, const struct proxy *defpx, const char *file, int line,
char **err) char **err)
{ {
#ifdef HAVE_SSL_PROVIDERS
int ret = -1; int ret = -1;
if (*(args[1]) == 0) { if (*(args[1]) == 0) {
@ -226,6 +234,10 @@ static int ssl_parse_global_ssl_provider(char **args, int section_type, struct p
ret = 0; ret = 0;
return ret; return ret;
#else
memprintf(err, "'%s' is not supported by %s.", args[0], OpenSSL_version(OPENSSL_VERSION));
return -1;
#endif
} }
/* parse the "ssl-provider-path" keyword in global section. /* parse the "ssl-provider-path" keyword in global section.
@ -235,6 +247,7 @@ static int ssl_parse_global_ssl_provider_path(char **args, int section_type, str
const struct proxy *defpx, const char *file, int line, const struct proxy *defpx, const char *file, int line,
char **err) char **err)
{ {
#ifdef HAVE_SSL_PROVIDERS
if (*(args[1]) == 0) { if (*(args[1]) == 0) {
memprintf(err, "global statement '%s' expects a directory path as an argument.", args[0]); memprintf(err, "global statement '%s' expects a directory path as an argument.", args[0]);
return -1; return -1;
@ -243,8 +256,11 @@ static int ssl_parse_global_ssl_provider_path(char **args, int section_type, str
OSSL_PROVIDER_set_default_search_path(NULL, args[1]); OSSL_PROVIDER_set_default_search_path(NULL, args[1]);
return 0; return 0;
} #else
memprintf(err, "'%s' is not supported by %s.", args[0], OpenSSL_version(OPENSSL_VERSION));
return -1;
#endif #endif
}
/* parse the "ssl-default-bind-ciphers" / "ssl-default-server-ciphers" keywords /* parse the "ssl-default-bind-ciphers" / "ssl-default-server-ciphers" keywords
* in global section. Returns <0 on alert, >0 on warning, 0 on success. * in global section. Returns <0 on alert, >0 on warning, 0 on success.
@ -300,7 +316,6 @@ static int ssl_parse_global_ciphersuites(char **args, int section_type, struct p
#endif #endif
} }
#if defined(SSL_CTX_set1_curves_list)
/* /*
* parse the "ssl-default-bind-curves" keyword in a global section. * parse the "ssl-default-bind-curves" keyword in a global section.
* Returns <0 on alert, >0 on warning, 0 on success. * Returns <0 on alert, >0 on warning, 0 on success.
@ -309,6 +324,10 @@ static int ssl_parse_global_curves(char **args, int section_type, struct proxy *
const struct proxy *defpx, const char *file, int line, const struct proxy *defpx, const char *file, int line,
char **err) char **err)
{ {
#ifndef SSL_CTX_set1_curves_list
memprintf(err, "'%s' is not supported by %s.", args[0], OpenSSL_version(OPENSSL_VERSION));
return -1;
#else
char **target; char **target;
target = (args[0][12] == 'b') ? &global_ssl.listen_default_curves : &global_ssl.connect_default_curves; target = (args[0][12] == 'b') ? &global_ssl.listen_default_curves : &global_ssl.connect_default_curves;
@ -323,10 +342,9 @@ static int ssl_parse_global_curves(char **args, int section_type, struct proxy *
free(*target); free(*target);
*target = strdup(args[1]); *target = strdup(args[1]);
return 0; return 0;
}
#endif #endif
}
#if defined(SSL_CTX_set1_sigalgs_list)
/* /*
* parse the "ssl-default-bind-sigalgs" and "ssl-default-server-sigalgs" keyword in a global section. * parse the "ssl-default-bind-sigalgs" and "ssl-default-server-sigalgs" keyword in a global section.
* Returns <0 on alert, >0 on warning, 0 on success. * Returns <0 on alert, >0 on warning, 0 on success.
@ -335,6 +353,10 @@ static int ssl_parse_global_sigalgs(char **args, int section_type, struct proxy
const struct proxy *defpx, const char *file, int line, const struct proxy *defpx, const char *file, int line,
char **err) char **err)
{ {
#ifndef SSL_CTX_set1_sigalgs_list
memprintf(err, "'%s' is not supported by %s.", args[0], OpenSSL_version(OPENSSL_VERSION));
return -1;
#else
char **target; char **target;
target = (args[0][12] == 'b') ? &global_ssl.listen_default_sigalgs : &global_ssl.connect_default_sigalgs; target = (args[0][12] == 'b') ? &global_ssl.listen_default_sigalgs : &global_ssl.connect_default_sigalgs;
@ -350,10 +372,9 @@ static int ssl_parse_global_sigalgs(char **args, int section_type, struct proxy
free(*target); free(*target);
*target = strdup(args[1]); *target = strdup(args[1]);
return 0; return 0;
}
#endif #endif
}
#if defined(SSL_CTX_set1_client_sigalgs_list)
/* /*
* parse the "ssl-default-bind-client-sigalgs" keyword in a global section. * parse the "ssl-default-bind-client-sigalgs" keyword in a global section.
* Returns <0 on alert, >0 on warning, 0 on success. * Returns <0 on alert, >0 on warning, 0 on success.
@ -362,6 +383,10 @@ static int ssl_parse_global_client_sigalgs(char **args, int section_type, struct
const struct proxy *defpx, const char *file, int line, const struct proxy *defpx, const char *file, int line,
char **err) char **err)
{ {
#ifndef SSL_CTX_set1_client_sigalgs_list
memprintf(err, "'%s' is not supported by %s.", args[0], OpenSSL_version(OPENSSL_VERSION));
return -1;
#else
char **target; char **target;
target = (args[0][12] == 'b') ? &global_ssl.listen_default_client_sigalgs : &global_ssl.connect_default_client_sigalgs; target = (args[0][12] == 'b') ? &global_ssl.listen_default_client_sigalgs : &global_ssl.connect_default_client_sigalgs;
@ -377,8 +402,8 @@ static int ssl_parse_global_client_sigalgs(char **args, int section_type, struct
free(*target); free(*target);
*target = strdup(args[1]); *target = strdup(args[1]);
return 0; return 0;
}
#endif #endif
}
/* parse various global tune.ssl settings consisting in positive integers. /* parse various global tune.ssl settings consisting in positive integers.
* Returns <0 on alert, >0 on warning, 0 on success. * Returns <0 on alert, >0 on warning, 0 on success.
@ -575,7 +600,6 @@ static int ssl_parse_global_lifetime(char **args, int section_type, struct proxy
return 0; return 0;
} }
#ifndef OPENSSL_NO_DH
/* parse "ssl-dh-param-file". /* parse "ssl-dh-param-file".
* Returns <0 on alert, >0 on warning, 0 on success. * Returns <0 on alert, >0 on warning, 0 on success.
*/ */
@ -583,6 +607,7 @@ static int ssl_parse_global_dh_param_file(char **args, int section_type, struct
const struct proxy *defpx, const char *file, int line, const struct proxy *defpx, const char *file, int line,
char **err) char **err)
{ {
#ifndef OPENSSL_NO_DH
if (too_many_args(1, args, err, NULL)) if (too_many_args(1, args, err, NULL))
return -1; return -1;
@ -596,9 +621,11 @@ static int ssl_parse_global_dh_param_file(char **args, int section_type, struct
return -1; return -1;
} }
return 0; return 0;
} #else
memprintf(err, "'%s' is not supported by %s (no DH support).", args[0], OpenSSL_version(OPENSSL_VERSION));
return -1;
#endif #endif
}
/* parse "ssl.default-dh-param". /* parse "ssl.default-dh-param".
* Returns <0 on alert, >0 on warning, 0 on success. * Returns <0 on alert, >0 on warning, 0 on success.
@ -2738,12 +2765,12 @@ static struct srv_kw_list srv_kws = { "SSL", { }, {
{ "force-tlsv12", srv_parse_tls_method_options, 0, 1, 1 }, /* force TLSv12 */ { "force-tlsv12", srv_parse_tls_method_options, 0, 1, 1 }, /* force TLSv12 */
{ "force-tlsv13", srv_parse_tls_method_options, 0, 1, 1 }, /* force TLSv13 */ { "force-tlsv13", srv_parse_tls_method_options, 0, 1, 1 }, /* force TLSv13 */
{ "ktls", srv_parse_ktls, 1, 1, 1 }, /* enable or disable kTLS */ { "ktls", srv_parse_ktls, 1, 1, 1 }, /* enable or disable kTLS */
{ "no-check-sni-auto", srv_parse_no_check_sni_auto, 0, 1, 0 }, /* disable automatic SNI selection for health checks */ { "no-check-sni-auto", srv_parse_no_check_sni_auto, 0, 1, 1 }, /* disable automatic SNI selection for health checks */
{ "no-check-ssl", srv_parse_no_check_ssl, 0, 1, 0 }, /* disable SSL for health checks */ { "no-check-ssl", srv_parse_no_check_ssl, 0, 1, 0 }, /* disable SSL for health checks */
{ "no-renegotiate", srv_parse_renegotiate, 0, 1, 1 }, /* Disable renegotiation */ { "no-renegotiate", srv_parse_renegotiate, 0, 1, 1 }, /* Disable renegotiation */
{ "no-send-proxy-v2-ssl", srv_parse_no_send_proxy_ssl, 0, 1, 0 }, /* do not send PROXY protocol header v2 with SSL info */ { "no-send-proxy-v2-ssl", srv_parse_no_send_proxy_ssl, 0, 1, 0 }, /* do not send PROXY protocol header v2 with SSL info */
{ "no-send-proxy-v2-ssl-cn", srv_parse_no_send_proxy_cn, 0, 1, 0 }, /* do not send PROXY protocol header v2 with CN */ { "no-send-proxy-v2-ssl-cn", srv_parse_no_send_proxy_cn, 0, 1, 0 }, /* do not send PROXY protocol header v2 with CN */
{ "no-sni-auto", srv_parse_no_sni_auto, 0, 1, 0 }, /* disable automatic SNI selection */ { "no-sni-auto", srv_parse_no_sni_auto, 0, 1, 1 }, /* disable automatic SNI selection */
{ "no-ssl", srv_parse_no_ssl, 0, 1, 0 }, /* disable SSL processing */ { "no-ssl", srv_parse_no_ssl, 0, 1, 0 }, /* disable SSL processing */
{ "no-ssl-reuse", srv_parse_no_ssl_reuse, 0, 1, 1 }, /* disable session reuse */ { "no-ssl-reuse", srv_parse_no_ssl_reuse, 0, 1, 1 }, /* disable session reuse */
{ "no-sslv3", srv_parse_tls_method_options, 0, 0, 1 }, /* disable SSLv3 */ { "no-sslv3", srv_parse_tls_method_options, 0, 0, 1 }, /* disable SSLv3 */
@ -2779,18 +2806,12 @@ static struct cfg_kw_list cfg_kws = {ILH, {
{ CFG_GLOBAL, "maxsslconn", ssl_parse_global_int }, { CFG_GLOBAL, "maxsslconn", ssl_parse_global_int },
{ CFG_GLOBAL, "ssl-default-bind-options", ssl_parse_default_bind_options }, { CFG_GLOBAL, "ssl-default-bind-options", ssl_parse_default_bind_options },
{ CFG_GLOBAL, "ssl-default-server-options", ssl_parse_default_server_options }, { CFG_GLOBAL, "ssl-default-server-options", ssl_parse_default_server_options },
#ifndef OPENSSL_NO_DH
{ CFG_GLOBAL, "ssl-dh-param-file", ssl_parse_global_dh_param_file }, { CFG_GLOBAL, "ssl-dh-param-file", ssl_parse_global_dh_param_file },
#endif
{ CFG_GLOBAL, "ssl-mode-async", ssl_parse_global_ssl_async }, { CFG_GLOBAL, "ssl-mode-async", ssl_parse_global_ssl_async },
#if defined(USE_ENGINE) && !defined(OPENSSL_NO_ENGINE)
{ CFG_GLOBAL, "ssl-engine", ssl_parse_global_ssl_engine }, { CFG_GLOBAL, "ssl-engine", ssl_parse_global_ssl_engine },
#endif
#ifdef HAVE_SSL_PROVIDERS
{ CFG_GLOBAL, "ssl-propquery", ssl_parse_global_ssl_propquery }, { CFG_GLOBAL, "ssl-propquery", ssl_parse_global_ssl_propquery },
{ CFG_GLOBAL, "ssl-provider", ssl_parse_global_ssl_provider }, { CFG_GLOBAL, "ssl-provider", ssl_parse_global_ssl_provider },
{ CFG_GLOBAL, "ssl-provider-path", ssl_parse_global_ssl_provider_path }, { CFG_GLOBAL, "ssl-provider-path", ssl_parse_global_ssl_provider_path },
#endif
{ CFG_GLOBAL, "ssl-security-level", ssl_parse_security_level }, { CFG_GLOBAL, "ssl-security-level", ssl_parse_security_level },
{ CFG_GLOBAL, "ssl-skip-self-issued-ca", ssl_parse_skip_self_issued_ca }, { CFG_GLOBAL, "ssl-skip-self-issued-ca", ssl_parse_skip_self_issued_ca },
{ CFG_GLOBAL, "tune.ssl.cachesize", ssl_parse_global_int }, { CFG_GLOBAL, "tune.ssl.cachesize", ssl_parse_global_int },
@ -2806,18 +2827,12 @@ static struct cfg_kw_list cfg_kws = {ILH, {
{ CFG_GLOBAL, "tune.ssl.keylog", ssl_parse_global_keylog }, { CFG_GLOBAL, "tune.ssl.keylog", ssl_parse_global_keylog },
{ CFG_GLOBAL, "ssl-default-bind-ciphers", ssl_parse_global_ciphers }, { CFG_GLOBAL, "ssl-default-bind-ciphers", ssl_parse_global_ciphers },
{ CFG_GLOBAL, "ssl-default-server-ciphers", ssl_parse_global_ciphers }, { CFG_GLOBAL, "ssl-default-server-ciphers", ssl_parse_global_ciphers },
#if defined(SSL_CTX_set1_curves_list)
{ CFG_GLOBAL, "ssl-default-bind-curves", ssl_parse_global_curves }, { CFG_GLOBAL, "ssl-default-bind-curves", ssl_parse_global_curves },
{ CFG_GLOBAL, "ssl-default-server-curves", ssl_parse_global_curves }, { CFG_GLOBAL, "ssl-default-server-curves", ssl_parse_global_curves },
#endif
#if defined(SSL_CTX_set1_sigalgs_list)
{ CFG_GLOBAL, "ssl-default-bind-sigalgs", ssl_parse_global_sigalgs }, { CFG_GLOBAL, "ssl-default-bind-sigalgs", ssl_parse_global_sigalgs },
{ CFG_GLOBAL, "ssl-default-server-sigalgs", ssl_parse_global_sigalgs }, { CFG_GLOBAL, "ssl-default-server-sigalgs", ssl_parse_global_sigalgs },
#endif
#if defined(SSL_CTX_set1_client_sigalgs_list)
{ CFG_GLOBAL, "ssl-default-bind-client-sigalgs", ssl_parse_global_client_sigalgs }, { CFG_GLOBAL, "ssl-default-bind-client-sigalgs", ssl_parse_global_client_sigalgs },
{ CFG_GLOBAL, "ssl-default-server-client-sigalgs", ssl_parse_global_client_sigalgs }, { CFG_GLOBAL, "ssl-default-server-client-sigalgs", ssl_parse_global_client_sigalgs },
#endif
{ CFG_GLOBAL, "ssl-default-bind-ciphersuites", ssl_parse_global_ciphersuites }, { CFG_GLOBAL, "ssl-default-bind-ciphersuites", ssl_parse_global_ciphersuites },
{ CFG_GLOBAL, "ssl-default-server-ciphersuites", ssl_parse_global_ciphersuites }, { CFG_GLOBAL, "ssl-default-server-ciphersuites", ssl_parse_global_ciphersuites },
{ CFG_GLOBAL, "ssl-load-extra-files", ssl_parse_global_extra_files }, { CFG_GLOBAL, "ssl-load-extra-files", ssl_parse_global_extra_files },

View file

@ -63,6 +63,7 @@
#include <haproxy/global.h> #include <haproxy/global.h>
#include <haproxy/http_ana.h> #include <haproxy/http_ana.h>
#include <haproxy/http_rules.h> #include <haproxy/http_rules.h>
#include <haproxy/http_htx.h>
#include <haproxy/lb_chash.h> #include <haproxy/lb_chash.h>
#include <haproxy/lb_fas.h> #include <haproxy/lb_fas.h>
#include <haproxy/lb_fwlc.h> #include <haproxy/lb_fwlc.h>
@ -88,7 +89,6 @@
#include <haproxy/sample.h> #include <haproxy/sample.h>
#include <haproxy/server.h> #include <haproxy/server.h>
#include <haproxy/session.h> #include <haproxy/session.h>
#include <haproxy/stats-t.h>
#include <haproxy/stick_table.h> #include <haproxy/stick_table.h>
#include <haproxy/stream.h> #include <haproxy/stream.h>
#include <haproxy/task.h> #include <haproxy/task.h>
@ -2319,6 +2319,18 @@ int check_config_validity()
"Please fix either value to remove this warning.\n", "Please fix either value to remove this warning.\n",
global.tune.bufsize_large, global.tune.bufsize); global.tune.bufsize_large, global.tune.bufsize);
global.tune.bufsize_large = 0; global.tune.bufsize_large = 0;
err_code |= ERR_WARN;
}
}
if (global.tune.bufsize_small > 0) {
if (global.tune.bufsize_small == global.tune.bufsize)
global.tune.bufsize_small = 0;
else if (global.tune.bufsize_small > global.tune.bufsize) {
ha_warning("invalid small buffer size %d bytes which is greater to default bufsize %d bytes.\n",
global.tune.bufsize_small, global.tune.bufsize);
global.tune.bufsize_small = 0;
err_code |= ERR_WARN;
} }
} }
@ -2378,6 +2390,9 @@ int check_config_validity()
cfgerr += check_action_rules(&defpx->http_req_rules, defpx, &err_code); cfgerr += check_action_rules(&defpx->http_req_rules, defpx, &err_code);
cfgerr += check_action_rules(&defpx->http_res_rules, defpx, &err_code); cfgerr += check_action_rules(&defpx->http_res_rules, defpx, &err_code);
cfgerr += check_action_rules(&defpx->http_after_res_rules, defpx, &err_code); cfgerr += check_action_rules(&defpx->http_after_res_rules, defpx, &err_code);
#ifdef USE_QUIC
cfgerr += check_action_rules(&defpx->quic_init_rules, defpx, &err_code);
#endif
err = NULL; err = NULL;
i = smp_resolve_args(defpx, &err); i = smp_resolve_args(defpx, &err);
@ -2390,6 +2405,8 @@ int check_config_validity()
else { else {
cfgerr += acl_find_targets(defpx); cfgerr += acl_find_targets(defpx);
} }
err_code |= proxy_check_http_errors(defpx);
} }
/* starting to initialize the main proxies list */ /* starting to initialize the main proxies list */

View file

@ -34,7 +34,7 @@
#include <haproxy/cfgparse.h> #include <haproxy/cfgparse.h>
#include <haproxy/check.h> #include <haproxy/check.h>
#include <haproxy/chunk.h> #include <haproxy/chunk.h>
#include <haproxy/counters.h> #include <haproxy/counters-t.h>
#include <haproxy/dgram.h> #include <haproxy/dgram.h>
#include <haproxy/dynbuf.h> #include <haproxy/dynbuf.h>
#include <haproxy/extcheck.h> #include <haproxy/extcheck.h>
@ -58,7 +58,6 @@
#include <haproxy/sample.h> #include <haproxy/sample.h>
#include <haproxy/server.h> #include <haproxy/server.h>
#include <haproxy/ssl_sock.h> #include <haproxy/ssl_sock.h>
#include <haproxy/stats-t.h>
#include <haproxy/task.h> #include <haproxy/task.h>
#include <haproxy/tcpcheck.h> #include <haproxy/tcpcheck.h>
#include <haproxy/thread.h> #include <haproxy/thread.h>
@ -1046,13 +1045,12 @@ int httpchk_build_status_header(struct server *s, struct buffer *buf)
/**************************************************************************/ /**************************************************************************/
/***************** Health-checks based on connections *********************/ /***************** Health-checks based on connections *********************/
/**************************************************************************/ /**************************************************************************/
/* This function is used only for server health-checks. It handles connection /* This function handles connection status updates including errors. If
* status updates including errors. If necessary, it wakes the check task up. * necessary, it wakes the check task up.
* It returns 0 on normal cases, <0 if at least one close() has happened on the
* connection (eg: reconnect). It relies on tcpcheck_main().
*/ */
int wake_srv_chk(struct stconn *sc) struct task *srv_chk_io_cb(struct task *t, void *ctx, unsigned int state)
{ {
struct stconn *sc = ctx;
struct connection *conn; struct connection *conn;
struct check *check = __sc_check(sc); struct check *check = __sc_check(sc);
int ret = 0; int ret = 0;
@ -1099,15 +1097,6 @@ int wake_srv_chk(struct stconn *sc)
end: end:
TRACE_LEAVE(CHK_EV_HCHK_WAKE, check); TRACE_LEAVE(CHK_EV_HCHK_WAKE, check);
return ret;
}
/* This function checks if any I/O is wanted, and if so, attempts to do so */
struct task *srv_chk_io_cb(struct task *t, void *ctx, unsigned int state)
{
struct stconn *sc = ctx;
wake_srv_chk(sc);
return t; return t;
} }
@ -1526,13 +1515,15 @@ int check_buf_available(void *target)
/* /*
* Allocate a buffer. If it fails, it adds the check in buffer wait queue. * Allocate a buffer. If it fails, it adds the check in buffer wait queue.
*/ */
struct buffer *check_get_buf(struct check *check, struct buffer *bptr) struct buffer *check_get_buf(struct check *check, struct buffer *bptr, unsigned int small_buffer)
{ {
struct buffer *buf = NULL; struct buffer *buf = NULL;
if (likely(!LIST_INLIST(&check->buf_wait.list)) && if (small_buffer == 0 || (buf = b_alloc_small(bptr)) == NULL) {
unlikely((buf = b_alloc(bptr, DB_CHANNEL)) == NULL)) { if (likely(!LIST_INLIST(&check->buf_wait.list)) &&
b_queue(DB_CHANNEL, &check->buf_wait, check, check_buf_available); unlikely((buf = b_alloc(bptr, DB_CHANNEL)) == NULL)) {
b_queue(DB_CHANNEL, &check->buf_wait, check, check_buf_available);
}
} }
return buf; return buf;
} }
@ -1544,8 +1535,11 @@ struct buffer *check_get_buf(struct check *check, struct buffer *bptr)
void check_release_buf(struct check *check, struct buffer *bptr) void check_release_buf(struct check *check, struct buffer *bptr)
{ {
if (bptr->size) { if (bptr->size) {
int defbuf = b_is_default(bptr);
b_free(bptr); b_free(bptr);
offer_buffers(check->buf_wait.target, 1); if (defbuf)
offer_buffers(check->buf_wait.target, 1);
} }
} }
@ -1665,7 +1659,6 @@ int start_check_task(struct check *check, int mininter,
*/ */
static int start_checks() static int start_checks()
{ {
struct proxy *px; struct proxy *px;
struct server *s; struct server *s;
char *errmsg = NULL; char *errmsg = NULL;
@ -1692,6 +1685,10 @@ static int start_checks()
*/ */
for (px = proxies_list; px; px = px->next) { for (px = proxies_list; px; px = px->next) {
for (s = px->srv; s; s = s->next) { for (s = px->srv; s; s = s->next) {
if ((px->options2 & PR_O2_USE_SBUF_CHECK) &&
(s->check.tcpcheck_rules->flags & TCPCHK_RULES_MAY_USE_SBUF))
s->check.state |= CHK_ST_USE_SMALL_BUFF;
if (s->check.state & CHK_ST_CONFIGURED) { if (s->check.state & CHK_ST_CONFIGURED) {
nbcheck++; nbcheck++;
if ((srv_getinter(&s->check) >= SRV_CHK_INTER_THRES) && if ((srv_getinter(&s->check) >= SRV_CHK_INTER_THRES) &&
@ -1816,7 +1813,15 @@ int init_srv_check(struct server *srv)
* specified. * specified.
*/ */
if (!srv->check.port && !is_addr(&srv->check.addr)) { if (!srv->check.port && !is_addr(&srv->check.addr)) {
if (!srv->check.use_ssl && srv->use_ssl != -1) /*
* If any setting is set for the check, then we can't
* assume we'll use the same XPRT as the server, the
* server may be QUIC, but we want a TCP check.
*/
if (!srv->check.use_ssl && srv->use_ssl != -1 &&
!srv->check.via_socks4 && !srv->check.send_proxy &&
(!srv->check.alpn_len || (srv->check.alpn_len == srv->ssl_ctx.alpn_len && !strncmp(srv->check.alpn_str, srv->ssl_ctx.alpn_str, srv->check.alpn_len))) &&
(!srv->check.mux_proto || srv->check.mux_proto != srv->mux_proto))
srv->check.xprt = srv->xprt; srv->check.xprt = srv->xprt;
else if (srv->check.use_ssl == 1) else if (srv->check.use_ssl == 1)
srv->check.xprt = xprt_get(XPRT_SSL); srv->check.xprt = xprt_get(XPRT_SSL);
@ -2067,6 +2072,7 @@ static int srv_parse_addr(char **args, int *cur_arg, struct proxy *curpx, struct
char **errmsg) char **errmsg)
{ {
struct sockaddr_storage *sk; struct sockaddr_storage *sk;
struct protocol *proto;
int port1, port2, err_code = 0; int port1, port2, err_code = 0;
@ -2075,7 +2081,7 @@ static int srv_parse_addr(char **args, int *cur_arg, struct proxy *curpx, struct
goto error; goto error;
} }
sk = str2sa_range(args[*cur_arg+1], NULL, &port1, &port2, NULL, NULL, NULL, errmsg, NULL, NULL, NULL, sk = str2sa_range(args[*cur_arg+1], NULL, &port1, &port2, NULL, &proto, NULL, errmsg, NULL, NULL, NULL,
PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT); PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT);
if (!sk) { if (!sk) {
memprintf(errmsg, "'%s' : %s", args[*cur_arg], *errmsg); memprintf(errmsg, "'%s' : %s", args[*cur_arg], *errmsg);
@ -2083,6 +2089,7 @@ static int srv_parse_addr(char **args, int *cur_arg, struct proxy *curpx, struct
} }
srv->check.addr = *sk; srv->check.addr = *sk;
srv->check.proto = proto;
/* if agentaddr was never set, we can use addr */ /* if agentaddr was never set, we can use addr */
if (!(srv->flags & SRV_F_AGENTADDR)) if (!(srv->flags & SRV_F_AGENTADDR))
srv->agent.addr = *sk; srv->agent.addr = *sk;
@ -2112,7 +2119,11 @@ static int srv_parse_agent_addr(char **args, int *cur_arg, struct proxy *curpx,
goto error; goto error;
} }
set_srv_agent_addr(srv, &sk); set_srv_agent_addr(srv, &sk);
/* Agent currently only uses TCP */
if (sk.ss_family == AF_INET)
srv->agent.proto = &proto_tcpv4;
else
srv->agent.proto = &proto_tcpv6;
out: out:
return err_code; return err_code;

View file

@ -53,6 +53,22 @@ struct pool_head *pool_head_large_trash __read_mostly = NULL;
/* this is used to drain data, and as a temporary large buffer */ /* this is used to drain data, and as a temporary large buffer */
THREAD_LOCAL struct buffer trash_large = { }; THREAD_LOCAL struct buffer trash_large = { };
/* small trash chunks used for various conversions */
static THREAD_LOCAL struct buffer *small_trash_chunk;
static THREAD_LOCAL struct buffer small_trash_chunk1;
static THREAD_LOCAL struct buffer small_trash_chunk2;
/* small trash buffers used for various conversions */
static int small_trash_size __read_mostly = 0;
static THREAD_LOCAL char *small_trash_buf1 = NULL;
static THREAD_LOCAL char *small_trash_buf2 = NULL;
/* the trash pool for reentrant allocations */
struct pool_head *pool_head_small_trash __read_mostly = NULL;
/* this is used to drain data, and as a temporary small buffer */
THREAD_LOCAL struct buffer trash_small = { };
/* /*
* Returns a pre-allocated and initialized trash chunk that can be used for any * Returns a pre-allocated and initialized trash chunk that can be used for any
* type of conversion. Two chunks and their respective buffers are alternatively * type of conversion. Two chunks and their respective buffers are alternatively
@ -103,14 +119,40 @@ struct buffer *get_large_trash_chunk(void)
return large_trash_chunk; return large_trash_chunk;
} }
/* Similar to get_trash_chunk() but return a pre-allocated small chunk
* instead. Becasuse small buffers are not enabled by default, this function may
* return NULL.
*/
struct buffer *get_small_trash_chunk(void)
{
char *small_trash_buf;
if (!small_trash_size)
return NULL;
if (small_trash_chunk == &small_trash_chunk1) {
small_trash_chunk = &small_trash_chunk2;
small_trash_buf = small_trash_buf2;
}
else {
small_trash_chunk = &small_trash_chunk1;
small_trash_buf = small_trash_buf1;
}
*small_trash_buf = 0;
chunk_init(small_trash_chunk, small_trash_buf, small_trash_size);
return small_trash_chunk;
}
/* Returns a trash chunk accordingly to the requested size. This function may /* Returns a trash chunk accordingly to the requested size. This function may
* fail if the requested size is too big or if the large chubks are not * fail if the requested size is too big or if the large chubks are not
* configured. * configured.
*/ */
struct buffer *get_trash_chunk_sz(size_t size) struct buffer *get_trash_chunk_sz(size_t size)
{ {
if (likely(size <= trash_size)) if (likely(size > small_trash_size && size <= trash_size))
return get_trash_chunk(); return get_trash_chunk();
else if (small_trash_size && size <= small_trash_size)
return get_small_trash_chunk();
else if (large_trash_size && size <= large_trash_size) else if (large_trash_size && size <= large_trash_size)
return get_large_trash_chunk(); return get_large_trash_chunk();
else else
@ -122,17 +164,20 @@ struct buffer *get_trash_chunk_sz(size_t size)
*/ */
struct buffer *get_larger_trash_chunk(struct buffer *chk) struct buffer *get_larger_trash_chunk(struct buffer *chk)
{ {
struct buffer *chunk; struct buffer *chunk = NULL;
if (!chk) if (!chk || chk->size == small_trash_size) {
return get_trash_chunk(); /* no chunk or a small one, use a regular buffer */
chunk = get_trash_chunk();
}
else if (large_trash_size && chk->size <= large_trash_size) {
/* a regular byffer, use a large buffer if possible */
chunk = get_large_trash_chunk();
}
/* No large buffers or current chunk is alread a large trash chunk */ if (chk && chunk)
if (!large_trash_size || chk->size == large_trash_size) b_xfer(chunk, chk, b_data(chk));
return NULL;
chunk = get_large_trash_chunk();
b_xfer(chunk, chk, b_data(chk));
return chunk; return chunk;
} }
@ -166,9 +211,29 @@ static int alloc_large_trash_buffers(int bufsize)
return trash_large.area && large_trash_buf1 && large_trash_buf2; return trash_large.area && large_trash_buf1 && large_trash_buf2;
} }
/* allocates the trash small buffers if necessary. Returns 0 in case of
* failure. Unlike alloc_trash_buffers(), It is unexpected to call this function
* multiple times. Small buffers are not used during configuration parsing.
*/
static int alloc_small_trash_buffers(int bufsize)
{
small_trash_size = bufsize;
if (!small_trash_size)
return 1;
BUG_ON(trash_small.area && small_trash_buf1 && small_trash_buf2);
chunk_init(&trash_small, my_realloc2(trash_small.area, bufsize), bufsize);
small_trash_buf1 = (char *)my_realloc2(small_trash_buf1, bufsize);
small_trash_buf2 = (char *)my_realloc2(small_trash_buf2, bufsize);
return trash_small.area && small_trash_buf1 && small_trash_buf2;
}
static int alloc_trash_buffers_per_thread() static int alloc_trash_buffers_per_thread()
{ {
return alloc_trash_buffers(global.tune.bufsize) && alloc_large_trash_buffers(global.tune.bufsize_large); return (alloc_trash_buffers(global.tune.bufsize) &&
alloc_large_trash_buffers(global.tune.bufsize_large) &&
alloc_small_trash_buffers(global.tune.bufsize_large));
} }
static void free_trash_buffers_per_thread() static void free_trash_buffers_per_thread()
@ -180,6 +245,10 @@ static void free_trash_buffers_per_thread()
chunk_destroy(&trash_large); chunk_destroy(&trash_large);
ha_free(&large_trash_buf2); ha_free(&large_trash_buf2);
ha_free(&large_trash_buf1); ha_free(&large_trash_buf1);
chunk_destroy(&trash_small);
ha_free(&small_trash_buf2);
ha_free(&small_trash_buf1);
} }
/* Initialize the trash buffers. It returns 0 if an error occurred. */ /* Initialize the trash buffers. It returns 0 if an error occurred. */
@ -207,6 +276,14 @@ int init_trash_buffers(int first)
if (!pool_head_large_trash) if (!pool_head_large_trash)
return 0; return 0;
} }
if (!first && global.tune.bufsize_small) {
pool_head_small_trash = create_pool("small_trash",
sizeof(struct buffer) + global.tune.bufsize_small,
MEM_F_EXACT);
if (!pool_head_small_trash)
return 0;
}
return 1; return 1;
} }

Some files were not shown because too many files have changed in this diff Show more