Compare commits

...

67 commits

Author SHA1 Message Date
Christopher Faulet
1ad74d634a OPTIM: mux-fcgi: Reorganise fcgi_conn structure to fill some holes
Some checks failed
Contrib / admin/halog/ (push) Has been cancelled
Contrib / dev/flags/ (push) Has been cancelled
Contrib / dev/haring/ (push) Has been cancelled
Contrib / dev/hpack/ (push) Has been cancelled
Contrib / dev/poll/ (push) Has been cancelled
FreeBSD / clang (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
<drl> field was moved before <dsi> and term_evts_log was moved before the
demux buffer. 8 bytes was saved this way.
2026-06-16 18:03:01 +02:00
Tristan Madani
5985276735 BUG/MEDIUM: mux-fcgi: fix uint16_t overflow in drl += drp
The FCGI demux record length field (drl) is uint16_t. In the
ignore_record path, the expression "fconn->drl += fconn->drp" overflows
to 0 when contentLength=65535 and paddingLength>=1. This causes the
state machine to consider the record complete without consuming any
buffer data. The remaining buffer contents are then parsed as new FCGI
record headers.

The same drl+=drp pattern at lines 2382/2418/2475 is not affected
because drl is guaranteed to be 0 at those points (all content bytes
are consumed before reaching end_transfer).

Widen drl from uint16_t to uint32_t so that the addition of drp
(uint8_t, max 255) cannot overflow.

Reported-by: Tristan (@TristanInSec)
2026-06-16 18:03:01 +02:00
Christopher Faulet
43932db851 BUG/MEDIUM: http-act: Make a copy of the sample expr in (set/add)-headers-bin
For set-headers-bin and add-header-bin actions, the new HTX headers to add
or set are extracted from the result of a sample expression evaluation. This
result must be copied because it is based a static trash chunk and internal
functions manipulating HTX messages also use static chunks. So to not
overwrite the sample expression, for instance because a defrag is performed,
it must be copied into a dynamically allocated chunk.

It is also important because the sample expression may be based on part of
the HTX message itself.

This patch must be backported to 3.4.
2026-06-16 18:03:01 +02:00
Amaury Denoyelle
7266ed8c30 BUG/MEDIUM: mux_quic: fix freeze transfer after QCS rxbuf realign
Some checks failed
Contrib / admin/halog/ (push) Has been cancelled
Contrib / dev/flags/ (push) Has been cancelled
Contrib / dev/haring/ (push) Has been cancelled
Contrib / dev/hpack/ (push) Has been cancelled
Contrib / dev/poll/ (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
QCS uses a multiple Rx buffer for incoming data to decode. However this
may cause the app proto layer to block on decoding if some data are
splitted across two buffers. In this case qcs_transfer_rx_data() is used
to move the data in the same current buffer.

However, this function contains a bug as the next Rx buffer is not
reinserted as expected in the QCS tree after the move operation. This
can cause data loss which will cause a transfer freeze. This may affect
POST requests on the frontend side as well as most backend transfers.

This patch fixes the reinsert operation. It is now performed as expected
if the next rxbuf is not fully truncated. In the opposite case, the
rxbuf can simply be freed completely.

This must be backported up to 3.2.
2026-06-16 18:00:13 +02:00
Tristan Madani
9a6d1fe3f0 BUG/MINOR: hpack-tbl: add missing NULL check after hpack_dht_defrag()
hpack_dht_insert() has three call sites for hpack_dht_defrag(). Two of
them (lines 293 and 306) correctly check for a NULL return and bail out
with -1. The third (line 353, data-space defrag path) assigns the return
value to dht and immediately dereferences it without a NULL check.

When pool_head_hpack_tbl is exhausted, hpack_dht_alloc() returns NULL,
hpack_dht_defrag() propagates it, and line 354 dereferences NULL+0x0a
(offsetof wrap), crashing the worker with SIGSEGV.

Add a NULL check consistent with the two other call sites.

This must be backported to all stable versions.

Reported-by: Tristan (@TristanInSec)
2026-06-16 17:03:14 +02:00
Olivier Houchard
8e1c51378e BUG/MEDIUM: ssl: Don't free the early data buffer too early
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (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 0RTT is enabled, a temporary buffer for early data is used. We read
from it first when the mux asks for data, and then we free it when it is
empty, but that is not right, because maybe we have more early data to
receive, and then we no longer have any buffer to store them, and that
will eventually end up with the connection closed in error.
To fix that, as long as we haven't received all the early data yet, just
reset the buffer, instead of freeing it.
This should fix github issue #3416
This should be backported up to 2.8.
2026-06-15 19:40:43 +02:00
William Lallemand
33c765fc59 EXAMPLES: lua/acme: fix acme-gandi-livedns.lua configuration example
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (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 configuration example in acme-gandi-livedns.lua.
2026-06-15 13:50:22 +02:00
Amaury Denoyelle
08aa12392d BUG/MINOR: quic: fix rxbuf settings on backend side
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (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
QUIC flow control on bidirectional streams ensure that the peer cannot
emit more than what haproxy has allowed, which guarantees that buffering
is under controlled on the receiver side.

This limit is first announced on the transport parameter via
initial_max_stream_data_bidi_remote which is derived from configuration
value. QUIC MUX calculation for its streams is then directly based on
the transport parameter.

This mechanism works as expected on the frontend side, as in this case
all exchanges occur on remote streams opened by the opposite side.
However, this is not working as expected on the backend side, as in this
case transfers occur on streams opened locally by haproxy as the client.
Thus, configuration has no impact on backend side rxbuf which remains
set to a single buffer, causing important latency when retrieving large
objects.

This patch removes this limitation on the backend side by adjusting
quic_transport_params_init(). If <server> parameter is false, limitation
is set for initial_max_stream_data_bidi_local TP.

This must be backported up to 3.3.
2026-06-15 09:36:42 +02:00
Christopher Faulet
82a16a2927 BUG/MINOR: mux-h1: Properly resolve file path for 'h1-case-adjust-file'
The file specified by 'h1-case-adjust-file' directive is only loaded during
post-parsing. However when a relative path is used, the corresponding
absoulte path was not resolve during parsing. So the file could be loaded
relatively from the wrong location leading to a configuration error. It may
happen if several configuration files are used or if several
"default-config" are used. The last "default" location was always used.

To fix the issue, the absolute path of the file is now resolved when the
directive is parsed.

This patch should fix the issue #3415. It must be backported to all
versions.
2026-06-15 08:55:56 +02:00
Christopher Faulet
292b07270c BUG/MEDIUM: http-ana: Don't ignore L7 retry errors
with L7 retries are configured, when the max number of retries is reached
the error must be reported to the client. However, when it was an abort on a
reused connections, the client connection is silently closed. While it is
expected without L7 retries, to let the client retries on its own, it is
unexepcted with L7 retries.

So let's fix it by ignoring the SF_SRV_REUSED flag on the stream when a L7
retry fails. This way, a 502/425 will be reported to the client.

This patch should help to fix the issue #3414. It must be backported to all
supported versions.
2026-06-15 08:55:56 +02:00
Christopher Faulet
7bfa568e27 BUG/MINOR: http-ana: Remove a debugging memset on redirect
A memset used for debug was left when "keep-query" option was added. Let's
remove it. This bug should be harmless but it consumes extra CPU for
nothing.

This patch should be backported as far as 3.2.
2026-06-15 08:55:56 +02:00
Christopher Faulet
c64e242b1c DEBUG: stconn: Add a BUG_ON on shut flags when the endpoint is shut
Whne the endpoint is shut (an applet or a mux), at least one of the SHW
flags must be set (SE_SHW_SILENT or SE_SHW_NORMAL). It is mandatory for
muxes to perform the shutdown. Otherwise, the shutdown could be ignored.

So let's add a BUG_ON() on it to be sure this never happen.
2026-06-15 08:55:56 +02:00
William Lallemand
5530f50e4b DOC: httpclient: document status 0 on internal error
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (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 patch documents the behavior where the internal HTTP client sets
the response status to 0 when an error is encountered by the stream
(SF_ERR_MASK).

This allows users to distinguish between an HTTP status code returned
by a remote server and an internal error generated by HAProxy (e.g.
connection timeout, connection refused, etc.).
2026-06-14 13:19:26 +02:00
William Lallemand
f371bfd608 MEDIUM: httpclient: set res.status to 0 upon SF_ERR_MASK
With the httpclient it's difficult to get if the HTTP status code was
returned by the actual server or if it's the internal proxy that
generate the error.

This patch changes the behavior by setting the status to 0 when an error
is get by the stream.

There were already valid cases when the status was 0 on some error, so
that should not really change the error path in the scripts.
2026-06-14 10:46:38 +02:00
William Lallemand
0748799ad1 MEDIUM: httpclient/lua: allow multiple requests from a single core.httpclient() instance
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (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
Refactor the Lua HTTP client to defer initialization. core.httpclient()
no longer initializes the internal HTTP client immediately. Instead,
initialization now occurs within hlua_httpclient_send() when a request
method (e.g., get, put, head) is invoked.

The HTTPClient class now serves as a factory for accessing methods, while
a new class, HTTPClientRequest, has been introduced to represent individual
requests and manage the HTTP client lifecycle.

This change allows multiple requests to be executed using a single
HTTP client instance:

  local hc = core.httpclient()
  local res1 = hc:get({url = "...", headers = ...})
  local res2 = hc:post({url = "...", headers = ...})
  local res3 = hc:put({url = "...", headers = ...})

This refactor maintains backward compatibility, as existing scripts that
instantiate a new core.httpclient() for every request will continue to
work as expected.
2026-06-14 01:49:54 +02:00
William Lallemand
339d25636d REORG: httpclient/lua: move the lua httpclient code to http_client.c
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (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
Move the lua httpclient code from hlua.c to http_client.c

The code is almost the same but the registering of the class which is
done in hlua_http_client_init_state(), from REGISTER_HLUA_STATE_INIT()

check_args() calls have been replaced by hlua_check_args().

hlua_httpclient_destroy_all() is exported so it can be called in hlua.c.
hlua_httpclient_table_to_hdrs() is made static.
2026-06-13 21:18:20 +02:00
William Lallemand
974560128d MINOR: lua: export hlua_pusherror() and check_args()
hlua_pusherror() and check_args() are being exported.

check_args() is now a macro to hlua_check_args() so it's not confusing
when called outside hlua.c.
2026-06-13 21:18:20 +02:00
Amaury Denoyelle
2ff861747c BUG/MINOR: server: fix add server with consistent hash balancing
Some checks failed
Contrib / admin/halog/ (push) Has been cancelled
Contrib / dev/flags/ (push) Has been cancelled
Contrib / dev/haring/ (push) Has been cancelled
Contrib / dev/hpack/ (push) Has been cancelled
Contrib / dev/poll/ (push) Has been cancelled
FreeBSD / clang (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 dynamic server is added with consistent hash balancing on the
backend, its lb_nodes elements are allocated and associated with a
calculated server key. This operation is performed in add server handler
via srv_alloc_lb(). By default, the server key is based on its ID.
However, automatic server ID is calculated later in add server handler,
which means the initial lb_nodes are not valid.

This could cause load balacing issue but in fact this is not directly
visible as the server key is recalculated when the dynamic server is
enabled via chash_queue_dequeue_srv() : all server lb_nodes are dequeued
and requeued with the now proper key.

Thus, "add server" handler must be corrected as it is buggy when
considering it alone. The simplest solution of the current patch is to
initialize server ID before srv_alloc_lb() is invoked. There is no issue
as handler runs under thread isolation so there is no risk of multiple
servers manipulating the same ID. Server insertion in proxy ID tree is
still performed at the end of the handler when all fallible operation
are completed.

The fact that server key is recalculated when the server is set to ready
state is a side effect of the following patch which was introduced in
3.0. What this means though is that users of older releases are facing a
bigger issue, with load-balancing not working as expected. Thus,
this patch is even more crucial for 2.8 and older releases.

  faa8c3e024
  MEDIUM: lb-chash: Deterministic node hashes based on server address

This should fix github issue #3413. Thanks to Joao Morais for is
analysis on the problem.

This must be backported to all stable releases.
2026-06-12 15:31:41 +02:00
Olivier Houchard
98b1fd4ff9 BUG/MEDIUM: h3: Properly handle PUSH_PROMISE on backend connections
When we receive a PUSH_PROMISE frame while we don't expect it, flag it
as a connection error, do not just set ret to H3_ERR_ID_ERROR, as it
would just be considered the number of bytes we read, and could lead to
random corruption. This should only happen with backend connections.
This should be backported whenever commit 4a8bb2fe5 is backported.
2026-06-12 14:01:07 +02:00
Olivier Houchard
b9aa1c0e64 MEDIUM: tasks: Redispatch shared tasks when the thread is loaded
Now that there is no longer a shared wake queue, chances are if a shared task
is scheduled, it will always end up on the same thread. In
wake_expired_tasks(), when a task has to be waken up, randomly look to
three other threads, and if the runqueue of the current thread is at least
two time bigger than the runqueue of one of the other threads, then give
that task to that thread, so that our load gets reduced.
If we're giving the task to another thread, then we have to add the
TASK_RUNNING flag until we waked it up, otherwise the other thread could
just run it, if it gets waken up from another path, and free it while
we're still not done with it.
2 times has been chosen somewhat arbitrarily, and may be tweaked at a
later date if deemed not optimal.
2026-06-12 11:49:09 +02:00
Olivier Houchard
aaee6c463c MINOR: tasks: Remove wq_lock and the per-thread group wait queues
Now that they are no longer used, remove wq_lock and the per-thread
group wait queues.
2026-06-12 11:49:09 +02:00
Olivier Houchard
caa1cd0674 MINOR: tasks: Use __task_set_state_and_tid() in task_instant_wakeup()
Modify task_instant_wakeup() to use __task_set_state_and_tid().
It uses the new ownership behavior, but that's okay because
task_instant_wakeup() was not used anywhere.
2026-06-12 11:49:09 +02:00
Olivier Houchard
0988b9c773 MEDIUM: tasks: Remove the per-thread group wait queue
Totally remove the per-thread group wait queue. This was potentially a
source of contention, because there were only a global lock for all
those wait queues.
Instead, for shared tasks, there is now the concept of ownership for the
task. When a task is in the wait queue, run queue, or is running on that
particular thread, the task's tid is set to -2 - thread_tid, and only
that thread will be responsible for it until it is no longer running,
and in none of its queue.
When a shared task is scheduled to be run at a later time, if its
current tid is -1, then the current thread will take ownership, and put
it in its own wait queue. If it is already owned, then TASK_WOKEN_WQ is
added to the task's state, and a task_wakeup() is done, so that the
owner thread will add it in its wait queue.
If there is any owner, then a task_wakeup() will just add the task to
the owner's runqueue, otherwise the current thread will become the
owner.
2026-06-12 11:49:09 +02:00
Olivier Houchard
c9f3ddcb1e MINOR: tasks: Start using __task_set_state_and_tid()
Start using __task_set_state_and_tid() when we're changing the state of
the task while queueing it, in preparation to the future ownership
changes.
2026-06-12 11:49:09 +02:00
Olivier Houchard
95cb3251a0 MINOR: tasks: Use __task_get_current_owner() in task_kill.
In task_kill(), to know which thread to send the task to, use
__task_get_current_owner(), in preparation for future changes.
2026-06-12 11:49:09 +02:00
Olivier Houchard
74b16c5477 MINOR: tasks: Introduce __task_get_current_owner
Introduce a new function, __task_get_current_owner, that returns the
owner of a task based on its current tid.
-1 means there is no current owner, otherwise either the tid is >= 0, in
which case it will just return it, or it's < -1, in which case it will
return -2 - tid, the tid of the thread with the current ownership.
2026-06-12 11:49:09 +02:00
Olivier Houchard
8b6d8f5e4f MINOR: tasks: Add __task_get_new_tid_field()
Introduce __task_get_new_tid_field(), that provides the tid to be used
for a task.
For shared task, to mark temporary ownership of a task, instead of -1,
the tid will be set to -2-tid, tid being the tid of the current thread.
2026-06-12 11:49:09 +02:00
Olivier Houchard
91f9e3a3dd MINOR: tasks: Introduce __task_set_state_and_tid
Introduce a new function, __task_set_state_and_tid, that atomically can
set a task's state and its tid. This will be used later, as the tid will
be used to indicate task ownership even for shared tasks.
2026-06-12 11:49:09 +02:00
William Lallemand
92206fb02f DOC: acme: add mentions of lua features
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (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
Mention ACME.challenge_ready() and event_hdl which are useful in lua to
implement dns-01.
2026-06-11 23:51:45 +02:00
William Lallemand
d2c9bf70e5 EXAMPLES: lua/acme: add a dns-01 handler for Gandi LiveDNS API
This Lua script automates dns-01 ACME challenges using the Gandi LiveDNS
API v5. It subscribes to the ACME_DEPLOY event to set the required
_acme-challenge TXT record via the Gandi REST API, signals HAProxy that
the challenge is ready using ACME.challenge_ready(), then cleans up the
TXT record once the certificate is issued on ACME_NEWCERT.

The API key is read from the GANDI_API_KEY environment variable at
startup. Zone discovery is automatic: the script probes parent zones
from longest to shortest until Gandi accepts the record, which handles
both apex and wildcard certificates transparently.
2026-06-11 19:37:49 +02:00
William Lallemand
4bb21dae2f MINOR: acme: publish ACME_DEPLOY event via event_hdl
Add EVENT_HDL_SUB_ACME_DEPLOY to the ACME family. It is published in
the dns-01 challenge path after the TXT record information has been
prepared, carrying the certificate store name, domain, account
thumbprint, dns_record value, and optionally the provider and vars
strings.

Lua subscribers using core.event_sub() receive the event data as an
AcmeEvent object, which is the same class used for ACME_NEWCERT and
carries the fields relevant to the event type.
2026-06-11 19:14:52 +02:00
William Lallemand
81d7624e01 MINOR: acme: publish ACME_NEWCERT event via event_hdl
Add a new EVENT_HDL_SUB_ACME_NEWCERT event type in the ACME family.
It is published after a new certificate has been successfully fetched
and installed. The event carries the certificate store name, allowing
subscribers to act on newly available certificates.

Lua subscribers using core.event_sub() receive the event data as an
AcmeEvent object with a crtname field containing the certificate store
name.
2026-06-11 19:14:52 +02:00
Willy Tarreau
960fa1c921 BUG/MINOR: cpu-topo: use ha_diag_notice() to report thread creations
Using ha_diag_warning() to report the number of threads created resulted
in warnings being counted and possibly an error being fired when combined
with -dW:

  $ printf "global\nstats socket /tmp/sock1\n" | ./haproxy -dD -dW -c -f /dev/stdin; echo $?
  [NOTICE]   (10406) : haproxy version is 3.5-dev0-5091ac-35
  [NOTICE]   (10406) : path to executable is ./haproxy
  [DIAG]     (10406) : Created 20 threads split into 2 groups
  [ALERT]    (10406) : Some warnings were found and 'zero-warning' is set. Aborting.
  1

Now that we have ha_diag_notice(), let's use it:

  $ printf "global\nstats socket /tmp/sock1\n" | ./haproxy -dD -dW -c -f /dev/stdin; echo $?
  [DIAG]     (10513) : Created 20 threads split into 2 groups
  0

It would make sense to backport this to 3.2 because it helps validate configs
against diag warnings without triggering a false positive. It depends on
this previous patch:

  MINOR: errors: add ha_diag_notice() to report diag-level notifications
2026-06-11 18:49:57 +02:00
Willy Tarreau
7d63efa5f5 MINOR: errors: add ha_diag_notice() to report diag-level notifications
Right now the only way to report info that is only displayed in diag
mode with -dD is to use ha_diag_warning(). The problem is that this is
then counted as a warning and may result in errors when combined with
-dW, as happens for the CPU topology info:

  $ printf "global\nstats socket /tmp/sock1\n" | ./haproxy -dD -dW -c -f /dev/stdin; echo $?
  [NOTICE]   (10406) : haproxy version is 3.5-dev0-5091ac-35
  [NOTICE]   (10406) : path to executable is ./haproxy
  [DIAG]     (10406) : Created 20 threads split into 2 groups
  [ALERT]    (10406) : Some warnings were found and 'zero-warning' is set. Aborting.
  1

We need another level. This commit introduces ha_diag_notice() which only
emits a notification that doesn't count as a warning. Note that we could
even introduce an info level and revisit various messages so that notice
only reports certain events while info is for anything (like versions
above). That could be a future improvement.
2026-06-11 18:48:59 +02:00
Karol Kucharski
96b08e959c BUG/MEDIUM: ktls: defer enabling TLS ULP on a socket until connected
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (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 Linux tls module requires a socket to be in TCP_ESTABLISHED state
before we can enable the TLS ULP on the socket, if the socket is in any
other state, then the setsockopt() call will fail, and we won't use
kTLS on that socket.
To make sure we're not doing it too early, defer it until the TLS
handshake is done, which means the TCP connection is established.

This should be backported up to 3.3.

Signed-off-by: Karol Kucharski <kkucharski@fastlogic.pl>
2026-06-11 14:18:31 +02:00
William Lallemand
784f972a6f MINOR: acme/lua: implement ACME.challenge_ready() Lua function
Add a new ACME global Lua table with a challenge_ready(crt, dns) method
that wraps acme_challenge_ready(). It marks the ACME challenge for domain
<dns> in certificate <crt> as ready and returns the number of remaining
challenges, or 0 when all challenges are ready and validation has been
triggered. A Lua error is raised if the certificate or domain is not found.

The ACME table is registered for each lua_State via the new
REGISTER_HLUA_STATE_INIT() mechanism.
2026-06-11 15:01:38 +02:00
William Lallemand
5c0733db9a MEDIUM: lua: move longjmp annotation macros to hlua.h
__LJMP, WILL_LJMP() and MAY_LJMP() were defined locally in hlua.c,
making them unavailable to other modules that implement Lua bindings.
Move them to include/haproxy/hlua.h so they can be used outside of
hlua.c.
2026-06-11 14:40:27 +02:00
William Lallemand
d0fde90e16 MINOR: lua: add REGISTER_HLUA_STATE_INIT() to register state init callbacks
Add a registration mechanism so that modules outside of hlua.c can hook
into each lua_State creation. Modules call hap_register_hlua_state_init()
(or the REGISTER_HLUA_STATE_INIT() macro) with a callback of the form:

  int my_init(lua_State *L, char **errmsg);

The callback returns an ERR_* code. ERR_ALERT and ERR_WARN trigger
ha_alert()/ha_warning() respectively; any other non-zero errmsg is
emitted via ha_notice(). ERR_FATAL or ERR_ABORT cause exit(1).
Registered entries are freed in hlua_deinit().
2026-06-11 14:13:04 +02:00
Amaury Denoyelle
3dfd86062b BUILD: h3: fix compilation with USE_TRACE=0
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (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
Mark argument in h3_trace_header as unused if USE_TRACE is not set.

No need to backport unless HTTP/3 header traces are picked.
2026-06-11 11:47:57 +02:00
Amaury Denoyelle
cc01214a67 MINOR: h3: trace HTTP headers on BE side
Output HTTP/3 header traces on the backend side. As previous commit,
this relies on h3_trace_header() function.

Extra calls are added for fields extracted from the request start-line
which produce an HTTP/3 pseudo-header.
2026-06-11 11:40:06 +02:00
Amaury Denoyelle
00c081b5f3 MINOR: h3: trace HTTP headers on FE side
Output trace for HTTP/3 headers manipulated on the frontend side. This
is implement via a new utility function h3_trace_header(), largely
inspired from existing h2_trace_header().

An extra call is added for HTTP/3 response :status which is extracted
from the HTX start line.
2026-06-11 11:40:06 +02:00
Amaury Denoyelle
53fe4181a5 MINOR: h3: extend trace verbosity
Define two new values for HTTP/3 trace verbosity : simple and advanced.
For now, these values are unused. However advanced level will become
useful to implement HTTP/3 header traces.
2026-06-11 11:40:06 +02:00
William Lallemand
9e60d35aaf MINOR: acme: introduce acme_challenge_ready() for reuse outside the CLI
Extract the challenge-readiness logic from cli_acme_chall_ready_parse()
into a new acme_challenge_ready(crt, dns) function so it can be called
from other contexts such as Lua event handlers.

It slightly changes the messages on the CLI.
2026-06-11 11:33:27 +02:00
William Lallemand
0a90ff6b3d BUG/MEDIUM: acme: stuck ACME task when authz is already "valid"
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (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 an ACME order is re-used or when a domain was recently validated,
the CA may return status "valid" for an authorization without requiring
any challenge to be solved.  In acme_res_auth(), this is handled by
setting auth->validated = 1 and jumping to out — but auth->ready is
never initialized and stays 0.

This became a bug in 3.4 when the "challenge-ready" option and the
ACME_CLI_WAIT state were introduced (commit 2b0c510aff).  ACME_CLI_WAIT
computes:

    all_cond_ready &= auth->ready;

across all authorizations.  A single auth->ready == 0 drives the AND
to zero and the task waits indefinitely for a readiness signal that
will never arrive, since no challenge was published and no external
agent will ever call challenge_ready() for that domain.

Fix it by setting auth->ready = ctx->cfg->cond_ready for already-valid
authorizations, marking them as satisfying all required readiness
conditions so ACME_CLI_WAIT can proceed normally.

This should be backported to 3.4.
2026-06-10 18:19:55 +02:00
Olivier Houchard
6c75202b48 BUILD: servers: Fix build with -std=gnu89
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (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
Commit 3c923d075 introduced a C99ism by declaring a variable in a for loop,
don't do that, especially since there already is a variable named "i"
declared.
This should fix the build when -std=c89 is used.
This should be backported if commit 3c923d075 is backported.
2026-06-10 10:28:52 +02:00
Amaury Denoyelle
fb38e40ad5 BUG/MINOR: quic: fix Initial length value in sent packets
Some checks failed
FreeBSD / clang (push) Waiting to run
Contrib / admin/halog/ (push) Has been cancelled
Contrib / dev/flags/ (push) Has been cancelled
Contrib / dev/haring/ (push) Has been cancelled
Contrib / dev/hpack/ (push) Has been cancelled
Contrib / dev/poll/ (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 packets using a long header contains a Length field. Its value is
the length of the content following it, i.e. the packet number field and
the remaining payload (QUIC frames and TLS AEAD tag).

Computation to determine the packet length is performed in
qc_do_build_pkt(). However this calculation is incorrect when Initial
padding is added on a small enough Initial packet. As length field is
encoded as a varint, it changes the field size which grow from one to
two bytes, reducing in effect the total required padding length from one
byte. However, length value is not updated and thus is one byte bigger
than the final packet payload with padding.

Fix this calculation by reducing the length value after padding size has
been adjusted.

This bug caused the peer to reject such faulty packets. However, its
impact is minor as it only happened only for Initial with small enough
payload. Packets used for ClientHello/ServerHello exchanges should be
large enough and typically not concerned by this bug, except maybe in
case of fragmentation.

This bug was detected by testing QUIC backend with a quiche server. The
server endpoint reported the faulty packets with the following trace :
  [2026-06-09T13:42:13.694179158Z ERROR quiche_server] 1b1b961c9c4ae1f470f3687510b120da1f5d5f5a recv failed: InvalidPacket

This must be backported up to 3.0.
2026-06-09 15:42:33 +02:00
Christopher Faulet
9bc37232f4 REGTESTS: Fix log matching in healthcheck-section.vtc
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (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
One of the regex matching syslog messages for S2 was not correct, making the
script fail depending on the order of the healthchecks. It is now fixed.
2026-06-09 08:42:01 +02:00
Olivier Houchard
3c923d075c MEDIUM: servers: Move to a per-thread idle connection cleanup task
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (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
Having a single task to take care of idle connection cleanup across all
servers leads to high contention. It uses a lock to maintain its tree of
servers to track, and then can acquire the idle_conns lock for each thread.
Instead, have one task per thread. Each thread will maintain its own
tree, so there will be no need for any lock, and it will just acquire
its own idle_conns lock, so it will lead to less contention.
This is a performance improvement, so backporting is optional, but may be
considered if it is worth it. That would require backporting commit
6f8dab2583 too.
2026-06-08 15:38:22 +02:00
Olivier Houchard
6f8dab2583 MINOR: servers: Add a back-pointer to the server in srv_per_thread
In struct srv_per_thread, add a pointer to the server, as with just a
pointer to srv_per_thread, we can't figure out the related server.
2026-06-08 15:37:50 +02:00
Olivier Houchard
a4520229a7 BUG/MEDIUM: checks: Dequeue checks on purge
When tune.max-checks-per-thread is used, checks that should run are
queued, to avoid having too many checks running at the same time.
But if the check is about to be purged, because the server is being
deleted, we have to explicitly remove it from the queue as that memory is
about to be freed, otherwise it will cause a use-after-free.
Also, queued checks have not yet incremented th_ctx->running_checks, so
don't decrement it if we're queued.

This should be backported up to 3.0.
2026-06-08 15:06:09 +02:00
Willy Tarreau
3fa818c78f MINOR: memprof: be careful to account allocations only once
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (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
For certain calls like strdup(), certain libc call the malloc() symbol
themselves, resulting in both strdup() and malloc() accounting for the
allocation while a single free() call is accounted for. Usually it's
not very hard to spot as these allocations are done inside libc, but
yet they complicate the tracing of allocations.

Let's note when we enter a handler and refrain from doing the accounting
again in this case. This way, the strdup() call place will be accountable
for the allocation and the libc's internal malloc() will not be seen.
2026-06-08 13:46:18 +02:00
Willy Tarreau
a7888f0373 MINOR: memprof: make in_memprof a bitfield instead of a counter
It's not convenient to use it as it is now because it may only be
used to count passes via the memprof init code. Let's turn it to
a bitfield instead so that we can also check what we're doing there.
This is safe because all callers of memprof_init() check for the
bit being zero first so it's not reentrant.
2026-06-08 13:46:18 +02:00
Willy Tarreau
ef191c46d7 BUG/MINOR: acl: report "ACL" not "map" in ACL ID lookup failures
As reported by @broxio in issue #3411, when trying to delete an ACL by
its name, in case of error the message says "unknown map identifier".
We need to check the type to decide between map and ACL as in other
messages.

This can be backported to all stable branches. Thanks to @broxio for
reporting the issue with a reproducer and providing this tested fix.
2026-06-08 13:45:39 +02:00
Willy Tarreau
b9fa07bd20 MINOR: pools: reject creation of pools containing invalid chars in their name
In order to preventively avoid issues that complicate debugging, let's
report to developers early if a pool name is not acceptable. This patch
does it in create_pool_from_reg() which catches both direct and declared
registrations. Aside the previous case, this didn't catch any other
occurrence.
2026-06-08 08:54:37 +02:00
Willy Tarreau
172306c308 CLEANUP: sessions: simplify the sess_priv_conns pool name
Using "show pools detailed" on the CLI breaks the column alignment on
"sess_priv_conns" because the pool name contains spaces: "session priv
conns list", which is not welcome as pool names are truncated after the
12th chars anyway. Let's shorten it to the pool's name as done for many
other ones: sess_priv_conns.

This can be backported as far as 3.0 where this name was introduced,
because it helps when trying to sum or graph certain metrics during
debugging.
2026-06-08 08:44:25 +02:00
Willy Tarreau
e51ae5ce66 BUG/MEDIUM: xprt_qmux: implement ->get_ssl_sock_ctx() to get the SSL laye
conn_get_ssl_sock_ctx() retrieves the ssl_sock_ctx of a connection by
calling conn->xprt->get_ssl_sock_ctx(). Only ssl_sock implements this
method, and it returns conn->xprt_ctx. This works because for every
existing XPRT combination the SSL layer is the topmost one: even
xprt_handshake (SOCKS4, PROXY, NetScaler CIP) is installed *below*
ssl_sock, so conn->xprt keeps pointing to ssl_sock.

Qmux changes this assumption: xprt_qmux is stacked *on top of* ssl_sock
and keeps the SSL layer as its lower layer to exchange the QUIC transport
parameters over the established TLS stream. During the qmux handshake,
conn->xprt therefore points to xprt_qmux, which does not implement
get_ssl_sock_ctx(), making conn_get_ssl_sock_ctx() return NULL for the
whole connection, affecting every caller that inspects the SSL layer
(sample fetches, logging, ssl_sock_infocbk(), ...).

The visible consequence was a crash: when the peer sends a TLS alert
during the qmux handshake, the SSL library calls ssl_sock_infocbk(),
which recovers a valid connection but a NULL ctx, rightfully triggering
the "BUG_ON(!ctx)" early in the function.

This patch implements xprt_qmux_get_ssl_sock_ctx() so that it returns
the ssl_sock_ctx of the lower layer when it is the SSL layer, just like
ssl_sock_get_ctx() does. conn_get_ssl_sock_ctx() then works again for
all callers while the qmux handshake is in progress. After the handshake,
conn->xprt is restored to the SSL layer so nothing else changes.

This should be backported to 3.4.
2026-06-08 08:31:20 +02:00
Olivier Houchard
45a64123d6 BUG/MEDIUM: threads: Fiw build when using no thread
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (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 thread_detect_count(), avoid any usage of thread_cpu_enable_at_boot
if we're building without thread support. That variable is only defined
when building with threads, and those tests make little sense when
building with no thread, anyway.
This was submitted by: ririnto <ririnto@kakao.com>
This should fix github issue #3408.
This should be backported to 3.4.
2026-06-08 01:16:49 +02:00
Willy Tarreau
ac776e3819 BUG/MEDIUM: regex: initialize the match array earlier during boot
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (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
As reported by @zhanhb in github issue #3410, since 3.3 with commit
fda6dc959 ("MINOR: regex: use a thread-local match pointer for pcre2"),
the local_pcre2_match array is initialized too late for use by Lua. If
a lua-load makes use of regex, it may segfault (actually using PCRE2
is fine but PCRE2_JIT will crash):

Let's change the init sequence so that the first thread's context is
initialized early at boot and other threads are initialized when they
are created. For lua-load-per-thread, all extra threads will run on
the first thread's temporary storage during init but that's not a
problem since the sole purpose is to avoid concurrent accesses.

Thanks to @zhanbb for the detailed report and quick tests. This needs
to be backported to 3.3.
2026-06-07 07:46:32 +02:00
Christopher Faulet
1e00743520 REGTESTS: checks: Add script for external healthchecks
Some checks failed
Contrib / admin/halog/ (push) Has been cancelled
Contrib / dev/flags/ (push) Has been cancelled
Contrib / dev/haring/ (push) Has been cancelled
Contrib / dev/hpack/ (push) Has been cancelled
Contrib / dev/poll/ (push) Has been cancelled
FreeBSD / clang (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 script is quite basic but it should validate the external healthchecks
are working well.
2026-06-05 17:15:31 +02:00
Christopher Faulet
b227ad2dc7 BUG/MINOR: tcpcheck: Override external check if healthcheck section is set
When an external check was configured at the proxy level, the healthcheck
section set on a server was not considered. The main reason was that the
check type of the server was always inherited for the proxy one.

To fix the issue, when a healthcheck section is set on a server line, the
check type for the server is forced to TCPCHK.

This patch must be backported to 3.4.
2026-06-05 17:15:31 +02:00
Amaury Denoyelle
07deafa104 BUG/MINOR: mux_quic: do not interrupt recv on error/incomplete data
Some checks failed
Contrib / admin/halog/ (push) Has been cancelled
Contrib / dev/flags/ (push) Has been cancelled
Contrib / dev/haring/ (push) Has been cancelled
Contrib / dev/hpack/ (push) Has been cancelled
Contrib / dev/poll/ (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
Prior to this patch, qcc_io_recv() stream decoding loop was interrupted
on the first decoding error or if incomplete data could not be parsed.

This patch adjusts this part so that loop is stopped only on a
connection level error. In case of a stream level error or on incomplete
data, decoding continues on the next QCS entry.

Without this patch, there is a risk that a QCS decode is not performed
as expected, with a possible client timeout firing. This is pretty
unlikely though. However this patch is still necessary to remove
completely this possibility.

This should be backported up to 3.2.
2026-06-05 16:27:10 +02:00
Amaury Denoyelle
a39b1a40ad OPTIM: mux_quic: remove QCS from recv_list on reset
When a RESET_STREAM is received, QCS Rx channel is closed and pending Rx
data and buf are cleared without being transmitted to upper stream
layer.

This patch complements this by removing the QCS from recv_list if
present in it. This is a small optimization nothing would be performed
for such QCS on qcc_io_recv().
2026-06-05 15:42:44 +02:00
Amaury Denoyelle
83ae0c250c BUG/MEDIUM: mux_quic: prevent risk of infinite loop on recv
When a RESET_STREAM is received, QCS Rx channel is closed and pending Rx
data and buf are cleared without being transmitted to upper stream
layer.

This can cause an issue if this QCS instance is present in the QCC
recv_list. When qcc_io_recv() is executed after reset handling, an
infinite loop is triggered for the QCS instance as qcs_rx_avail_data()
always return 0.

This issue happened due to the poor writing of the while loop in
qcc_io_recv() which is not correctly protected against infinite
execution.

To prevent this issue, this patch rewrites the loop. Crucially,
LIST_DEL_INIT() is now performed unconditionally outside of the inner
loop. This guarantees that even if the inner loop is not executed, the
stream will be removed from QCC recv_list and iteration will progress.

This is functionally correct as a QCS should not be present in recv_list
if there is no avail data or demux is currently blocked. For the first
condition, qcc_decode_qcs() will be called again when new data is read
unless demux is blocked. In this case, QCS will be reinserted in the
list on unblocking, with a rescheduling to invoke qcc_decode_qcs().

In the context of the currently found reproducer linked to stream reset,
the QCS instance can be safely removed from the recv_list without
implication.

This must be backported up to 3.2.
2026-06-05 15:32:55 +02:00
Christopher Faulet
f7bc8246ee BUG/MEDIUM: server/checks: Support healtcheck keyword on default-server lines
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
FreeBSD / clang (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 healthcheck keyword could be parsed on default-server lines but not
copied during server initialization, making it ineffective. But there is
also a true issue by setting it on a default-server. The pseudo server used
to parse the default-server line is not initialized via the new_server()
function, as regular servers. So there is no tcpcheck information inherited
from the proxy. We must take care of that when the "healthcheck" keyword is
parsed to avoid crashes.

This patch must be backported to 3.4.
2026-06-04 21:53:32 +02:00
Christopher Faulet
3daf4498f3 MINOR: check: Don't dump buffers state in check traces for external checks
In healthcheck trace messages, there is no reason to dump the in/out buffers
state for external checks. So let's skip this part in that case.
2026-06-04 21:50:12 +02:00
Christopher Faulet
4b9c8b24c5 BUG/MEDIUM: check: Ignore small-buffer option when starting an external check
When an external check is started for a server, there is no tcpcheck
ruleset. The pointer is NULL. It was an issue leading to a crash if the
small-buffer option was enabled on the healthchecks. However, it is
irrelevant for external checks because it is only usefull to tcp checks.

So, the option must be ignored if there is no tcpcheck ruleset.

This patch must be backported to 3.4.
2026-06-04 19:19:02 +02:00
Christopher Faulet
6a7b27a0a4 BUG/MEDIUM: check: Skip tcpcheck post-config for external checks
When an external check was configured on a backend, the tcpcheck post config
for backend's servers was still performed instead to be skipped. The led to
a NULL-deref on the tcpcheck ruleset pointer and so to a segfault.

It seems to be only an issue for the 3.4 and higher. However, for older
versions, the tcpcheck post-config is still performed for external checks
and it is not really clean. This can hide some bugs.

For the 3.4, a workaround consists in configuring the backend to use a
tcp-check before configuring the external check:

  backend be
    option tcp-check
    option external-check
    ...

This patch should fix the issue #3407. It could be good to backport it to
all supported versions.
2026-06-04 18:52:25 +02:00
53 changed files with 2003 additions and 999 deletions

View file

@ -32637,9 +32637,9 @@ https://github.com/haproxy/wiki/wiki/ACME:--native-haproxy
Current limitations:
- The feature is limited to the http-01, dns-01 or dns-persist-01 challenges
for now. http-01 is completely handled by HAProxy, but dns-01 and
dns-persist-01 needs either the dataplaneAPI or another 3rd party
tool to talk to a DNS provider API. dns-persist-01 only needs the TXT entry
to be set once, so it could be set manually without a tool.
dns-persist-01 needs either the dataplaneAPI, a lua script using event_hdl or
another 3rd party tool to talk to a DNS provider API. dns-persist-01 only
needs the TXT entry to be set once, so it could be set manually without a tool.
- It is possible to start without an existing certificate on the disk. To do
so, the certificate must configured in a crt-store.
When using the "acme" keyword in a crt-store, a temporary key pair will be
@ -32710,6 +32710,8 @@ challenge-ready <value>[,<value>]*
"acme challenge_ready <crt> domain <domain>" on the master CLI or
the stats socket. This allows an external DNS provisioning tool to
confirm that the TXT record has been set before HAProxy proceeds.
It is also possible to signal the "cli" readiness using the
ACME.challenge_ready() lua function.
dns - perform a DNS pre-check by resolving the TXT record for
"_acme-challenge.<domain>" using the configured "default" resolvers

View file

@ -526,12 +526,6 @@ In addition, some variables are related to the global runqueue:
unsigned int grq_total; /* total number of entries in the global run queue, atomic */
static unsigned int global_rqueue_ticks; /* insertion count in the grq, use rq_lock */
And others to the global wait queue:
struct eb_root timers; /* sorted timers tree, global, accessed under wq_lock */
__decl_aligned_rwlock(wq_lock); /* RW lock related to the wait queue */
struct eb_root timers; /* sorted timers tree, global, accessed under wq_lock */
2022-06-14 - progress on task affinity
==========

View file

@ -52,6 +52,16 @@ make sure to respect this ordering when adding new ones.
cast and freed. The const char* is here to leave more freedom to use consts
when making such options lists.
- void hap_register_hlua_state_init(int (*fct)())
This adds a call to function <fct> to the list of functions to be called each
time a new Lua state is created by hlua_init_state(). This allows source
files other than hlua.c to register objects and functions into the Lua API
without modifying hlua.c directly. The function <fct> must return an ERR_*
code. If the errmsg pointer is set on return, it is printed as an alert,
warning, or notice depending on the error code. If ERR_ABORT or ERR_FATAL is
returned, haproxy will exit with status 1.
- void hap_register_per_thread_alloc(int (*fct)())
This adds a call to function <fct> to the list of functions to be called when
@ -324,6 +334,18 @@ alphanumerically ordered:
is that it allows to register multiple <post> callbacks and to register them
elsewhere in the code.
- REGISTER_HLUA_STATE_INIT(fct)
Registers function <fct> to be called for each new Lua state created by
hlua_init_state(). This allows source files other than hlua.c to register
objects and functions into the Lua API without modifying hlua.c directly.
This is done by registering a call to hap_register_hlua_state_init(fct) at
stage STG_REGISTER. The function <fct> must be of type
(int (*fct)(lua_State *L, char **errmsg)) and must return an ERR_* code.
If the errmsg pointer is set on return, it is printed as an alert, warning,
or notice depending on the error code. If ERR_ABORT or ERR_FATAL is returned,
haproxy will exit with status 1.
- REGISTER_PER_THREAD_ALLOC(fct)
Registers a call to register_per_thread_alloc(fct) at stage STG_REGISTER.

View file

@ -893,9 +893,7 @@ Core class
**context**: init, task, action
This function returns a new object of a *httpclient* class. An *httpclient*
object must be used to process one and only one request. It must never be
reused to process several requests.
This function returns a new object of a *httpclient* class.
:returns: A :ref:`httpclient_class` object.
@ -1031,6 +1029,12 @@ Core class
Be careful when subscribing to this type since many events might be
generated.
**ACME** Family:
* **ACME_DEPLOY**: when a dns-01 challenge TXT record must be deployed
externally before HAProxy can proceed with the ACME challenge
* **ACME_NEWCERT**: when a new certificate is successfully installed
.. Note::
Use **SERVER** in **event_types** to subscribe to all server events types
at once. Note that this should only be used for testing purposes since a
@ -1048,7 +1052,8 @@ Core class
* **event** (*string*): the event type (one of the **event_types** specified
when subscribing)
* **event_data**: specific to each event family (For **SERVER** family,
a :ref:`server_event_class` object)
a :ref:`server_event_class` object; for **ACME** family,
a :ref:`acme_event_class` object)
* **sub**: class to manage the subscription from within the event
(a :ref:`event_sub_class` object)
* **when**: timestamp corresponding to the date when the event was generated.
@ -2580,10 +2585,7 @@ HTTPClient class
.. js:class:: HTTPClient
The httpclient class allows issue of outbound HTTP requests through a simple
API without the knowledge of HAProxy internals. Any instance must be used to
process one and only one request. It must never be reused to process several
requests.
API without the knowledge of HAProxy internals.
.. js:function:: HTTPClient.get(httpclient, request)
.. js:function:: HTTPClient.head(httpclient, request)
.. js:function:: HTTPClient.put(httpclient, request)
@ -2612,7 +2614,8 @@ HTTPClient class
haproxy address format.
:param integer request.timeout: Optional timeout parameter, set a
"timeout server" on the connections.
:returns: Lua table containing the response
:returns: Lua table containing the response. If an internal error occurs (e.g.
connection failure, timeout, etc.), the ``status`` field will be set to 0.
.. code-block:: lua
@ -4739,6 +4742,75 @@ CertCache class
CertCache.set{filename="certs/localhost9994.pem.rsa", crt=crt}
ACME class
==========
.. js:class:: ACME
This class provides access to the ACME (Automatic Certificate Management
Environment) subsystem. It allows Lua scripts to interact with ongoing ACME
certificate challenges.
.. js:function:: ACME.challenge_ready(crt, dns)
Marks the ACME challenge for domain <dns> in certificate <crt> as ready.
Returns the number of remaining challenges, or 0 if all challenges are ready
and validation has been triggered. Raises a Lua error if the certificate or
domain is not found.
:param string crt: The filename of the certificate.
:param string dns: The domain name for which the challenge is ready.
:returns: The number of remaining challenges (integer), or 0 when all
challenges are done and validation has been triggered.
.. _acme_event_class:
AcmeEvent class
===============
.. js:class:: AcmeEvent
This class is provided with every **ACME** event.
See :js:func:`core.event_sub()` for more info.
.. js:attribute:: AcmeEvent.crtname
Contains the certificate store name.
.. js:attribute:: AcmeEvent.domain
Contains the domain being challenged.
Only available for **ACME_DEPLOY** events.
.. js:attribute:: AcmeEvent.thumbprint
Contains the account key JWK thumbprint.
Only available for **ACME_DEPLOY** events.
.. js:attribute:: AcmeEvent.dns_record
Contains the DNS TXT record value that must be set at
``_acme-challenge.<domain>``.
Only available for **ACME_DEPLOY** events.
.. js:attribute:: AcmeEvent.provider
Contains the DNS provider name configured in the ACME section.
Only set if a provider was configured.
Only available for **ACME_DEPLOY** events.
.. js:attribute:: AcmeEvent.vars
Contains the ACME vars string configured in the ACME section.
Only set if vars were configured.
Only available for **ACME_DEPLOY** events.
External Lua libraries
======================

View file

@ -2469,7 +2469,8 @@ httpclient [--htx] <method> <URI>
name in the URL using the "default" resolvers section, which is populated
with the DNS servers of your /etc/resolv.conf by default. However it won't be
able to resolve an host from /etc/hosts if you don't use a local dns daemon
which can resolve those.
which can resolve those. If an internal error occurs (e.g. connection failure,
timeout, etc.), the status code will be set to 0.
The --htx option allow to use the haproxy internal htx representation using
the htx_dump() function, mainly used for debugging.

View file

@ -0,0 +1,162 @@
-- ACME dns-01 automation via event_hdl callbacks using the Gandi LiveDNS API v5
--
-- HAProxy Configuration:
--
-- global
-- expose-experimental-directives
-- tune.lua.bool-sample-conversion normal
-- lua-load examples/lua/acme-gandi-livedns.lua
-- log stderr local0
--
-- acme LE
-- directory https://acme-staging-v02.api.letsencrypt.org/directory
-- contact foobar@example.com
-- challenge dns-01
-- challenge-ready cli,dns
--
-- crt-store
-- load crt foobar.pem acme LE domains *.foobar.example.com
--
-- Start HAProxy with the GANDI_API_KEY variable:
--
-- GANDI_API_KEY=fer89wf498w4f98we74f98wwiw787f8we4f8 ./haproxy -W -f haproxy.cfg
--
-- Gandi Personal Access Token (https://account.gandi.net -> Security -> Personal Access Tokens).
-- Set the GANDI_API_KEY environment variable before starting HAProxy.
local GANDI_API_KEY = os.getenv("GANDI_API_KEY") or error("GANDI_API_KEY environment variable is not set")
-- Gandi LiveDNS API base URL.
local GANDI_API_URL = "https://api.gandi.net/v5/livedns"
-- ---------------------------------------------------------------------------
-- Gandi LiveDNS helpers
-- ---------------------------------------------------------------------------
-- Try to set the _acme-challenge TXT record for <domain> to <txt_value>.
-- Probes each possible parent zone (longest first) until Gandi accepts one.
-- Returns the zone and record name on success, or nil on failure.
local function dns_set_txt(domain, txt_value)
local labels = {}
for label in domain:gmatch("[^.]+") do
labels[#labels + 1] = label
end
for i = 1, #labels - 1 do
local zone = table.concat(labels, ".", i + 1)
local name = "_acme-challenge." .. table.concat(labels, ".", 1, i)
local url = string.format("%s/domains/%s/records/%s/TXT", GANDI_API_URL, zone, name)
local body = string.format('{"rrset_values":["%s"],"rrset_ttl":300}', txt_value)
core.log(core.debug, string.format("acme: trying PUT %s", url))
-- Remove any stale TXT record first so the new value propagates cleanly.
local hc_del = core.httpclient()
hc_del:delete({
url = url,
headers = { ["Authorization"] = { "Bearer " .. GANDI_API_KEY } },
})
local hc = core.httpclient()
local res = hc:put({
url = url,
headers = {
["Authorization"] = { "Bearer " .. GANDI_API_KEY },
["Content-Type"] = { "application/json" },
},
body = body,
})
if res and (res.status == 200 or res.status == 201) then
core.log(core.notice, string.format(
"acme: TXT record set: %s in zone %s", name, zone))
return zone, name
end
end
core.log(core.alert, string.format(
"acme: failed to set TXT record for _acme-challenge.%s: no valid zone found", domain))
return nil, nil
end
-- Deletes the TXT record identified by <zone> and <name>.
local function dns_del_txt(zone, name)
local url = string.format("%s/domains/%s/records/%s/TXT", GANDI_API_URL, zone, name)
core.log(core.notice, string.format("acme: DELETE %s", url))
local hc = core.httpclient()
local res = hc:delete({
url = url,
headers = {
["Authorization"] = { "Bearer " .. GANDI_API_KEY },
},
})
if not res or res.status ~= 204 then
local status = res and res.status or "nil"
core.log(core.alert, string.format(
"acme: Gandi DELETE failed for %s/%s (status=%s)", zone, name, status))
return false
end
core.log(core.notice, string.format(
"acme: TXT record deleted: %s in zone %s", name, zone))
return true
end
-- ---------------------------------------------------------------------------
-- Tasks
-- ---------------------------------------------------------------------------
-- Track deployed TXT records per cert path so they can be cleaned up.
-- deployed[crt][domain] = { zone = ..., name = ... }
local deployed = {}
-- Spawn a background task per ACME_DEPLOY event to set the TXT record and
-- signal challenge readiness. Using register_task keeps HTTP calls in a
-- plain task context.
core.event_sub({"ACME_DEPLOY"}, function(event, data, sub, when)
local crt = data.crtname
local domain = data.domain
local record = data.dns_record
core.register_task(function()
local zone, name = dns_set_txt(domain, record)
if not zone then
core.log(core.alert, string.format(
"acme: aborting challenge for crt=%s domain=%s", crt, domain))
return
end
-- Remember this record for cleanup on ACME_NEWCERT.
if not deployed[crt] then deployed[crt] = {} end
deployed[crt][domain] = { zone = zone, name = name }
-- Signal HAProxy that the dns-01 challenge for this domain is ready.
local ok, ret = pcall(ACME.challenge_ready, crt, domain)
if not ok then
core.log(core.alert, string.format(
"acme: challenge_ready error for crt=%s domain=%s: %s", crt, domain, ret))
elseif ret == 0 then
core.log(core.notice, string.format(
"acme: all challenges ready for crt=%s, validation starting", crt))
else
core.log(core.info, string.format(
"acme: crt=%s domain=%s ready, %d challenge(s) still pending",
crt, domain, ret))
end
end)
end)
-- ACME_NEWCERT: remove the TXT records that were set for this certificate.
core.event_sub({"ACME_NEWCERT"}, function(event, data, sub, when)
local crt = data.crtname
if not deployed[crt] then return end
core.register_task(function()
for _, rec in pairs(deployed[crt]) do
dns_del_txt(rec.zone, rec.name)
end
deployed[crt] = nil
end)
end)

View file

@ -135,6 +135,29 @@ struct acme_ctx {
#define ACME_VERB_ADVANCED 4
#define ACME_VERB_COMPLETE 5
/* event data for EVENT_HDL_SUB_ACME_DEPLOY:
* published when a dns-01 challenge TXT record must be deployed externally.
*/
struct event_hdl_cb_data_acme_deploy {
struct {
char *crtname; /* certificate store name (heap-allocated) */
char *domain; /* domain being challenged (heap-allocated) */
char *thumbprint; /* account key JWK thumbprint (heap-allocated) */
char *dns_record; /* DNS TXT record value to set (heap-allocated) */
char *provider; /* DNS provider name (heap-allocated, may be NULL) */
char *vars; /* ACME vars string (heap-allocated, may be NULL) */
} safe;
};
/* event data for EVENT_HDL_SUB_ACME_NEWCERT:
* published when a new certificate was successfully installed.
*/
struct event_hdl_cb_data_acme_newcert {
struct {
char *crtname; /* certificate store name (heap-allocated) */
} safe;
};
#endif /* ! HAVE_ACME */
#endif

View file

@ -5,8 +5,14 @@
#include <haproxy/ssl_ckch-t.h>
int ckch_conf_acme_init(void *value, char *buf, struct ckch_store *s, int cli, const char *filename, int linenum, char **err);
int acme_challenge_ready(const char *crt, const char *dns);
EVP_PKEY *acme_gen_tmp_pkey();
X509 *acme_gen_tmp_x509();
#if defined(USE_LUA)
#include <haproxy/hlua-t.h>
#include <haproxy/event_hdl-t.h>
void acme_hlua_event_push_args(struct hlua *hlua, struct event_hdl_sub_type event, void *data);
#endif /* USE_LUA */
#endif

View file

@ -101,6 +101,8 @@ void ha_warning(const char *fmt, ...)
* These functions are reserved to output diagnostics on MODE_DIAG.
* Use the underscore variants only if MODE_DIAG has already been checked.
*/
void ha_diag_notice(const char *fmt, ...)
__attribute__ ((format(printf, 1 ,2)));
void _ha_vdiag_warning(const char *fmt, va_list argp);
void _ha_diag_warning(const char *fmt, ...);
void ha_diag_warning(const char *fmt, ...)

View file

@ -290,6 +290,16 @@ struct event_hdl_sub {
#define EVENT_HDL_SUB_PAT_REF_COMMIT EVENT_HDL_SUB_TYPE(2,4)
#define EVENT_HDL_SUB_PAT_REF_CLEAR EVENT_HDL_SUB_TYPE(2,5)
/* ACME family, published in global subscription list.
* Provides event_hdl_cb_data_acme_deploy and event_hdl_cb_data_acme_newcert
* structs (defined in haproxy/acme-t.h).
*/
#define EVENT_HDL_SUB_ACME EVENT_HDL_SUB_FAMILY(3)
/* a new certificate was successfully installed */
#define EVENT_HDL_SUB_ACME_NEWCERT EVENT_HDL_SUB_TYPE(3,1)
/* dns-01 challenge must be deployed externally */
#define EVENT_HDL_SUB_ACME_DEPLOY EVENT_HDL_SUB_TYPE(3,2)
/* --------------------------------------- */
/* Please reflect changes above in event_hdl_sub_type_map defined

View file

@ -47,6 +47,7 @@
#define CLASS_HTTP "HTTP"
#define CLASS_HTTP_MSG "HTTPMessage"
#define CLASS_HTTPCLIENT "HTTPClient"
#define CLASS_HTTPCLIENT_REQ "HTTPClientRequest"
#define CLASS_MAP "Map"
#define CLASS_APPLET_TCP "AppletTCP"
#define CLASS_APPLET_HTTP "AppletHTTP"
@ -259,6 +260,11 @@ struct hlua_patref_iterator_context {
struct pat_ref_gen *gen; /* the generation we are iterating over */
};
struct hlua_state_init_fct {
struct list list;
int (*fct)(lua_State *L, char **errmsg);
};
#else /* USE_LUA */
/************************ For use when Lua is disabled ********************/

View file

@ -26,6 +26,17 @@
#ifdef USE_LUA
/* Lua uses longjmp to perform yield or throwing errors. This
* macro is used only for identifying the function that can
* not return because a longjmp is executed.
* __LJMP marks a prototype of hlua file that can use longjmp.
* WILL_LJMP() marks an lua function that will use longjmp.
* MAY_LJMP() marks an lua function that may use longjmp.
*/
#define __LJMP
#define WILL_LJMP(func) do { func; my_unreachable(); } while(0)
#define MAY_LJMP(func) func
/* The following macros are used to set flags. */
#define HLUA_SET_RUN(__hlua) do {(__hlua)->flags |= HLUA_RUN;} while(0)
#define HLUA_CLR_RUN(__hlua) do {(__hlua)->flags &= ~HLUA_RUN;} while(0)
@ -51,6 +62,11 @@
/* Lua HAProxy integration functions. */
void hlua_yield_asap(lua_State *L);
void hap_register_hlua_state_init(int (*fct)(lua_State *L, char **errmsg));
/* simplified way to register a lua_State init callback from any file */
#define REGISTER_HLUA_STATE_INIT(fct) \
INITCALL1(STG_REGISTER, hap_register_hlua_state_init, (fct))
const char *hlua_traceback(lua_State *L, const char* sep);
void hlua_ctx_destroy(struct hlua *lua);
void hlua_init();
@ -65,6 +81,15 @@ void hlua_pushref(lua_State *L, int ref);
void hlua_unref(lua_State *L, int ref);
struct hlua *hlua_gethlua(lua_State *L);
void hlua_yieldk(lua_State *L, int nresults, lua_KContext ctx, lua_KFunction k, int timeout, unsigned int flags);
int hlua_pusherror(lua_State *L, const char *fmt, ...);
__LJMP static inline void hlua_check_args(lua_State *L, int nb, char *fcn)
{
if (lua_gettop(L) == nb)
return;
WILL_LJMP(luaL_error(L, "'%s' needs %d arguments", fcn, nb));
}
#define check_args hlua_check_args
#else /* USE_LUA */

View file

@ -38,4 +38,9 @@ static inline int httpclient_started(struct httpclient *hc)
return !!(hc->flags & HTTPCLIENT_FS_STARTED);
}
#ifdef USE_LUA
#include <haproxy/hlua-t.h>
void hlua_httpclient_destroy_all(struct hlua *hlua);
#endif
#endif /* !_HAPROXY_HTTPCLIENT_H */

View file

@ -279,6 +279,8 @@ struct srv_per_thread {
struct ceb_root *idle_conns; /* Shareable idle connections */
struct ceb_root *safe_conns; /* Safe idle connections */
struct ceb_root *avail_conns; /* Connections in use, but with still new streams available */
struct server *srv; /* Back-pointer to the server */
struct eb32_node idle_node; /* When to next do cleanup in the idle connections */
#ifdef USE_QUIC
struct ist quic_retry_token;
#endif
@ -401,7 +403,6 @@ struct server {
* thread, and generally at the same time.
*/
THREAD_ALIGN();
struct eb32_node idle_node; /* When to next do cleanup in the idle connections */
unsigned int curr_idle_conns; /* Current number of orphan idling connections, both the idle and the safe lists */
unsigned int curr_idle_nb; /* Current number of connections in the idle list */
unsigned int curr_safe_nb; /* Current number of connections in the safe list */

View file

@ -41,9 +41,9 @@
#include <haproxy/tools.h>
__decl_thread(extern HA_SPINLOCK_T idle_conn_srv_lock);
extern struct idle_conns idle_conns[MAX_THREADS];
extern struct task *idle_conn_task;
extern struct task *idle_conn_task[MAX_THREADS];
extern struct eb_root idle_conn_srv[MAX_THREADS];
extern struct mt_list servers_list;
extern struct dict server_key_dict;

View file

@ -255,6 +255,7 @@ struct ssl_keylog {
#define SSL_SOCK_F_KTLS_RECV (1 << 3) /* kTLS receive is configure on that socket */
#define SSL_SOCK_F_CTRL_SEND (1 << 4) /* We want to send a kTLS control message for that socket */
#define SSL_SOCK_F_HAS_ALPN (1 << 5) /* An ALPN has been negotiated */
#define SSL_SOCK_F_KTLS_ULP (1 << 6) /* TLS ULP is enabled on that socket */
struct ssl_sock_ctx {
struct connection *conn;

View file

@ -53,7 +53,7 @@
/* use this to check a task state or to clean it up before queueing */
#define TASK_WOKEN_ANY (TASK_WOKEN_OTHER|TASK_WOKEN_INIT|TASK_WOKEN_TIMER| \
TASK_WOKEN_IO|TASK_WOKEN_SIGNAL|TASK_WOKEN_MSG| \
TASK_WOKEN_RES)
TASK_WOKEN_RES|TASK_WOKEN_WQ)
#define TASK_F_TASKLET 0x00008000 /* nature of this task: 0=task 1=tasklet */
#define TASK_F_USR1 0x00010000 /* preserved user flag 1, application-specific, def:0 */
@ -61,7 +61,8 @@
#define TASK_F_UEVT2 0x00040000 /* one-shot user event type 2, application specific, def:0 */
#define TASK_F_WANTS_TIME 0x00080000 /* task/tasklet wants th_ctx->sched_call_date to be set */
#define TASK_F_UEVT3 0x00100000 /* one-shot user event type 3, application specific, def:0 */
/* unused: 0x200000..0x80000000 */
#define TASK_WOKEN_WQ 0x00200000 /* The task has been waken up only to be put in the wait queue, because its expire changed */
/* unused: 0x400000..0x80000000 */
/* These flags are persistent across scheduler calls */
#define TASK_PERSISTENT (TASK_SELF_WAKING | TASK_KILLED | \
@ -82,7 +83,7 @@ static forceinline char *task_show_state(char *buf, size_t len, const char *deli
_(TASK_KILLED, _(TASK_HEAVY, _(TASK_WOKEN_INIT,
_(TASK_WOKEN_TIMER, _(TASK_WOKEN_IO, _(TASK_WOKEN_SIGNAL,
_(TASK_WOKEN_MSG, _(TASK_WOKEN_RES, _(TASK_WOKEN_OTHER,
_(TASK_F_TASKLET, _(TASK_F_USR1))))))))))))));
_(TASK_F_TASKLET, _(TASK_F_USR1, _(TASK_WOKEN_WQ)))))))))))))));
/* epilogue */
_(~0U);
return buf;

View file

@ -91,14 +91,13 @@ extern struct pool_head *pool_head_task;
extern struct pool_head *pool_head_tasklet;
extern struct pool_head *pool_head_notification;
__decl_thread(extern HA_RWLOCK_T wq_lock THREAD_ALIGNED());
void __tasklet_wakeup_on(struct tasklet *tl, int thr);
struct list *__tasklet_wakeup_after(struct list *head, struct tasklet *tl);
void task_kill(struct task *t);
void tasklet_kill(struct tasklet *t);
void __task_wakeup(struct task *t);
void __task_queue(struct task *task, struct eb_root *wq);
void __task_queue(struct task *task);
static inline void _task_queue(struct task *task, const struct ha_caller *caller);
unsigned int run_tasks_from_lists(unsigned int budgets[]);
@ -118,7 +117,7 @@ void process_runnable_tasks(void);
void wake_expired_tasks(void);
/* Checks the next timer for the current thread by looking into its own timer
* list and the global one. It may return TICK_ETERNITY if no timer is present.
* list. It may return TICK_ETERNITY if no timer is present.
* Note that the next timer might very well be slightly in the past.
*/
int next_timer_expiry(void);
@ -205,6 +204,77 @@ static inline uint64_t task_mono_time(void)
return th_ctx->sched_call_date;
}
#if !defined(HA_CAS_IS_8B) && !defined(HA_HAVE_CAS_DW)
__decl_thread(extern HA_SPINLOCK_T task_state_tid);
#endif
static inline int __task_set_state_and_tid(struct task *t, int expected_tid, int new_tid, unsigned int current, unsigned int wanted)
{
#if defined(HA_CAS_IS_8B) || defined(HA_HAVE_CAS_DW)
uint64_t expected_value;
uint64_t new_value;
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
expected_value = ((uint64_t)(current) << 32) | (uint32_t)expected_tid;
#else
expected_value = current | ((uint64_t)expected_tid << 32);
#endif
do {
int tid_seen;
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
tid_seen = (expected_value & 0xffffffff);
if (tid_seen != expected_tid)
return 0;
if ((expected_value >> 32) != current)
return 0;
new_value = ((uint64_t)wanted << 32) | (uint32_t)new_tid;
#else
tid_seen = (expected_value >> 32);
if (tid_seen != expected_tid)
return 0;
if ((expected_value & 0xffffffff) != current)
return 0;
new_value = wanted | ((uint64_t)new_tid << 32);
#endif
#if defined(HA_CAS_IS_8B)
} while (!HA_ATOMIC_CAS((uint64_t *)&t->state, &expected_value, new_value) && __ha_cpu_relax());
#elif defined(HA_HAVE_CAS_DW)
} while (!HA_ATOMIC_DWCAS((uint64_t *)&t->state, &expected_value, &new_value) && __ha_cpu_relax());
#endif
return 1;
#else /* !HA_CAS_IS_8B && !HA_HAVE_CAS_DW */
int old_state;
int ret = 0;
HA_SPIN_LOCK(OTHER_LOCK, &task_state_tid);
if (_HA_ATOMIC_LOAD(&t->tid) == expected_tid) {
old_state = _HA_ATOMIC_LOAD(&t->state);
if (old_state == current && HA_ATOMIC_CAS(&t->state, &old_state, wanted)) {
_HA_ATOMIC_STORE(&t->tid, new_tid);
ret = 1;
}
}
HA_SPIN_UNLOCK(OTHER_LOCK, &task_state_tid);
return ret;
#endif
}
static inline int __task_get_new_tid_field(int curtid)
{
if (curtid >= 0 || curtid < -1)
return curtid;
return -2 - tid;
}
static inline int __task_get_current_owner(int curtid)
{
if (curtid >= 0 || curtid == -1)
return curtid;
return ~(curtid + 1);
}
/* puts the task <t> in run queue with reason flags <f>, and returns <t> */
/* This will put the task in the local runqueue if the task is only runnable
* by the current thread, in the global runqueue otherwies. With DEBUG_TASK,
@ -220,7 +290,9 @@ static inline void _task_wakeup(struct task *t, unsigned int f, const struct ha_
state = _HA_ATOMIC_OR_FETCH(&t->state, f);
while (!(state & (TASK_RUNNING | TASK_QUEUED))) {
if (_HA_ATOMIC_CAS(&t->state, &state, state | TASK_QUEUED)) {
int expected_tid = _HA_ATOMIC_LOAD(&t->tid);
if (__task_set_state_and_tid(t, expected_tid, __task_get_new_tid_field(expected_tid), state, state | TASK_QUEUED)) {
if (likely(caller)) {
caller = HA_ATOMIC_XCHG(&t->caller, caller);
BUG_ON((ulong)caller & 1);
@ -231,6 +303,7 @@ static inline void _task_wakeup(struct task *t, unsigned int f, const struct ha_
__task_wakeup(t);
break;
}
state = _HA_ATOMIC_LOAD(&t->state);
}
}
@ -245,14 +318,28 @@ static inline void task_drop_running(struct task *t, unsigned int f)
{
unsigned int state, new_state;
state = _HA_ATOMIC_LOAD(&t->state);
while (1) {
new_state = state | f;
int cur_tid, new_tid;
state = _HA_ATOMIC_LOAD(&t->state);
new_state = (state | f) &~ TASK_RUNNING;
cur_tid = t->tid;
if ((new_state & TASK_WOKEN_WQ) && __task_get_current_owner(cur_tid) == tid) {
_task_queue(t, NULL);
new_state &= ~TASK_WOKEN_WQ;
}
if (new_state & TASK_WOKEN_ANY)
new_state |= TASK_QUEUED;
if (_HA_ATOMIC_CAS(&t->state, &state, new_state & ~TASK_RUNNING))
if ((new_state & TASK_QUEUED) || cur_tid >= 0 || task_in_wq(t) ||
__task_get_current_owner(cur_tid) != tid)
new_tid = cur_tid;
else
new_tid = -1;
if (__task_set_state_and_tid(t, cur_tid, new_tid, state, new_state))
break;
__ha_cpu_relax();
}
@ -273,31 +360,21 @@ static inline struct task *__task_unlink_wq(struct task *t)
return t;
}
/* remove a task from its wait queue. It may either be the local wait queue if
* the task is bound to a single thread or the global queue. If the task uses a
* shared wait queue, the global wait queue lock is used.
/* remove a task from its wait queue, which during normal operations will be
* the current thread's wait queue.
*/
static inline struct task *task_unlink_wq(struct task *t)
{
unsigned long locked;
if (likely(task_in_wq(t))) {
locked = t->tid < 0;
BUG_ON(t->tid >= 0 && t->tid != tid && !(global.mode & MODE_STOPPING));
if (locked)
HA_RWLOCK_WRLOCK(TASK_WQ_LOCK, &wq_lock);
BUG_ON(__task_get_current_owner(t->tid) != tid && !(global.mode & MODE_STOPPING));
__task_unlink_wq(t);
if (locked)
HA_RWLOCK_WRUNLOCK(TASK_WQ_LOCK, &wq_lock);
}
return t;
}
/* Place <task> into the wait queue, where it may already be. If the expiration
* timer is infinite, do nothing and rely on wake_expired_task to clean up.
* If the task uses a shared wait queue, it's queued into the global wait queue,
* protected by the global wq_lock, otherwise by it necessarily belongs to the
* current thread'sand is queued without locking.
*/
#define task_queue(t) \
_task_queue(t, MK_CALLER(WAKEUP_TYPE_TASK_QUEUE, 0, 0))
@ -316,34 +393,17 @@ static inline void _task_queue(struct task *task, const struct ha_caller *caller
if (!tick_isset(task->expire))
return;
#ifdef USE_THREAD
if (task->tid < 0) {
HA_RWLOCK_WRLOCK(TASK_WQ_LOCK, &wq_lock);
if (!task_in_wq(task) || tick_is_lt(task->expire, task->wq.key)) {
if (likely(caller)) {
caller = HA_ATOMIC_XCHG(&task->caller, caller);
BUG_ON((ulong)caller & 1);
BUG_ON(task->tid >= 0 && task->tid != tid);
if (!task_in_wq(task) || tick_is_lt(task->expire, task->wq.key)) {
if (likely(caller)) {
caller = HA_ATOMIC_XCHG(&task->caller, caller);
BUG_ON((ulong)caller & 1);
#ifdef DEBUG_TASK
HA_ATOMIC_STORE(&task->debug.prev_caller, caller);
HA_ATOMIC_STORE(&task->debug.prev_caller, caller);
#endif
}
__task_queue(task, &tg_ctx->timers);
}
HA_RWLOCK_WRUNLOCK(TASK_WQ_LOCK, &wq_lock);
} else
#endif
{
BUG_ON(task->tid != tid);
if (!task_in_wq(task) || tick_is_lt(task->expire, task->wq.key)) {
if (likely(caller)) {
caller = HA_ATOMIC_XCHG(&task->caller, caller);
BUG_ON((ulong)caller & 1);
#ifdef DEBUG_TASK
HA_ATOMIC_STORE(&task->debug.prev_caller, caller);
#endif
}
__task_queue(task, &th_ctx->timers);
}
__task_queue(task);
}
}
@ -363,6 +423,11 @@ static inline void task_set_thread(struct task *t, int thr)
/* no shared queue without threads */
thr = 0;
#endif
/*
* Nothing to do, the task is only temporarily owned
*/
if (thr == -1 && t->tid == -2 - tid)
return;
if (unlikely(task_in_wq(t))) {
task_unlink_wq(t);
t->tid = thr;
@ -440,24 +505,30 @@ static inline void _tasklet_wakeup_on(struct tasklet *tl, int thr, uint f, const
static inline void _task_instant_wakeup(struct task *t, unsigned int f, const struct ha_caller *caller)
{
int thr = t->tid;
int newtid;
unsigned int state;
if (thr < 0)
thr = tid;
/* first, let's update the task's state with the wakeup condition */
state = _HA_ATOMIC_OR_FETCH(&t->state, f);
/* next we need to make sure the task was not/will not be added to the
* run queue because the tasklet list's mt_list uses the same storage
* as the task's run_queue.
*/
do {
state = _HA_ATOMIC_LOAD(&t->state);
thr = t->tid;
if (thr == -1)
newtid = -2 - tid;
else
newtid = thr;
/* do nothing if someone else already added it */
if (state & (TASK_QUEUED|TASK_RUNNING))
return;
} while (!_HA_ATOMIC_CAS(&t->state, &state, state | TASK_QUEUED));
} while (!__task_set_state_and_tid(t, thr, newtid, state, state | TASK_QUEUED));
if (newtid < 0)
thr = __task_get_current_owner(newtid);
BUG_ON_HOT(task_in_rq(t));
/* at this point we're the first ones to add this task to the list */
@ -707,11 +778,11 @@ static inline void tasklet_set_tid(struct tasklet *tl, int tid)
static inline void _task_schedule(struct task *task, int when, const struct ha_caller *caller)
{
int did_lock = 0;
/* TODO: mthread, check if there is no task with this test */
if (task_in_rq(task))
return;
#ifdef USE_THREAD
if (task->tid < 0) {
/*
* If the task is already running, then just wake it up, just
@ -729,44 +800,26 @@ static inline void _task_schedule(struct task *task, int when, const struct ha_c
task_wakeup(task, TASK_WOKEN_OTHER);
return;
}
/* FIXME: is it really needed to lock the WQ during the check ? */
HA_RWLOCK_WRLOCK(TASK_WQ_LOCK, &wq_lock);
if (task_in_wq(task))
when = tick_first(when, task->expire);
task->expire = when;
if (!task_in_wq(task) || tick_is_lt(task->expire, task->wq.key)) {
if (likely(caller)) {
caller = HA_ATOMIC_XCHG(&task->caller, caller);
BUG_ON((ulong)caller & 1);
#ifdef DEBUG_TASK
HA_ATOMIC_STORE(&task->debug.prev_caller, caller);
#endif
}
__task_queue(task, &tg_ctx->timers);
}
task_drop_running(task, 0);
HA_RWLOCK_WRUNLOCK(TASK_WQ_LOCK, &wq_lock);
did_lock = 1;
} else
#endif
{
BUG_ON(task->tid != tid);
if (task_in_wq(task))
when = tick_first(when, task->expire);
task->expire = when;
if (!task_in_wq(task) || tick_is_lt(task->expire, task->wq.key)) {
if (likely(caller)) {
caller = HA_ATOMIC_XCHG(&task->caller, caller);
BUG_ON((ulong)caller & 1);
if (task_in_wq(task))
when = tick_first(when, task->expire);
task->expire = when;
if (!task_in_wq(task) || tick_is_lt(task->expire, task->wq.key)) {
if (likely(caller)) {
caller = HA_ATOMIC_XCHG(&task->caller, caller);
BUG_ON((ulong)caller & 1);
#ifdef DEBUG_TASK
HA_ATOMIC_STORE(&task->debug.prev_caller, caller);
HA_ATOMIC_STORE(&task->debug.prev_caller, caller);
#endif
}
__task_queue(task, &th_ctx->timers);
}
__task_queue(task);
}
if (did_lock)
task_drop_running(task, 0);
}
/* returns the string corresponding to a task type as found in the task caller

View file

@ -178,7 +178,6 @@ struct ha_rwlock {
*/
enum lock_label {
TASK_RQ_LOCK,
TASK_WQ_LOCK,
LISTENER_LOCK,
PROXY_LOCK,
SERVER_LOCK,

View file

@ -135,8 +135,6 @@ struct tgroup_ctx {
ulong threads_idle; /* mask of threads idling in the poller */
ulong stopping_threads; /* mask of threads currently stopping */
struct eb_root timers; /* wait queue (sorted timers tree, global, accessed under wq_lock) */
uint niced_tasks; /* number of niced tasks in this group's run queues */
uint committed_extra_streams; /* sum of extra front streams committed by muxes in this group */

View file

@ -0,0 +1,77 @@
varnishtest "Health-checks: some external check tests"
feature ignore_unknown_macro
#REGTEST_TYPE=slow
server s1 {
rxreq
expect req.method == GET
expect req.url == /health
expect req.proto == HTTP/1.1
txresp
} -start
syslog S1 -level notice {
recv
expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be[0-9]/srv succeeded, reason: External check passed, code: 0"
recv
expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be[0-9]/srv succeeded, reason: External check passed, code: 0"
} -start
syslog S2 -level notice {
recv
expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be[0-9]/srv succeeded.*code: 200"
} -start
haproxy h1 -conf {
global
.if feature(THREAD)
thread-groups 1
.endif
external-check
insecure-fork-wanted
healthcheck http-health
type httpchk
http-check send meth GET uri /health ver HTTP/1.1
defaults
mode http
timeout client "${HAPROXY_TEST_TIMEOUT-5s}"
timeout server "${HAPROXY_TEST_TIMEOUT-5s}"
timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
option log-health-checks
backend be1
log ${S1_addr}:${S1_port} len 2048 local0
option external-check
external-check command /bin/true
server srv ${h1_li1_addr}:${h1_li1_port} check inter 100ms rise 1 fall 1
defaults
mode http
timeout client "${HAPROXY_TEST_TIMEOUT-5s}"
timeout server "${HAPROXY_TEST_TIMEOUT-5s}"
timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
option external-check
external-check command /bin/true
option log-health-checks
backend be2
log ${S1_addr}:${S1_port} len 2048 local0
server srv ${h1_li1_addr}:${h1_li1_port} check inter 100ms rise 1 fall 1
backend be3
log ${S2_addr}:${S2_port} len 2048 local0
option external-check
external-check command /bin/true
server srv ${s1_addr}:${s1_port} check inter 100ms rise 1 fall 1 healthcheck http-health
listen li1
mode http
bind "fd@${li1}"
http-request return status 200
} -start
syslog S1 -wait
syslog S2 -wait

View file

@ -144,7 +144,7 @@ syslog S2 -level notice {
recv
expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be2/srv[0-9]+ succeeded"
recv
expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be2/srv[0-9] succeeded"
expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be2/srv[0-9]+ succeeded"
recv
expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be2/srv[0-9]+ succeeded"
recv

View file

@ -18,6 +18,8 @@ core.register_service("fakeserv", "http", function(applet)
end)
local function cron()
local httpclient = core.httpclient()
-- wait for until the correct port is set through the c0 request..
while vtc_port == 0 do
core.msleep(1)
@ -30,19 +32,16 @@ local function cron()
body = body .. i .. ' ABCDEFGHIJKLMNOPQRSTUVWXYZ\n'
end
core.Info("First httpclient request")
local httpclient = core.httpclient()
local response = httpclient:post{url="http://127.0.0.1:" .. vtc_port, body=body}
core.Info("Received: " .. response.body)
body = response.body
core.Info("Second httpclient request")
local httpclient2 = core.httpclient()
local response2 = httpclient2:post{url="http://127.0.0.1:" .. vtc_port2, body=body}
local response2 = httpclient:post{url="http://127.0.0.1:" .. vtc_port2, body=body}
core.Info("Third httpclient request")
local httpclient3 = core.httpclient()
local response3 = httpclient3:get{url="http://127.0.0.1", dst = vtc_port3, headers={ [ "Host" ] = { "foobar.haproxy.local" } }}
local response3 = httpclient:get{url="http://127.0.0.1", dst = vtc_port3, headers={ [ "Host" ] = { "foobar.haproxy.local" } }}
end

View file

@ -19,6 +19,7 @@
#include <haproxy/acme-t.h>
#include <haproxy/acme_resolvers.h>
#include <haproxy/event_hdl.h>
#include <haproxy/base64.h>
#include <haproxy/intops.h>
#include <haproxy/cfgparse.h>
@ -37,6 +38,12 @@
#include <haproxy/ssl_utils.h>
#include <haproxy/tools.h>
#include <haproxy/trace.h>
#ifdef USE_LUA
#include <lua.h>
#include <lauxlib.h>
#include <haproxy/hlua.h>
#include <haproxy/hlua_fcn.h>
#endif
#define TRACE_SOURCE &trace_acme
@ -1450,6 +1457,27 @@ error:
return ret;
}
/* mfree callback for EVENT_HDL_SUB_ACME_DEPLOY: frees heap-allocated fields */
static void acme_deploy_event_mfree(const void *data)
{
struct event_hdl_cb_data_acme_deploy *e = (struct event_hdl_cb_data_acme_deploy *)data;
ha_free(&e->safe.crtname);
ha_free(&e->safe.domain);
ha_free(&e->safe.thumbprint);
ha_free(&e->safe.dns_record);
ha_free(&e->safe.provider);
ha_free(&e->safe.vars);
}
/* mfree callback for EVENT_HDL_SUB_ACME_NEWCERT: frees the heap-allocated path */
static void acme_newcert_event_mfree(const void *data)
{
const struct event_hdl_cb_data_acme_newcert *e = data;
free(e->safe.crtname);
}
/*
* Update every certificate instances for the new store
*
@ -1503,6 +1531,15 @@ int acme_update_certificate(struct task *task, struct acme_ctx *ctx, char **errm
if (dpapi)
sink_write(dpapi, LOG_HEADER_NONE, 0, line, 3);
{
struct event_hdl_cb_data_acme_newcert cb_data = { };
cb_data.safe.crtname = strdup(ctx->store->path);
if (cb_data.safe.crtname)
event_hdl_publish(NULL, EVENT_HDL_SUB_ACME_NEWCERT,
EVENT_HDL_CB_DATA_DM(&cb_data, acme_newcert_event_mfree));
}
ctx->store = NULL;
ret = 0;
@ -2028,6 +2065,7 @@ int acme_res_auth(struct task *task, struct acme_ctx *ctx, struct acme_auth *aut
/* if auth is already valid we need to skip solving challenges */
if (strncasecmp("valid", trash.area, trash.data) == 0) {
auth->validated = 1;
auth->ready = ctx->cfg->cond_ready; /* no challenge needed, satisfy all readiness conditions */
goto out;
}
@ -2181,6 +2219,22 @@ int acme_res_auth(struct task *task, struct acme_ctx *ctx, struct acme_auth *aut
dpapi = sink_find("dpapi");
if (dpapi)
sink_write(dpapi, LOG_HEADER_NONE, 0, line, nmsg);
{
struct event_hdl_cb_data_acme_deploy cb_data = { };
cb_data.safe.crtname = strdup(ctx->store->path);
cb_data.safe.domain = isttest(auth->dns) ? strndup(auth->dns.ptr, auth->dns.len) : NULL;
cb_data.safe.thumbprint = ctx->cfg->account.thumbprint ? strdup(ctx->cfg->account.thumbprint) : NULL;
cb_data.safe.dns_record = strndup(dns_record->area, dns_record->data);
cb_data.safe.provider = ctx->cfg->provider ? strdup(ctx->cfg->provider) : NULL;
cb_data.safe.vars = ctx->cfg->vars ? strdup(ctx->cfg->vars) : NULL;
if (cb_data.safe.crtname && cb_data.safe.dns_record)
event_hdl_publish(NULL, EVENT_HDL_SUB_ACME_DEPLOY,
EVENT_HDL_CB_DATA_DM(&cb_data, acme_deploy_event_mfree));
else
acme_deploy_event_mfree(&cb_data);
}
}
else if (strcasecmp(ctx->cfg->challenge, "http-01") == 0) {
/* only useful for http-01 */
@ -3505,16 +3559,64 @@ err:
return cli_dynerr(appctx, errmsg);
}
/*
* Change the readiness of an ACME challenge per couple <crt>+<dns>
* Return:
* - -2 if the crt was not found
* - -1 if an non-ready couple crt+dns wasn't not found
* - 0 if the challenges are ready for the certificate
* - > 0 with the number of remaining challenge to enable
*/
int acme_challenge_ready(const char *crt, const char *dns)
{
struct ebmb_node *node = NULL;
struct acme_ctx *ctx = NULL;
struct acme_auth *auth = NULL;
int found = 0;
int remain = 0;
HA_RWLOCK_WRLOCK(OTHER_LOCK, &acme_lock);
node = ebst_lookup(&acme_tasks, crt);
if (!node) {
HA_RWLOCK_WRUNLOCK(OTHER_LOCK, &acme_lock);
return -2;
}
ctx = ebmb_entry(node, struct acme_ctx, node);
if (ctx->cfg->cond_ready & ACME_RDY_CLI)
auth = ctx->auths;
while (auth) {
if (strncmp(dns, auth->dns.ptr, auth->dns.len) == 0) {
if ((auth->ready & ACME_RDY_CLI) == 0) {
auth->ready |= ACME_RDY_CLI;
found++;
}
}
if ((auth->ready & ACME_RDY_CLI) == 0)
remain++;
auth = auth->next;
}
/* no remaining challenge to ready: wake the task only if it is
* currently suspended in ACME_CLI_WAIT, not in the middle of an
* HTTP exchange.
*/
if (!remain && ctx->state == ACME_CLI_WAIT)
task_wakeup(ctx->task, TASK_WOKEN_MSG);
HA_RWLOCK_WRUNLOCK(OTHER_LOCK, &acme_lock);
if (!found)
return -1;
return remain;
}
static int cli_acme_chall_ready_parse(char **args, char *payload, struct appctx *appctx, void *private)
{
char *msg = NULL;
const char *crt;
const char *dns;
struct acme_ctx *ctx = NULL;
struct acme_auth *auth = NULL;
int found = 0;
int remain = 0;
struct ebmb_node *node = NULL;
int ret;
if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
return 1;
@ -3527,39 +3629,13 @@ static int cli_acme_chall_ready_parse(char **args, char *payload, struct appctx
crt = args[2];
dns = args[4];
HA_RWLOCK_WRLOCK(OTHER_LOCK, &acme_lock);
node = ebst_lookup(&acme_tasks, crt);
if (node) {
ctx = ebmb_entry(node, struct acme_ctx, node);
if (ctx->cfg->cond_ready & ACME_RDY_CLI)
auth = ctx->auths;
while (auth) {
if (strncmp(dns, auth->dns.ptr, auth->dns.len) == 0) {
if (!(auth->ready & ACME_RDY_CLI)) {
auth->ready |= ACME_RDY_CLI;
found++;
} else {
memprintf(&msg, "ACME challenge for crt \"%s\" and dns \"%s\" was already READY !\n", crt, dns);
}
}
if ((auth->ready & ACME_RDY_CLI) == 0)
remain++;
auth = auth->next;
}
}
HA_RWLOCK_WRUNLOCK(OTHER_LOCK, &acme_lock);
if (!found) {
if (!msg)
memprintf(&msg, "Couldn't find an ACME task using crt \"%s\" and dns \"%s\" to set as ready!\n", crt, dns);
goto err;
} else {
if (!remain) {
if (ctx)
task_wakeup(ctx->task, TASK_WOKEN_MSG);
return cli_dynmsg(appctx, LOG_INFO, memprintf(&msg, "%d '%s' challenge(s) ready! All challenges ready, starting challenges validation!", found, dns));
} else {
return cli_dynmsg(appctx, LOG_INFO, memprintf(&msg, "%d '%s' challenge(s) ready! Remaining challenges to deploy: %d", found, dns, remain));
}
ret = acme_challenge_ready(crt, dns);
if (ret < 0) {
return cli_dynerr(appctx, memprintf(&msg, "Couldn't find an ACME task using crt \"%s\" and dns \"%s\" to set as ready!\n", crt, dns));
} else if (ret == 0) {
return cli_dynmsg(appctx, LOG_INFO, memprintf(&msg, "'%s' challenges ready! All challenges ready, starting challenges validation!", crt));
} else if (ret > 0) {
return cli_dynmsg(appctx, LOG_INFO, memprintf(&msg, "'%s' challenge(s) ready! Remaining challenges to deploy: %d", crt, ret));
}
err:
@ -3669,6 +3745,127 @@ static void __acme_init(void)
}
INITCALL0(STG_REGISTER, __acme_init);
#ifdef USE_LUA
#define CLASS_ACME_EVENT "AcmeEvent"
static int class_acme_event_ref;
/* Push a new AcmeEvent object for an ACME_DEPLOY event onto the Lua stack.
* The object exposes crtname, domain, thumbprint, dns_record fields, and
* optionally provider and vars if they were configured.
*/
static void hlua_fcn_new_acme_event_deploy(lua_State *L, const struct event_hdl_cb_data_acme_deploy *e)
{
lua_newtable(L);
lua_rawgeti(L, LUA_REGISTRYINDEX, class_acme_event_ref);
lua_setmetatable(L, -2);
lua_pushstring(L, e->safe.crtname ? e->safe.crtname : "");
lua_setfield(L, -2, "crtname");
lua_pushstring(L, e->safe.domain ? e->safe.domain : "");
lua_setfield(L, -2, "domain");
lua_pushstring(L, e->safe.thumbprint ? e->safe.thumbprint : "");
lua_setfield(L, -2, "thumbprint");
lua_pushstring(L, e->safe.dns_record ? e->safe.dns_record : "");
lua_setfield(L, -2, "dns_record");
if (e->safe.provider) {
lua_pushstring(L, e->safe.provider);
lua_setfield(L, -2, "provider");
}
if (e->safe.vars) {
lua_pushstring(L, e->safe.vars);
lua_setfield(L, -2, "vars");
}
}
/* Push a new AcmeEvent object for an ACME_NEWCERT event onto the Lua stack.
* The object exposes a <crtname> field with the certificate store name.
*/
static void hlua_fcn_new_acme_event_newcert(lua_State *L, const char *crtname)
{
lua_newtable(L);
lua_rawgeti(L, LUA_REGISTRYINDEX, class_acme_event_ref);
lua_setmetatable(L, -2);
lua_pushstring(L, crtname);
lua_setfield(L, -2, "crtname");
}
/*
* ACME.challenge_ready(crt, dns)
*
* Marks the ACME challenge for domain <dns> in certificate <crt> as ready.
* Returns the number of remaining challenges, or 0 if all challenges are
* ready and validation has been triggered.
* Raises a Lua error if the certificate or domain is not found.
*/
__LJMP static int hlua_acme_challenge_ready(lua_State *L)
{
const char *crt;
const char *dns;
int ret;
if (lua_gettop(L) != 2)
WILL_LJMP(luaL_error(L, "'ACME.challenge_ready' needs 2 arguments."));
crt = MAY_LJMP(luaL_checkstring(L, 1));
dns = MAY_LJMP(luaL_checkstring(L, 2));
ret = acme_challenge_ready(crt, dns);
if (ret == -2)
WILL_LJMP(luaL_error(L, "ACME.challenge_ready: certificate '%s' not found", crt));
if (ret == -1)
WILL_LJMP(luaL_error(L, "ACME.challenge_ready: domain '%s' not found for certificate '%s'", dns, crt));
lua_pushinteger(L, ret);
return 1;
}
static int acme_hlua_init_state(lua_State *L, char **errmsg)
{
/* Register AcmeEvent class */
lua_newtable(L);
class_acme_event_ref = hlua_register_metatable(L, CLASS_ACME_EVENT);
lua_newtable(L);
hlua_class_function(L, "challenge_ready", hlua_acme_challenge_ready);
lua_setglobal(L, "ACME");
return ERR_NONE;
}
REGISTER_HLUA_STATE_INIT(acme_hlua_init_state);
/* Push ACME event data as a Lua table for core.event_sub() handlers.
* Called from hlua_event_hdl_cb_push_args() when the event family is ACME.
*/
void acme_hlua_event_push_args(struct hlua *hlua, struct event_hdl_sub_type event, void *data)
{
if (!lua_checkstack(hlua->T, 3))
WILL_LJMP(luaL_error(hlua->T, "Lua out of memory error."));
if (event_hdl_sub_type_equal(EVENT_HDL_SUB_ACME_DEPLOY, event)) {
struct event_hdl_cb_data_acme_deploy *e_acme = data;
hlua->nargs += 1;
MAY_LJMP(hlua_fcn_new_acme_event_deploy(hlua->T, e_acme));
}
else if (event_hdl_sub_type_equal(EVENT_HDL_SUB_ACME_NEWCERT, event)) {
struct event_hdl_cb_data_acme_newcert *e_acme = data;
hlua->nargs += 1;
MAY_LJMP(hlua_fcn_new_acme_event_newcert(hlua->T, e_acme->safe.crtname));
}
}
#endif /* USE_LUA */
#endif /* ! HAVE_ACME */
/*

View file

@ -80,7 +80,10 @@ static const char *const memprof_methods[MEMPROF_METH_METHODS] = {
struct memprof_stats memprof_stats[MEMPROF_HASH_BUCKETS + 1] = { };
/* used to detect recursive calls */
static THREAD_LOCAL int in_memprof = 0;
#define MEMPROF_IN_INIT (1U << 0)
#define MEMPROF_IN_HANDLER (1U << 1)
static THREAD_LOCAL uint in_memprof = 0; // arithmetic OR of MEMPROF_IN_*
/* These ones are used by glibc and will be called early. They are in charge of
* initializing the handlers with the original functions.
@ -137,7 +140,7 @@ static __attribute__((noreturn)) void memprof_die(const char *msg)
*/
static void memprof_init()
{
in_memprof++;
in_memprof |= MEMPROF_IN_INIT;
memprof_malloc_handler = get_sym_next_addr("malloc");
if (!memprof_malloc_handler)
memprof_die("FATAL: malloc() function not found.\n");
@ -168,7 +171,7 @@ static void memprof_init()
memprof_aligned_alloc_handler = get_sym_next_addr("aligned_alloc");
memprof_posix_memalign_handler = get_sym_next_addr("posix_memalign");
in_memprof--;
in_memprof &= ~MEMPROF_IN_INIT;
}
/* the initial handlers will initialize all regular handlers and will call the
@ -177,7 +180,7 @@ static void memprof_init()
*/
static void *memprof_malloc_initial_handler(size_t size)
{
if (in_memprof) {
if (in_memprof & MEMPROF_IN_INIT) {
/* it's likely that dlsym() needs malloc(), let's fail */
return NULL;
}
@ -188,7 +191,7 @@ static void *memprof_malloc_initial_handler(size_t size)
static void *memprof_calloc_initial_handler(size_t nmemb, size_t size)
{
if (in_memprof) {
if (in_memprof & MEMPROF_IN_INIT) {
/* it's likely that dlsym() needs calloc(), let's fail */
return NULL;
}
@ -198,7 +201,7 @@ static void *memprof_calloc_initial_handler(size_t nmemb, size_t size)
static void *memprof_realloc_initial_handler(void *ptr, size_t size)
{
if (in_memprof) {
if (in_memprof & MEMPROF_IN_INIT) {
/* it's likely that dlsym() needs realloc(), let's fail */
return NULL;
}
@ -209,7 +212,7 @@ static void *memprof_realloc_initial_handler(void *ptr, size_t size)
static char *memprof_strdup_initial_handler(const char *s)
{
if (in_memprof) {
if (in_memprof & MEMPROF_IN_INIT) {
/* probably that dlsym() needs strdup(), let's fail */
return NULL;
}
@ -228,7 +231,7 @@ static void memprof_free_initial_handler(void *ptr)
static char *memprof_strndup_initial_handler(const char *s, size_t n)
{
if (in_memprof) {
if (in_memprof & MEMPROF_IN_INIT) {
/* probably that dlsym() needs strndup(), let's fail */
return NULL;
}
@ -239,7 +242,7 @@ static char *memprof_strndup_initial_handler(const char *s, size_t n)
static void *memprof_valloc_initial_handler(size_t sz)
{
if (in_memprof) {
if (in_memprof & MEMPROF_IN_INIT) {
/* probably that dlsym() needs valloc(), let's fail */
return NULL;
}
@ -250,7 +253,7 @@ static void *memprof_valloc_initial_handler(size_t sz)
static void *memprof_pvalloc_initial_handler(size_t sz)
{
if (in_memprof) {
if (in_memprof & MEMPROF_IN_INIT) {
/* probably that dlsym() needs pvalloc(), let's fail */
return NULL;
}
@ -261,7 +264,7 @@ static void *memprof_pvalloc_initial_handler(size_t sz)
static void *memprof_memalign_initial_handler(size_t al, size_t sz)
{
if (in_memprof) {
if (in_memprof & MEMPROF_IN_INIT) {
/* probably that dlsym() needs memalign(), let's fail */
return NULL;
}
@ -272,7 +275,7 @@ static void *memprof_memalign_initial_handler(size_t al, size_t sz)
static void *memprof_aligned_alloc_initial_handler(size_t al, size_t sz)
{
if (in_memprof) {
if (in_memprof & MEMPROF_IN_INIT) {
/* probably that dlsym() needs aligned_alloc(), let's fail */
return NULL;
}
@ -283,7 +286,7 @@ static void *memprof_aligned_alloc_initial_handler(size_t al, size_t sz)
static int memprof_posix_memalign_initial_handler(void **ptr, size_t al, size_t sz)
{
if (in_memprof) {
if (in_memprof & MEMPROF_IN_INIT) {
/* probably that dlsym() needs posix_memalign(), let's fail */
return ENOMEM;
}
@ -344,11 +347,13 @@ void *malloc(size_t size)
struct memprof_stats *bin;
void *ret;
if (likely(!(profiling & HA_PROF_MEMORY)))
if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
return memprof_malloc_handler(size);
in_memprof |= MEMPROF_IN_HANDLER;
ret = memprof_malloc_handler(size);
size = malloc_usable_size(ret) + sizeof(void *);
in_memprof &= ~MEMPROF_IN_HANDLER;
bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_MALLOC);
if (unlikely(th_ctx->lock_level & 0x7F))
@ -371,11 +376,13 @@ void *calloc(size_t nmemb, size_t size)
struct memprof_stats *bin;
void *ret;
if (likely(!(profiling & HA_PROF_MEMORY)))
if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
return memprof_calloc_handler(nmemb, size);
in_memprof |= MEMPROF_IN_HANDLER;
ret = memprof_calloc_handler(nmemb, size);
size = malloc_usable_size(ret) + sizeof(void *);
in_memprof &= ~MEMPROF_IN_HANDLER;
bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_CALLOC);
if (unlikely(th_ctx->lock_level & 0x7F))
@ -401,12 +408,14 @@ void *realloc(void *ptr, size_t size)
size_t size_before;
void *ret;
if (likely(!(profiling & HA_PROF_MEMORY)))
if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
return memprof_realloc_handler(ptr, size);
in_memprof |= MEMPROF_IN_HANDLER;
size_before = malloc_usable_size(ptr);
ret = memprof_realloc_handler(ptr, size);
size = malloc_usable_size(ret);
in_memprof &= ~MEMPROF_IN_HANDLER;
/* only count the extra link for new allocations */
if (!ptr)
@ -439,11 +448,13 @@ char *strdup(const char *s)
size_t size;
char *ret;
if (likely(!(profiling & HA_PROF_MEMORY)))
if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
return memprof_strdup_handler(s);
in_memprof |= MEMPROF_IN_HANDLER;
ret = memprof_strdup_handler(s);
size = malloc_usable_size(ret) + sizeof(void *);
in_memprof &= ~MEMPROF_IN_HANDLER;
bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_STRDUP);
if (unlikely(th_ctx->lock_level & 0x7F))
@ -469,13 +480,15 @@ void free(void *ptr)
struct memprof_stats *bin;
size_t size_before;
if (likely(!(profiling & HA_PROF_MEMORY) || !ptr)) {
if (likely(!(profiling & HA_PROF_MEMORY) || !ptr || (in_memprof & MEMPROF_IN_HANDLER))) {
memprof_free_handler(ptr);
return;
}
in_memprof |= MEMPROF_IN_HANDLER;
size_before = malloc_usable_size(ptr) + sizeof(void *);
memprof_free_handler(ptr);
in_memprof &= ~MEMPROF_IN_HANDLER;
bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_FREE);
if (unlikely(th_ctx->lock_level & 0x7F))
@ -495,10 +508,13 @@ char *strndup(const char *s, size_t size)
return NULL;
ret = memprof_strndup_handler(s, size);
if (likely(!(profiling & HA_PROF_MEMORY)))
if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
return ret;
in_memprof |= MEMPROF_IN_HANDLER;
size = malloc_usable_size(ret) + sizeof(void *);
in_memprof &= ~MEMPROF_IN_HANDLER;
bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_STRNDUP);
if (unlikely(th_ctx->lock_level & 0x7F))
_HA_ATOMIC_ADD(&bin->locked_calls, 1);
@ -516,10 +532,13 @@ void *valloc(size_t size)
return NULL;
ret = memprof_valloc_handler(size);
if (likely(!(profiling & HA_PROF_MEMORY)))
if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
return ret;
in_memprof |= MEMPROF_IN_HANDLER;
size = malloc_usable_size(ret) + sizeof(void *);
in_memprof &= ~MEMPROF_IN_HANDLER;
bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_VALLOC);
if (unlikely(th_ctx->lock_level & 0x7F))
_HA_ATOMIC_ADD(&bin->locked_calls, 1);
@ -537,10 +556,13 @@ void *pvalloc(size_t size)
return NULL;
ret = memprof_pvalloc_handler(size);
if (likely(!(profiling & HA_PROF_MEMORY)))
if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
return ret;
in_memprof |= MEMPROF_IN_HANDLER;
size = malloc_usable_size(ret) + sizeof(void *);
in_memprof &= ~MEMPROF_IN_HANDLER;
bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_PVALLOC);
if (unlikely(th_ctx->lock_level & 0x7F))
_HA_ATOMIC_ADD(&bin->locked_calls, 1);
@ -558,10 +580,13 @@ void *memalign(size_t align, size_t size)
return NULL;
ret = memprof_memalign_handler(align, size);
if (likely(!(profiling & HA_PROF_MEMORY)))
if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
return ret;
in_memprof |= MEMPROF_IN_HANDLER;
size = malloc_usable_size(ret) + sizeof(void *);
in_memprof &= ~MEMPROF_IN_HANDLER;
bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_MEMALIGN);
if (unlikely(th_ctx->lock_level & 0x7F))
_HA_ATOMIC_ADD(&bin->locked_calls, 1);
@ -579,10 +604,13 @@ void *aligned_alloc(size_t align, size_t size)
return NULL;
ret = memprof_aligned_alloc_handler(align, size);
if (likely(!(profiling & HA_PROF_MEMORY)))
if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
return ret;
in_memprof |= MEMPROF_IN_HANDLER;
size = malloc_usable_size(ret) + sizeof(void *);
in_memprof &= ~MEMPROF_IN_HANDLER;
bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_ALIGNED_ALLOC);
if (unlikely(th_ctx->lock_level & 0x7F))
_HA_ATOMIC_ADD(&bin->locked_calls, 1);
@ -600,13 +628,16 @@ int posix_memalign(void **ptr, size_t align, size_t size)
return ENOMEM;
ret = memprof_posix_memalign_handler(ptr, align, size);
if (likely(!(profiling & HA_PROF_MEMORY)))
if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
return ret;
if (ret != 0) // error
return ret;
in_memprof |= MEMPROF_IN_HANDLER;
size = malloc_usable_size(*ptr) + sizeof(void *);
in_memprof &= ~MEMPROF_IN_HANDLER;
bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_POSIX_MEMALIGN);
if (unlikely(th_ctx->lock_level & 0x7F))
_HA_ATOMIC_ADD(&bin->locked_calls, 1);

View file

@ -2486,16 +2486,17 @@ init_proxies_list_stage1:
/* At this point, target names have already been resolved. */
/***********************************************************/
idle_conn_task = task_new_anywhere();
if (!idle_conn_task) {
ha_alert("parsing : failed to allocate global idle connection task.\n");
cfgerr++;
}
else {
idle_conn_task->process = srv_cleanup_idle_conns;
idle_conn_task->context = NULL;
for (i = 0; i < global.nbthread; i++) {
idle_conn_srv[i] = EB_ROOT;
idle_conn_task[i] = task_new_on(i);
if (!idle_conn_task[i]) {
ha_alert("parsing : failed to allocate global idle connection task.\n");
cfgerr++;
}
else {
idle_conn_task[i]->process = srv_cleanup_idle_conns;
idle_conn_task[i]->context = NULL;
for (i = 0; i < global.nbthread; i++) {
idle_conns[i].cleanup_task = task_new_on(i);
if (!idle_conns[i].cleanup_task) {
ha_alert("parsing : failed to allocate idle connection tasks for thread '%d'.\n", i);

View file

@ -232,6 +232,9 @@ static void check_trace(enum trace_level level, uint64_t mask,
chunk_appendf(&trace_buf, " sc=%p(0x%08x)", check->sc, check->sc->flags);
}
if (check->type != PR_O2_TCPCHK_CHK)
return;
if (mask & CHK_EV_TCPCHK) {
const char *type;
@ -1404,7 +1407,20 @@ struct task *process_chk_conn(struct task *t, void *context, unsigned int state)
check_release_buf(check, &check->bi);
check_release_buf(check, &check->bo);
_HA_ATOMIC_DEC(&th_ctx->running_checks);
if (unlikely(LIST_INLIST(&check->check_queue))) {
/*
* If that check is still queued, and we're about to
* purge it, then remove it from the queue, as it is
* about to be freed.
* This can happen if a server is deleted while the check
* is queued.
*/
if (check->state & CHK_ST_PURGE)
LIST_DEL_INIT(&check->check_queue);
}
else
_HA_ATOMIC_DEC(&th_ctx->running_checks);
_HA_ATOMIC_DEC(&th_ctx->active_checks);
check->state &= ~(CHK_ST_INPROGRESS|CHK_ST_IN_ALLOC|CHK_ST_OUT_ALLOC);
check->state &= ~CHK_ST_READY;
@ -1563,6 +1579,7 @@ void free_check(struct check *check)
ha_free(&check->tcpcheck);
}
LIST_DEL_INIT(&check->check_queue);
pool_free(pool_head_uniqueid, istptr(check->unique_id));
check->unique_id = IST_NULL;
ha_free(&check->pool_conn_name);
@ -1682,7 +1699,7 @@ static int start_checks()
for (px = proxies_list; px; px = px->next) {
for (s = px->srv; s; s = s->next) {
if ((px->options2 & PR_O2_USE_SBUF_CHECK) &&
(s->check.tcpcheck->rs->flags & TCPCHK_RULES_MAY_USE_SBUF))
(s->check.tcpcheck->rs && s->check.tcpcheck->rs->flags & TCPCHK_RULES_MAY_USE_SBUF))
s->check.state |= CHK_ST_USE_SMALL_BUFF;
if (s->check.state & CHK_ST_CONFIGURED) {
@ -1799,6 +1816,9 @@ int init_srv_check(struct server *srv)
if (!srv->do_check || !(srv->proxy->cap & PR_CAP_BE))
goto out;
if (!srv->check.type && (srv->proxy->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK)
goto init;
check_type = srv->check.tcpcheck->rs->flags & TCPCHK_RULES_PROTO_CHK;
if (!(srv->flags & SRV_F_DYNAMIC)) {
@ -1939,7 +1959,7 @@ int init_srv_check(struct server *srv)
}
init:
err = init_check(&srv->check, srv->proxy->options2 & PR_O2_CHK_ANY);
err = init_check(&srv->check, srv->check.type ? srv->check.type : (srv->proxy->options2 & PR_O2_CHK_ANY));
if (err) {
ha_alert("config: %s '%s': unable to init check for server '%s' (%s).\n",
proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);

View file

@ -1279,7 +1279,7 @@ static int cpu_policy_first_usable_node(int policy, int tmin, int tmax, int gmin
if (tmin <= thr_count && thr_count < tmax)
tmax = thr_count;
ha_diag_warning("Multi-socket cpu detected, automatically binding on active CPUs of '%d' (%u active cpu(s))\n", first_node_id, cpu_count);
ha_diag_notice("Multi-socket cpu detected, automatically binding on active CPUs of '%d' (%u active cpu(s))\n", first_node_id, cpu_count);
if (!global.nbthread)
global.nbthread = tmax;
@ -1586,9 +1586,9 @@ static int cpu_policy_group_by_cluster(int policy, int tmin, int tmax, int gmin,
}
if (global.nbthread)
ha_diag_warning("Created %d threads split into %d groups\n", global.nbthread, global.nbtgroups);
ha_diag_notice("Created %d threads split into %d groups\n", global.nbthread, global.nbtgroups);
else
ha_diag_warning("Could not determine any CPU cluster\n");
ha_diag_notice("Could not determine any CPU cluster\n");
return 0;
}
@ -1684,9 +1684,9 @@ static int cpu_policy_group_by_ccx(int policy, int tmin, int tmax, int gmin, int
}
if (global.nbthread)
ha_diag_warning("Created %d threads split into %d groups\n", global.nbthread, global.nbtgroups);
ha_diag_notice("Created %d threads split into %d groups\n", global.nbthread, global.nbtgroups);
else
ha_diag_warning("Could not determine any CPU cluster\n");
ha_diag_notice("Could not determine any CPU cluster\n");
return 0;
}

View file

@ -386,6 +386,20 @@ void ha_diag_warning(const char *fmt, ...)
}
}
/*
* Displays the message on stderr with the pid if MODE_DIAG is set.
*/
void ha_diag_notice(const char *fmt, ...)
{
va_list argp;
if (global.mode & MODE_DIAG) {
va_start(argp, fmt);
print_message(1, "DIAG", fmt, argp);
va_end(argp);
}
}
/*
* Displays the message on stderr with the pid.
*/

View file

@ -48,6 +48,9 @@ static struct event_hdl_sub_type_map event_hdl_sub_type_map[] = {
{"PAT_REF_SET", EVENT_HDL_SUB_PAT_REF_SET},
{"PAT_REF_COMMIT", EVENT_HDL_SUB_PAT_REF_COMMIT},
{"PAT_REF_CLEAR", EVENT_HDL_SUB_PAT_REF_CLEAR},
{"ACME", EVENT_HDL_SUB_ACME},
{"ACME_NEWCERT", EVENT_HDL_SUB_ACME_NEWCERT},
{"ACME_DEPLOY", EVENT_HDL_SUB_ACME_DEPLOY},
};
/* internal types (only used in this file) */

View file

@ -92,6 +92,10 @@ static const struct name_desc h3_trace_decoding[] = {
{ .name="clean", .desc="only user-friendly stuff, generally suitable for level \"user\"" },
#define H3_VERB_MINIMAL 2
{ .name="minimal", .desc="report only qcc/qcs state and flags, no real decoding" },
#define H3_VERB_SIMPLE 3
{ .name="simple", .desc="add request/response status line or frame info when available" },
#define H3_VERB_ADVANCED 4
{ .name="advanced", .desc="add header fields or frame decoding when available" },
{ /* end */ }
};
@ -630,6 +634,51 @@ static struct ist _h3_trim_header(struct ist value)
return v;
}
static void _h3_trace_header(const struct ist n, const struct ist v,
uint64_t mask, const struct ist trc_loc, const char *func,
const struct qcc *qcc, const struct qcs *qcs)
{
struct ist n_short, v_short;
const char *c_str __maybe_unused;
const char *s_str __maybe_unused;
chunk_reset(&trash);
c_str = chunk_newstr(&trash);
if (qcc)
chunk_appendf(&trash, "qcc=%p(%c)", qcc, (qcc->flags & QC_CF_IS_BACK) ? 'B' : 'F');
s_str = chunk_newstr(&trash);
if (qcs)
chunk_appendf(&trash, " qcc=%p(%llu)", qcs, (ullong)qcs->id);
n_short = ist2(chunk_newstr(&trash), 0);
istscpy(&n_short, n, 256);
trash.data += n_short.len;
if (n_short.len != n.len)
chunk_appendf(&trash, " (... +%ld)", (long)(n.len - n_short.len));
v_short = ist2(chunk_newstr(&trash), 0);
istscpy(&v_short, v, 1024);
trash.data += v_short.len;
if (v_short.len != v.len)
chunk_appendf(&trash, " (... +%ld)", (long)(v.len - v_short.len));
TRACE_PRINTF_LOC(TRACE_LEVEL_USER, mask, trc_loc, func,
0, 0, 0, 0, "%s%s %s %s: %s", c_str, s_str,
mask & H3_EV_TX_HDR ? "sndh" : "rcvh",
istptr(n_short), istptr(v_short));
}
/* Output a trace for HTTP/3 header <n>:<v> if tracing is enabled. */
static void h3_trace_header(const struct ist n, const struct ist v,
uint64_t mask, const struct ist trc_loc, const char *func,
const struct qcc *qcc, const struct qcs *qcs)
{
if ((TRACE_SOURCE)->verbosity >= H3_VERB_ADVANCED &&
TRACE_ENABLED(TRACE_LEVEL_USER, mask, qcc ? qcc->conn : 0, qcs, 0, 0))
_h3_trace_header(n, v, mask, trc_loc, func, qcc, qcs);
}
/* Parse from buffer <buf> a H3 HEADERS frame of length <len>. Data are copied
* in a local HTX buffer and transfer to the stream connector layer. <fin> must be
* set if this is the last data to transfer from this stream.
@ -695,6 +744,12 @@ static ssize_t h3_req_headers_to_htx(struct qcs *qcs, const struct buffer *buf,
goto out;
}
if ((TRACE_SOURCE)->verbosity >= H3_VERB_ADVANCED &&
TRACE_ENABLED(TRACE_LEVEL_USER, H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, 0, 0, 0)) {
for (i = 0; list[i].n.len; ++i)
h3_trace_header(list[i].n, list[i].v, H3_EV_RX_HDR, ist(TRC_LOC), __FUNCTION__, qcs->qcc, qcs);
}
if (!b_alloc(&htx_buf, DB_SE_RX)) {
TRACE_ERROR("HTX buffer alloc failure", H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, qcs);
len = -1;
@ -1183,6 +1238,13 @@ static ssize_t h3_resp_headers_to_htx(struct qcs *qcs, const struct buffer *buf,
goto out;
}
if ((TRACE_SOURCE)->verbosity >= H3_VERB_ADVANCED &&
TRACE_ENABLED(TRACE_LEVEL_USER, H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, 0, 0, 0)) {
int i;
for (i = 0; list[i].n.len; ++i)
h3_trace_header(list[i].n, list[i].v, H3_EV_RX_HDR, ist(TRC_LOC), __FUNCTION__, qcs->qcc, qcs);
}
if (!(appbuf = qcc_get_stream_rxbuf(qcs))) {
TRACE_ERROR("buffer alloc failure", H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, qcs);
len = -1;
@ -2012,8 +2074,9 @@ static ssize_t h3_rcv_buf(struct qcs *qcs, struct buffer *b, int fin)
* receipt of a PUSH_PROMISE frame that contains a larger push ID than
* the client has advertised as a connection error of H3_ID_ERROR.
*/
ret = H3_ERR_ID_ERROR;
break;
TRACE_ERROR("Received unexpected PUSH_PROMISE frame", H3_EV_RX_FRAME, qcs->qcc->conn, qcs);
qcc_set_error(qcs->qcc, H3_ERR_ID_ERROR, 1, muxc_tevt_type_proto_err);
goto err;
case H3_FT_MAX_PUSH_ID:
/* h3_check_frame_valid() must reject on client side. */
BUG_ON(conn_is_back(qcs->qcc->conn));
@ -2259,6 +2322,7 @@ static int h3_req_headers_send(struct qcs *qcs, struct htx *htx)
if (qpack_encode_method(&headers_buf, sl->info.req.meth, meth))
goto err_full;
h3_trace_header(ist(":method"), meth, H3_EV_TX_HDR, ist(TRC_LOC), __FUNCTION__, qcs->qcc, qcs);
if (uri.ptr[0] != '/' && uri.ptr[0] != '*') {
int len = 1;
@ -2290,13 +2354,23 @@ static int h3_req_headers_send(struct qcs *qcs, struct htx *htx)
if (qpack_encode_scheme(&headers_buf, scheme))
goto err_full;
h3_trace_header(ist(":scheme"), scheme, H3_EV_TX_HDR, ist(TRC_LOC), __FUNCTION__, qcs->qcc, qcs);
if (qpack_encode_path(&headers_buf, uri))
goto err_full;
h3_trace_header(ist(":path"), uri, H3_EV_TX_HDR, ist(TRC_LOC), __FUNCTION__, qcs->qcc, qcs);
if (istlen(auth)) {
if (qpack_encode_auth(&headers_buf, auth))
goto err_full;
h3_trace_header(ist(":authority"), auth, H3_EV_TX_HDR, ist(TRC_LOC), __FUNCTION__, qcs->qcc, qcs);
}
if ((TRACE_SOURCE)->verbosity >= H3_VERB_ADVANCED &&
TRACE_ENABLED(TRACE_LEVEL_USER, H3_EV_TX_FRAME|H3_EV_TX_HDR, qcs->qcc->conn, 0, 0, 0)) {
int i;
for (i = 0; list[i].n.len; ++i)
h3_trace_header(list[i].n, list[i].v, H3_EV_TX_HDR, ist(TRC_LOC), __FUNCTION__, qcs->qcc, qcs);
}
if (!(sl->flags & HTX_SL_F_XFER_LEN)) {
@ -2433,6 +2507,13 @@ static int h3_resp_headers_send(struct qcs *qcs, struct htx *htx)
TRACE_USER("handling final HTX response", H3_EV_STRM_SEND, qcs->qcc->conn, qcs);
h3s->flags &= ~H3_SF_SENT_INTERIM;
}
if ((TRACE_SOURCE)->verbosity >= H3_VERB_ADVANCED) {
char sts[4];
h3_trace_header(ist(":status"), ist(ultoa_r(status, sts, sizeof(sts))),
H3_EV_TX_FRAME|H3_EV_TX_HDR, ist(TRC_LOC), __FUNCTION__,
qcs->qcc, qcs);
}
}
else if (type == HTX_BLK_HDR) {
if (unlikely(hdr >= sizeof(list) / sizeof(list[0]) - 1)) {
@ -2450,6 +2531,14 @@ static int h3_resp_headers_send(struct qcs *qcs, struct htx *htx)
}
}
if ((TRACE_SOURCE)->verbosity >= H3_VERB_ADVANCED &&
TRACE_ENABLED(TRACE_LEVEL_USER, H3_EV_TX_FRAME|H3_EV_TX_HDR, qcs->qcc->conn, 0, 0, 0)) {
int i;
for (i = 0; list[i].n.len; ++i)
h3_trace_header(list[i].n, list[i].v, H3_EV_TX_HDR, ist(TRC_LOC), __FUNCTION__, qcs->qcc, qcs);
}
/* Current function expects HTX start-line to be present. This also
* ensures <status> conformance has been checked prior to encoding it.
*/

View file

@ -2810,6 +2810,7 @@ void deinit(void)
struct cfg_postparser *pprs, *pprsb;
char **tmp = init_env;
int cur_fd;
int i;
/* the user may want to skip this phase */
if (global.tune.options & GTUNE_QUICK_EXIT)
@ -2886,8 +2887,10 @@ void deinit(void)
ha_free(&global.server_state_base);
ha_free(&global.server_state_file);
ha_free(&global.stats_file);
task_destroy(idle_conn_task);
idle_conn_task = NULL;
for (i = 0; i < global.nbthread; i++) {
task_destroy(idle_conn_task[i]);
idle_conn_task[i] = NULL;
}
list_for_each_entry_safe(log, logb, &global.loggers, list) {
LIST_DEL_INIT(&log->list);

View file

@ -73,6 +73,9 @@
#include <haproxy/event_hdl.h>
#include <haproxy/check.h>
#include <haproxy/mailers.h>
#if defined(HAVE_ACME)
#include <haproxy/acme.h>
#endif /* HAVE_ACME */
/* Global LUA flags */
@ -105,16 +108,6 @@ static uint8_t hlua_body = 1;
*/
static uint8_t hlua_bool_sample_conversion = HLUA_BOOL_SAMPLE_CONVERSION_UNK;
/* Lua uses longjmp to perform yield or throwing errors. This
* macro is used only for identifying the function that can
* not return because a longjmp is executed.
* __LJMP marks a prototype of hlua file that can use longjmp.
* WILL_LJMP() marks an lua function that will use longjmp.
* MAY_LJMP() marks an lua function that may use longjmp.
*/
#define __LJMP
#define WILL_LJMP(func) do { func; my_unreachable(); } while(0)
#define MAY_LJMP(func) func
/* This couple of function executes securely some Lua calls outside of
* the lua runtime environment. Each Lua call can return a longjmp
@ -176,6 +169,24 @@ static int hlua_panic_ljmp(lua_State *L) { WILL_LJMP(longjmp(safe_ljmp_env, 1));
*/
static struct list referenced_functions = LIST_HEAD_INIT(referenced_functions);
/* List of callbacks registered via hap_register_hlua_state_init(), called
* for each new lua_State created in hlua_init_state().
*/
static struct list hlua_state_init_list = LIST_HEAD_INIT(hlua_state_init_list);
void hap_register_hlua_state_init(int (*fct)(lua_State *L, char **errmsg))
{
struct hlua_state_init_fct *entry;
entry = calloc(1, sizeof(*entry));
if (!entry) {
ha_alert("hlua: out of memory registering state init callback\n");
exit(1);
}
entry->fct = fct;
LIST_APPEND(&hlua_state_init_list, &entry->list);
}
/* This variable is used only during initialization to identify the Lua state
* currently being initialized. 0 is the common lua state, 1 to n are the Lua
* states dedicated to each thread (in this case hlua_state_id==tid+1).
@ -499,7 +510,6 @@ static int class_fetches_ref;
static int class_converters_ref;
static int class_http_ref;
static int class_http_msg_ref;
static int class_httpclient_ref;
static int class_map_ref;
static int class_applet_tcp_ref;
static int class_applet_http_ref;
@ -988,13 +998,6 @@ const char *hlua_traceback(lua_State *L, const char* sep)
* stack. If the number of arguments available is not the same
* then <nb> an error is thrown.
*/
__LJMP static inline void check_args(lua_State *L, int nb, char *fcn)
{
if (lua_gettop(L) == nb)
return;
WILL_LJMP(luaL_error(L, "'%s' needs %d arguments", fcn, nb));
}
/* This function pushes an error string prefixed by the file name
* and the line number where the error is encountered.
*
@ -1011,7 +1014,7 @@ __LJMP static int _hlua_pusherror(lua_State *L)
return 1;
}
static int hlua_pusherror(lua_State *L, const char *fmt, ...)
int hlua_pusherror(lua_State *L, const char *fmt, ...)
{
va_list argp;
int ret = 1;
@ -1783,34 +1786,6 @@ int hlua_ctx_init(struct hlua *lua, int state_id, struct task *task)
return 1;
}
/* kill all associated httpclient to this hlua task
* We must take extra precautions as we're manipulating lua-exposed
* objects without the main lua lock.
*/
static void hlua_httpclient_destroy_all(struct hlua *hlua)
{
struct hlua_httpclient *hlua_hc;
/* use thread-safe accessors for hc_list since GC cycle initiated by
* another thread sharing the same main lua stack (lua coroutine)
* could execute hlua_httpclient_gc() on the hlua->hc_list items
* in parallel: Lua GC applies on the main stack, it is not limited to
* a single coroutine stack, see Github issue #2037 for reference.
* Remember, coroutines created using lua_newthread() are not meant to
* be thread safe in Lua. (From lua co-author:
* http://lua-users.org/lists/lua-l/2011-07/msg00072.html)
*
* This security measure is superfluous when 'lua-load-per-thread' is used
* since in this case coroutines exclusively run on the same thread
* (main stack is not shared between OS threads).
*/
while ((hlua_hc = MT_LIST_POP(&hlua->hc_list, typeof(hlua_hc), by_hlua))) {
httpclient_stop_and_destroy(hlua_hc->hc);
hlua_hc->hc = NULL;
}
}
/* Used to destroy the Lua coroutine when the attached stream or task
* is destroyed. The destroy also the memory context. The struct "lua"
* will be freed.
@ -7950,488 +7925,6 @@ __LJMP static int hlua_http_msg_unset_eom(lua_State *L)
return 0;
}
/*
*
*
* Class HTTPClient
*
*
*/
__LJMP static struct hlua_httpclient *hlua_checkhttpclient(lua_State *L, int ud)
{
return MAY_LJMP(hlua_checkudata(L, ud, class_httpclient_ref));
}
/* stops the httpclient and ask it to kill itself */
__LJMP static int hlua_httpclient_gc(lua_State *L)
{
struct hlua_httpclient *hlua_hc;
MAY_LJMP(check_args(L, 1, "__gc"));
hlua_hc = MAY_LJMP(hlua_checkhttpclient(L, 1));
if (MT_LIST_DELETE(&hlua_hc->by_hlua)) {
/* we won the race against hlua_httpclient_destroy_all() */
httpclient_stop_and_destroy(hlua_hc->hc);
hlua_hc->hc = NULL;
}
return 0;
}
__LJMP static int hlua_httpclient_new(lua_State *L)
{
struct hlua_httpclient *hlua_hc;
struct hlua *hlua;
/* Get hlua struct, or NULL if we execute from main lua state */
hlua = hlua_gethlua(L);
if (!hlua)
return 0;
/* Check stack size. */
if (!lua_checkstack(L, 3)) {
hlua_pusherror(L, "httpclient: full stack");
goto err;
}
/* Create the object: obj[0] = userdata. */
lua_newtable(L);
hlua_hc = MAY_LJMP(lua_newuserdata(L, sizeof(*hlua_hc)));
lua_rawseti(L, -2, 0);
memset(hlua_hc, 0, sizeof(*hlua_hc));
hlua_hc->hc = httpclient_new(hlua, 0, IST_NULL);
if (!hlua_hc->hc)
goto err;
MT_LIST_APPEND(&hlua->hc_list, &hlua_hc->by_hlua);
/* Pop a class stream metatable and affect it to the userdata. */
lua_rawgeti(L, LUA_REGISTRYINDEX, class_httpclient_ref);
lua_setmetatable(L, -2);
return 1;
err:
WILL_LJMP(lua_error(L));
return 0;
}
/*
* Callback of the httpclient, this callback wakes the lua task up, once the
* httpclient receives some data
*
*/
static void hlua_httpclient_cb(struct httpclient *hc)
{
struct hlua *hlua = hc->caller;
if (!hlua || !hlua->task)
return;
task_wakeup(hlua->task, TASK_WOKEN_MSG);
}
/*
* Fill the lua stack with headers from the httpclient response
* This works the same way as the hlua_http_get_headers() function
*/
__LJMP static int hlua_httpclient_get_headers(lua_State *L, struct hlua_httpclient *hlua_hc)
{
struct http_hdr *hdr;
lua_newtable(L);
for (hdr = hlua_hc->hc->res.hdrs; hdr && isttest(hdr->n); hdr++) {
struct ist n, v;
int len;
n = hdr->n;
v = hdr->v;
/* Check for existing entry:
* assume that the table is on the top of the stack, and
* push the key in the stack, the function lua_gettable()
* perform the lookup.
*/
lua_pushlstring(L, n.ptr, n.len);
lua_gettable(L, -2);
switch (lua_type(L, -1)) {
case LUA_TNIL:
/* Table not found, create it. */
lua_pop(L, 1); /* remove the nil value. */
lua_pushlstring(L, n.ptr, n.len); /* push the header name as key. */
lua_newtable(L); /* create and push empty table. */
lua_pushlstring(L, v.ptr, v.len); /* push header value. */
lua_rawseti(L, -2, 0); /* index header value (pop it). */
lua_rawset(L, -3); /* index new table with header name (pop the values). */
break;
case LUA_TTABLE:
/* Entry found: push the value in the table. */
len = lua_rawlen(L, -1);
lua_pushlstring(L, v.ptr, v.len); /* push header value. */
lua_rawseti(L, -2, len+1); /* index header value (pop it). */
lua_pop(L, 1); /* remove the table (it is stored in the main table). */
break;
default:
/* Other cases are errors. */
hlua_pusherror(L, "internal error during the parsing of headers.");
WILL_LJMP(lua_error(L));
}
}
return 1;
}
/*
* Allocate and return an array of http_hdr ist extracted from the <headers> lua table
*
* Caller must free the result
*/
struct http_hdr *hlua_httpclient_table_to_hdrs(lua_State *L)
{
struct http_hdr hdrs[global.tune.max_http_hdr];
struct http_hdr *result = NULL;
uint32_t hdr_num = 0;
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
struct ist name, value;
const char *n, *v;
size_t nlen, vlen;
if (!lua_isstring(L, -2) || !lua_istable(L, -1)) {
/* Skip element if the key is not a string or if the value is not a table */
goto next_hdr;
}
n = lua_tolstring(L, -2, &nlen);
name = ist2(n, nlen);
/* Loop on header's values */
lua_pushnil(L);
while (lua_next(L, -2)) {
if (!lua_isstring(L, -1)) {
/* Skip the value if it is not a string */
goto next_value;
}
if (hdr_num >= global.tune.max_http_hdr) {
lua_pop(L, 2);
goto skip_headers;
}
v = lua_tolstring(L, -1, &vlen);
value = ist2(v, vlen);
name = ist2(n, nlen);
hdrs[hdr_num].n = istdup(name);
hdrs[hdr_num].v = istdup(value);
hdr_num++;
next_value:
lua_pop(L, 1);
}
next_hdr:
lua_pop(L, 1);
}
if (hdr_num) {
/* alloc and copy the headers in the httpclient struct */
result = calloc((hdr_num + 1), sizeof(*result));
if (!result)
goto skip_headers;
memcpy(result, hdrs, sizeof(struct http_hdr) * (hdr_num + 1));
result[hdr_num].n = IST_NULL;
result[hdr_num].v = IST_NULL;
}
skip_headers:
return result;
}
/*
* For each yield, checks if there is some data in the httpclient and push them
* in the lua buffer, once the httpclient finished its job, push the result on
* the stack
*/
__LJMP static int hlua_httpclient_rcv_yield(lua_State *L, int status, lua_KContext ctx)
{
struct buffer *tr;
int res;
struct hlua *hlua = hlua_gethlua(L);
struct hlua_httpclient *hlua_hc = hlua_checkhttpclient(L, 1);
tr = get_trash_chunk();
res = httpclient_res_xfer(hlua_hc->hc, tr);
luaL_addlstring(&hlua_hc->b, b_orig(tr), res);
if (!httpclient_data(hlua_hc->hc) && httpclient_ended(hlua_hc->hc)) {
luaL_pushresult(&hlua_hc->b);
lua_settable(L, -3);
lua_pushstring(L, "status");
lua_pushinteger(L, hlua_hc->hc->res.status);
lua_settable(L, -3);
lua_pushstring(L, "reason");
lua_pushlstring(L, hlua_hc->hc->res.reason.ptr, hlua_hc->hc->res.reason.len);
lua_settable(L, -3);
lua_pushstring(L, "headers");
hlua_httpclient_get_headers(L, hlua_hc);
lua_settable(L, -3);
return 1;
}
if (httpclient_data(hlua_hc->hc))
task_wakeup(hlua->task, TASK_WOKEN_MSG);
MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_httpclient_rcv_yield, TICK_ETERNITY, 0));
return 0;
}
/*
* Call this when trying to stream a body during a request
*/
__LJMP static int hlua_httpclient_snd_yield(lua_State *L, int status, lua_KContext ctx)
{
struct hlua *hlua;
struct hlua_httpclient *hlua_hc = hlua_checkhttpclient(L, 1);
const char *body_str = NULL;
int ret;
int end = 0;
size_t buf_len;
size_t to_send = 0;
hlua = hlua_gethlua(L);
if (!hlua || !hlua->task)
WILL_LJMP(luaL_error(L, "The 'get' function is only allowed in "
"'frontend', 'backend' or 'task'"));
ret = lua_getfield(L, -1, "body");
if (ret != LUA_TSTRING)
goto rcv;
body_str = lua_tolstring(L, -1, &buf_len);
lua_pop(L, 1);
to_send = buf_len - hlua_hc->sent;
if ((hlua_hc->sent + to_send) >= buf_len)
end = 1;
/* the end flag is always set since we are using the whole remaining size */
hlua_hc->sent += httpclient_req_xfer(hlua_hc->hc, ist2(body_str + hlua_hc->sent, to_send), end);
if (buf_len > hlua_hc->sent) {
/* still need to process the buffer */
MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_httpclient_snd_yield, TICK_ETERNITY, 0));
} else {
goto rcv;
/* we sent the whole request buffer we can recv */
}
return 0;
rcv:
/* we return a "res" object */
lua_newtable(L);
lua_pushstring(L, "body");
luaL_buffinit(L, &hlua_hc->b);
task_wakeup(hlua->task, TASK_WOKEN_MSG);
MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_httpclient_rcv_yield, TICK_ETERNITY, 0));
return 1;
}
/*
* Send an HTTP request and wait for a response
*/
__LJMP static int hlua_httpclient_send(lua_State *L, enum http_meth_t meth)
{
struct hlua_httpclient *hlua_hc;
struct http_hdr *hdrs = NULL;
struct http_hdr *hdrs_i = NULL;
struct hlua *hlua;
const char *url_str = NULL;
const char *body_str = NULL;
size_t buf_len = 0;
int ret;
hlua = hlua_gethlua(L);
if (!hlua || !hlua->task)
WILL_LJMP(luaL_error(L, "The 'get' function is only allowed in "
"'frontend', 'backend' or 'task'"));
if (lua_gettop(L) != 2 || lua_type(L, -1) != LUA_TTABLE)
WILL_LJMP(luaL_error(L, "'get' needs a table as argument"));
hlua_hc = hlua_checkhttpclient(L, 1);
/* An HTTPclient instance must never process more that one request. So
* at this stage, it must never have been started.
*/
if (httpclient_started(hlua_hc->hc))
WILL_LJMP(luaL_error(L, "httpclient instance cannot be reused. It must process at most one request"));
lua_pushnil(L); /* first key */
while (lua_next(L, 2)) {
if (strcmp(lua_tostring(L, -2), "dst") == 0) {
if (httpclient_set_dst(hlua_hc->hc, lua_tostring(L, -1)) < 0)
WILL_LJMP(luaL_error(L, "Can't use the 'dst' argument"));
} else if (strcmp(lua_tostring(L, -2), "url") == 0) {
if (lua_type(L, -1) != LUA_TSTRING)
WILL_LJMP(luaL_error(L, "invalid parameter in 'url', must be a string"));
url_str = lua_tostring(L, -1);
} else if (strcmp(lua_tostring(L, -2), "timeout") == 0) {
if (lua_type(L, -1) != LUA_TNUMBER)
WILL_LJMP(luaL_error(L, "invalid parameter in 'timeout', must be a number"));
httpclient_set_timeout(hlua_hc->hc, lua_tointeger(L, -1));
} else if (strcmp(lua_tostring(L, -2), "headers") == 0) {
if (lua_type(L, -1) != LUA_TTABLE)
WILL_LJMP(luaL_error(L, "invalid parameter in 'headers', must be a table"));
hdrs = hlua_httpclient_table_to_hdrs(L);
} else if (strcmp(lua_tostring(L, -2), "body") == 0) {
if (lua_type(L, -1) != LUA_TSTRING)
WILL_LJMP(luaL_error(L, "invalid parameter in 'body', must be a string"));
body_str = lua_tolstring(L, -1, &buf_len);
} else {
WILL_LJMP(luaL_error(L, "'%s' invalid parameter name", lua_tostring(L, -2)));
}
/* removes 'value'; keeps 'key' for next iteration */
lua_pop(L, 1);
}
if (!url_str) {
WILL_LJMP(luaL_error(L, "'get' need a 'url' argument"));
return 0;
}
hlua_hc->sent = 0;
istfree(&hlua_hc->hc->req.url);
hlua_hc->hc->req.url = istdup(ist(url_str));
hlua_hc->hc->req.meth = meth;
/* update the httpclient callbacks */
hlua_hc->hc->ops.res_stline = hlua_httpclient_cb;
hlua_hc->hc->ops.res_headers = hlua_httpclient_cb;
hlua_hc->hc->ops.res_payload = hlua_httpclient_cb;
hlua_hc->hc->ops.res_end = hlua_httpclient_cb;
/* a body is available, it will use the request callback */
if (body_str && buf_len) {
hlua_hc->hc->ops.req_payload = hlua_httpclient_cb;
}
ret = httpclient_req_gen(hlua_hc->hc, hlua_hc->hc->req.url, meth, hdrs, IST_NULL);
/* free the temporary headers array */
hdrs_i = hdrs;
while (hdrs_i && isttest(hdrs_i->n)) {
istfree(&hdrs_i->n);
istfree(&hdrs_i->v);
hdrs_i++;
}
ha_free(&hdrs);
if (ret != ERR_NONE) {
WILL_LJMP(luaL_error(L, "Can't generate the HTTP request"));
return 0;
}
if (!httpclient_start(hlua_hc->hc))
WILL_LJMP(luaL_error(L, "couldn't start the httpclient"));
MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_httpclient_snd_yield, TICK_ETERNITY, 0));
return 0;
}
/*
* Sends an HTTP HEAD request and wait for a response
*
* httpclient:head(url, headers, payload)
*/
__LJMP static int hlua_httpclient_head(lua_State *L)
{
return hlua_httpclient_send(L, HTTP_METH_HEAD);
}
/*
* Send an HTTP GET request and wait for a response
*
* httpclient:get(url, headers, payload)
*/
__LJMP static int hlua_httpclient_get(lua_State *L)
{
return hlua_httpclient_send(L, HTTP_METH_GET);
}
/*
* Sends an HTTP PUT request and wait for a response
*
* httpclient:put(url, headers, payload)
*/
__LJMP static int hlua_httpclient_put(lua_State *L)
{
return hlua_httpclient_send(L, HTTP_METH_PUT);
}
/*
* Send an HTTP POST request and wait for a response
*
* httpclient:post(url, headers, payload)
*/
__LJMP static int hlua_httpclient_post(lua_State *L)
{
return hlua_httpclient_send(L, HTTP_METH_POST);
}
/*
* Sends an HTTP DELETE request and wait for a response
*
* httpclient:delete(url, headers, payload)
*/
__LJMP static int hlua_httpclient_delete(lua_State *L)
{
return hlua_httpclient_send(L, HTTP_METH_DELETE);
}
/*
*
*
@ -10063,6 +9556,11 @@ __LJMP static void hlua_event_hdl_cb_push_args(struct hlua_event_sub *hlua_sub,
lua_settable(hlua->T, -3);
}
}
#if defined(HAVE_ACME)
else if (event_hdl_sub_family_equal(EVENT_HDL_SUB_ACME, event)) {
MAY_LJMP(acme_hlua_event_push_args(hlua, event, data));
}
#endif /* HAVE_ACME */
/* sub mgmt */
hlua->nargs += 1;
hlua_fcn_new_event_sub(hlua->T, hlua_sub->sub);
@ -14333,7 +13831,6 @@ lua_State *hlua_init_state(int thread_num)
hlua_class_function(L, "get_patref", hlua_get_patref);
hlua_class_function(L, "get_var", hlua_core_get_var);
hlua_class_function(L, "tcp", hlua_socket_new);
hlua_class_function(L, "httpclient", hlua_httpclient_new);
hlua_class_function(L, "event_sub", hlua_event_global_sub);
hlua_class_function(L, "log", hlua_log);
hlua_class_function(L, "Debug", hlua_log_debug);
@ -14655,30 +14152,6 @@ lua_State *hlua_init_state(int thread_num)
/* Register previous table in the registry with reference and named entry. */
class_http_msg_ref = hlua_register_metatable(L, CLASS_HTTP_MSG);
/*
*
* Register class HTTPClient
*
*/
/* Create and fill the metatable. */
lua_newtable(L);
lua_pushstring(L, "__index");
lua_newtable(L);
hlua_class_function(L, "get", hlua_httpclient_get);
hlua_class_function(L, "head", hlua_httpclient_head);
hlua_class_function(L, "put", hlua_httpclient_put);
hlua_class_function(L, "post", hlua_httpclient_post);
hlua_class_function(L, "delete", hlua_httpclient_delete);
lua_settable(L, -3); /* Sets the __index entry. */
/* Register the garbage collector entry. */
lua_pushstring(L, "__gc");
lua_pushcclosure(L, hlua_httpclient_gc, 0);
lua_settable(L, -3); /* Push the last 2 entries in the table at index -3 */
class_httpclient_ref = hlua_register_metatable(L, CLASS_HTTPCLIENT);
/*
*
* Register class AppletTCP
@ -14830,6 +14303,28 @@ lua_State *hlua_init_state(int thread_num)
/* Register previous table in the registry with reference and named entry. */
class_socket_ref = hlua_register_metatable(L, CLASS_SOCKET);
/* Call all registered state init callbacks. */
{
struct hlua_state_init_fct *e;
char *errmsg = NULL;
int err_code;
list_for_each_entry(e, &hlua_state_init_list, list) {
err_code = e->fct(L, &errmsg);
if (errmsg) {
if (err_code & ERR_ALERT)
ha_alert("Lua: %s\n", errmsg);
else if (err_code & ERR_WARN)
ha_warning("Lua: %s\n", errmsg);
else
ha_notice("Lua: %s\n", errmsg);
ha_free(&errmsg);
}
if (err_code & (ERR_ABORT|ERR_FATAL))
exit(1);
}
}
lua_atpanic(L, hlua_panic_safe);
return L;
@ -14918,10 +14413,16 @@ static void hlua_deinit()
{
int thr;
struct hlua_reg_filter *reg_flt, *reg_flt_bck;
struct hlua_state_init_fct *e, *eb;
list_for_each_entry_safe(reg_flt, reg_flt_bck, &referenced_filters, l)
release_hlua_reg_filter(reg_flt);
list_for_each_entry_safe(e, eb, &hlua_state_init_list, list) {
LIST_DELETE(&e->list);
free(e);
}
for (thr = 0; thr < MAX_THREADS+1; thr++) {
if (hlua_states[thr])
lua_close(hlua_states[thr]);

View file

@ -351,6 +351,8 @@ int hpack_dht_insert(struct hpack_dht *dht, struct ist name, struct ist value)
else {
/* need to defragment the table before inserting upfront */
dht = hpack_dht_defrag(dht);
if (!dht)
return -1;
wrap = dht->wrap + 1;
head = dht->head + 1;
dht->dte[head].addr = dht->dte[dht->front].addr - (name.len + value.len);

View file

@ -1490,6 +1490,7 @@ static enum act_return http_action_set_headers_bin(struct act_rule *rule, struct
struct http_msg *msg = ((rule->from == ACT_F_HTTP_REQ) ? &s->txn.http->req : &s->txn.http->rsp);
struct htx *htx = htxbuf(&msg->chn->buf);
struct sample *hdrs_bin;
struct buffer *copy = NULL;
char *p, *end;
enum act_return ret = ACT_RET_CONT;
struct http_hdr_ctx ctx;
@ -1500,8 +1501,19 @@ static enum act_return http_action_set_headers_bin(struct act_rule *rule, struct
if (!hdrs_bin)
return ACT_RET_CONT;
p = b_orig(&hdrs_bin->data.u.str);
end = b_tail(&hdrs_bin->data.u.str);
/* The sample may point into the very HTX message we're about to modify
* (e.g. req.body) or into a rotating trash chunk that http_add_header()
* reuses internally; either way a defrag/realloc would leave our p/end/
* n/v pointers dangling. Work on a private copy to stay safe.
*/
copy = alloc_trash_chunk();
if (!copy || b_data(&hdrs_bin->data.u.str) > b_size(copy))
goto fail_rewrite;
memcpy(b_orig(copy), b_orig(&hdrs_bin->data.u.str), b_data(&hdrs_bin->data.u.str));
b_set_data(copy, b_data(&hdrs_bin->data.u.str));
p = b_orig(copy);
end = b_tail(copy);
while (p < end) {
if (decode_varint(&p, end, &sz) == -1)
goto fail_rewrite;
@ -1546,6 +1558,7 @@ static enum act_return http_action_set_headers_bin(struct act_rule *rule, struct
ret = ACT_RET_ERR;
leave:
free_trash_chunk(copy);
return ret;
fail_rewrite:

View file

@ -1265,7 +1265,7 @@ int http_wait_for_response(struct stream *s, struct channel *rep, int an_bit)
struct htx *htx;
struct connection *srv_conn;
struct htx_sl *sl;
int n;
int n, l7_retry_failed = 0;
DBG_TRACE_ENTER(STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA, s, txn, msg);
@ -1306,19 +1306,22 @@ int http_wait_for_response(struct stream *s, struct channel *rep, int an_bit)
(!conn || conn->err_code != CO_ER_SSL_EARLY_FAILED)) {
if (co_data(rep) || do_l7_retry(s, s->scb) == 0)
return 0;
l7_retry_failed = 1;
}
/* Perform a L7 retry on empty response or because server refuses the early data. */
if ((txn->flags & TX_L7_RETRY) &&
(s->be->retry_type & PR_RE_EARLY_ERROR) &&
conn && conn->err_code == CO_ER_SSL_EARLY_FAILED &&
do_l7_retry(s, s->scb) == 0) {
DBG_TRACE_DEVEL("leaving on L7 retry",
STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA, s, txn);
return 0;
conn && conn->err_code == CO_ER_SSL_EARLY_FAILED) {
if (do_l7_retry(s, s->scb) == 0) {
DBG_TRACE_DEVEL("leaving on L7 retry",
STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA, s, txn);
return 0;
}
l7_retry_failed = 1;
}
if (s->flags & SF_SRV_REUSED)
if (!l7_retry_failed && (s->flags & SF_SRV_REUSED))
goto abort_keep_alive;
if (s->be_tgcounters)
@ -1416,9 +1419,10 @@ int http_wait_for_response(struct stream *s, struct channel *rep, int an_bit)
STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA, s, txn);
return 0;
}
l7_retry_failed = 1;
}
if (s->flags & SF_SRV_REUSED)
if (!l7_retry_failed && (s->flags & SF_SRV_REUSED))
goto abort_keep_alive;
if (s->be_tgcounters)
@ -2517,7 +2521,6 @@ int http_apply_redirect_rule(struct redirect_rule *rule, struct stream *s, struc
}
case REDIRECT_TYPE_LOCATION:
default:
memset(chunk->area, 0x50, chunk->size);
if (rule->rdr_str) { /* this is an old "redirect" rule */
/* add location */
if (!chunk_memcat(chunk, rule->rdr_str, rule->rdr_len))

View file

@ -21,6 +21,12 @@
#include <haproxy/global.h>
#include <haproxy/istbuf.h>
#include <haproxy/h1_htx.h>
#ifdef USE_LUA
#include <haproxy/chunk.h>
#include <haproxy/hlua.h>
#include <haproxy/hlua_fcn.h>
#include <haproxy/task.h>
#endif
#include <haproxy/http.h>
#include <haproxy/http_ana-t.h>
#include <haproxy/http_client.h>
@ -39,6 +45,11 @@
#include <string.h>
#ifdef USE_LUA
static int class_httpclient_ref; /* httpclient LUA class */
static int class_httpclient_request_ref; /* httpclient request LUA class */
#endif
static struct proxy *httpclient_proxy;
#ifdef USE_OPENSSL
@ -677,6 +688,10 @@ void httpclient_applet_io_handler(struct appctx *appctx)
/* copy the status line in the httpclient */
hc->res.status = sl->info.res.status;
if (__sc_strm(appctx_sc(appctx))->flags & SF_ERR_MASK)
hc->res.status = 0;
hc->res.vsn = istdup(htx_sl_res_vsn(sl));
hc->res.reason = istdup(htx_sl_res_reason(sl));
htx_remove_blk(htx, blk);
@ -1487,3 +1502,559 @@ static struct cfg_kw_list cfg_kws = {ILH, {
}};
INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
/*
*
*
* Class HTTPClient
*
*
*/
#ifdef USE_LUA
/* kill all associated httpclient to this hlua task
* We must take extra precautions as we're manipulating lua-exposed
* objects without the main lua lock.
*/
void hlua_httpclient_destroy_all(struct hlua *hlua)
{
struct hlua_httpclient *hlua_hc;
/* use thread-safe accessors for hc_list since GC cycle initiated by
* another thread sharing the same main lua stack (lua coroutine)
* could execute hlua_httpclient_gc() on the hlua->hc_list items
* in parallel: Lua GC applies on the main stack, it is not limited to
* a single coroutine stack, see Github issue #2037 for reference.
* Remember, coroutines created using lua_newthread() are not meant to
* be thread safe in Lua. (From lua co-author:
* http://lua-users.org/lists/lua-l/2011-07/msg00072.html)
*
* This security measure is superfluous when 'lua-load-per-thread' is used
* since in this case coroutines exclusively run on the same thread
* (main stack is not shared between OS threads).
*/
while ((hlua_hc = MT_LIST_POP(&hlua->hc_list, typeof(hlua_hc), by_hlua))) {
httpclient_stop_and_destroy(hlua_hc->hc);
hlua_hc->hc = NULL;
}
}
__LJMP static struct hlua_httpclient *hlua_checkhttpclient(lua_State *L, int ud)
{
return MAY_LJMP(hlua_checkudata(L, ud, class_httpclient_request_ref));
}
/* stops the httpclient and ask it to kill itself */
__LJMP static int hlua_httpclient_gc(lua_State *L)
{
struct hlua_httpclient *hlua_hc;
MAY_LJMP(hlua_check_args(L, 1, "__gc"));
hlua_hc = MAY_LJMP(hlua_checkhttpclient(L, 1));
if (MT_LIST_DELETE(&hlua_hc->by_hlua)) {
/* we won the race against hlua_httpclient_destroy_all() */
httpclient_stop_and_destroy(hlua_hc->hc);
hlua_hc->hc = NULL;
}
return 0;
}
__LJMP static int hlua_httpclient_factory_new(lua_State *L)
{
lua_newtable(L);
lua_rawgeti(L, LUA_REGISTRYINDEX, class_httpclient_ref);
lua_setmetatable(L, -2);
return 1;
}
__LJMP static int hlua_httpclient_new(lua_State *L)
{
struct hlua_httpclient *hlua_hc;
struct hlua *hlua;
/* Get hlua struct, or NULL if we execute from main lua state */
hlua = hlua_gethlua(L);
if (!hlua)
return 0;
/* Check stack size. */
if (!lua_checkstack(L, 3)) {
hlua_pusherror(L, "httpclient: full stack");
goto err;
}
/* Create the object: obj[0] = userdata. */
lua_newtable(L);
hlua_hc = MAY_LJMP(lua_newuserdata(L, sizeof(*hlua_hc)));
lua_rawseti(L, -2, 0);
memset(hlua_hc, 0, sizeof(*hlua_hc));
MT_LIST_APPEND(&hlua->hc_list, &hlua_hc->by_hlua);
/* Pop a class stream metatable and affect it to the userdata. */
lua_rawgeti(L, LUA_REGISTRYINDEX, class_httpclient_request_ref);
lua_setmetatable(L, -2);
return 1;
err:
WILL_LJMP(lua_error(L));
return 0;
}
/*
* Callback of the httpclient, this callback wakes the lua task up, once the
* httpclient receives some data
*
*/
static void hlua_httpclient_cb(struct httpclient *hc)
{
struct hlua *hlua = hc->caller;
if (!hlua || !hlua->task)
return;
task_wakeup(hlua->task, TASK_WOKEN_MSG);
}
/*
* Fill the lua stack with headers from the httpclient response
* This works the same way as the hlua_http_get_headers() function
*/
__LJMP static int hlua_httpclient_get_headers(lua_State *L, struct hlua_httpclient *hlua_hc)
{
struct http_hdr *hdr;
lua_newtable(L);
for (hdr = hlua_hc->hc->res.hdrs; hdr && isttest(hdr->n); hdr++) {
struct ist n, v;
int len;
n = hdr->n;
v = hdr->v;
/* Check for existing entry:
* assume that the table is on the top of the stack, and
* push the key in the stack, the function lua_gettable()
* perform the lookup.
*/
lua_pushlstring(L, n.ptr, n.len);
lua_gettable(L, -2);
switch (lua_type(L, -1)) {
case LUA_TNIL:
/* Table not found, create it. */
lua_pop(L, 1); /* remove the nil value. */
lua_pushlstring(L, n.ptr, n.len); /* push the header name as key. */
lua_newtable(L); /* create and push empty table. */
lua_pushlstring(L, v.ptr, v.len); /* push header value. */
lua_rawseti(L, -2, 0); /* index header value (pop it). */
lua_rawset(L, -3); /* index new table with header name (pop the values). */
break;
case LUA_TTABLE:
/* Entry found: push the value in the table. */
len = lua_rawlen(L, -1);
lua_pushlstring(L, v.ptr, v.len); /* push header value. */
lua_rawseti(L, -2, len+1); /* index header value (pop it). */
lua_pop(L, 1); /* remove the table (it is stored in the main table). */
break;
default:
/* Other cases are errors. */
hlua_pusherror(L, "internal error during the parsing of headers.");
WILL_LJMP(lua_error(L));
}
}
return 1;
}
/*
* Allocate and return an array of http_hdr ist extracted from the <headers> lua table
*
* Caller must free the result
*/
static struct http_hdr *hlua_httpclient_table_to_hdrs(lua_State *L)
{
struct http_hdr hdrs[global.tune.max_http_hdr];
struct http_hdr *result = NULL;
uint32_t hdr_num = 0;
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
struct ist name, value;
const char *n, *v;
size_t nlen, vlen;
if (!lua_isstring(L, -2) || !lua_istable(L, -1)) {
/* Skip element if the key is not a string or if the value is not a table */
goto next_hdr;
}
n = lua_tolstring(L, -2, &nlen);
name = ist2(n, nlen);
/* Loop on header's values */
lua_pushnil(L);
while (lua_next(L, -2)) {
if (!lua_isstring(L, -1)) {
/* Skip the value if it is not a string */
goto next_value;
}
if (hdr_num >= global.tune.max_http_hdr) {
lua_pop(L, 2);
goto skip_headers;
}
v = lua_tolstring(L, -1, &vlen);
value = ist2(v, vlen);
name = ist2(n, nlen);
hdrs[hdr_num].n = istdup(name);
hdrs[hdr_num].v = istdup(value);
hdr_num++;
next_value:
lua_pop(L, 1);
}
next_hdr:
lua_pop(L, 1);
}
if (hdr_num) {
/* alloc and copy the headers in the httpclient struct */
result = calloc((hdr_num + 1), sizeof(*result));
if (!result)
goto skip_headers;
memcpy(result, hdrs, sizeof(struct http_hdr) * (hdr_num + 1));
result[hdr_num].n = IST_NULL;
result[hdr_num].v = IST_NULL;
}
skip_headers:
return result;
}
/*
* For each yield, checks if there is some data in the httpclient and push them
* in the lua buffer, once the httpclient finished its job, push the result on
* the stack
*/
__LJMP static int hlua_httpclient_rcv_yield(lua_State *L, int status, lua_KContext ctx)
{
struct buffer *tr;
int res;
struct hlua *hlua = hlua_gethlua(L);
struct hlua_httpclient *hlua_hc = hlua_checkhttpclient(L, 1);
tr = get_trash_chunk();
res = httpclient_res_xfer(hlua_hc->hc, tr);
luaL_addlstring(&hlua_hc->b, b_orig(tr), res);
if (!httpclient_data(hlua_hc->hc) && httpclient_ended(hlua_hc->hc)) {
luaL_pushresult(&hlua_hc->b);
lua_settable(L, -3);
lua_pushstring(L, "status");
lua_pushinteger(L, hlua_hc->hc->res.status);
lua_settable(L, -3);
lua_pushstring(L, "reason");
lua_pushlstring(L, hlua_hc->hc->res.reason.ptr, hlua_hc->hc->res.reason.len);
lua_settable(L, -3);
lua_pushstring(L, "headers");
hlua_httpclient_get_headers(L, hlua_hc);
lua_settable(L, -3);
return 1;
}
if (httpclient_data(hlua_hc->hc))
task_wakeup(hlua->task, TASK_WOKEN_MSG);
MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_httpclient_rcv_yield, TICK_ETERNITY, 0));
return 0;
}
/*
* Call this when trying to stream a body during a request
*/
__LJMP static int hlua_httpclient_snd_yield(lua_State *L, int status, lua_KContext ctx)
{
struct hlua *hlua;
struct hlua_httpclient *hlua_hc = hlua_checkhttpclient(L, 1);
const char *body_str = NULL;
int ret;
int end = 0;
size_t buf_len;
size_t to_send = 0;
hlua = hlua_gethlua(L);
if (!hlua || !hlua->task)
WILL_LJMP(luaL_error(L, "The 'get' function is only allowed in "
"'frontend', 'backend' or 'task'"));
ret = lua_getfield(L, -1, "body");
if (ret != LUA_TSTRING)
goto rcv;
body_str = lua_tolstring(L, -1, &buf_len);
lua_pop(L, 1);
to_send = buf_len - hlua_hc->sent;
if ((hlua_hc->sent + to_send) >= buf_len)
end = 1;
/* the end flag is always set since we are using the whole remaining size */
hlua_hc->sent += httpclient_req_xfer(hlua_hc->hc, ist2(body_str + hlua_hc->sent, to_send), end);
if (buf_len > hlua_hc->sent) {
/* still need to process the buffer */
MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_httpclient_snd_yield, TICK_ETERNITY, 0));
} else {
goto rcv;
/* we sent the whole request buffer we can recv */
}
return 0;
rcv:
/* we return a "res" object */
lua_newtable(L);
lua_pushstring(L, "body");
luaL_buffinit(L, &hlua_hc->b);
task_wakeup(hlua->task, TASK_WOKEN_MSG);
MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_httpclient_rcv_yield, TICK_ETERNITY, 0));
return 1;
}
/*
* Send an HTTP request and wait for a response
*/
__LJMP static int hlua_httpclient_send(lua_State *L, enum http_meth_t meth)
{
struct hlua_httpclient *hlua_hc;
struct http_hdr *hdrs = NULL;
struct http_hdr *hdrs_i = NULL;
struct hlua *hlua;
const char *url_str = NULL;
const char *body_str = NULL;
size_t buf_len = 0;
int ret;
hlua = hlua_gethlua(L);
if (!hlua || !hlua->task)
WILL_LJMP(luaL_error(L, "The 'get' function is only allowed in "
"'frontend', 'backend' or 'task'"));
if (lua_gettop(L) != 2 || lua_type(L, -1) != LUA_TTABLE)
WILL_LJMP(luaL_error(L, "'get' needs a table as argument"));
/* Create the internal httpclient request object and replace the factory at index 1 */
hlua_httpclient_new(L);
lua_replace(L, 1);
hlua_hc = hlua_checkhttpclient(L, 1);
hlua_hc->hc = httpclient_new(hlua, 0, IST_NULL);
if (!hlua_hc->hc)
WILL_LJMP(luaL_error(L, "out of memory"));
lua_pushnil(L); /* first key */
while (lua_next(L, 2)) {
if (strcmp(lua_tostring(L, -2), "dst") == 0) {
if (httpclient_set_dst(hlua_hc->hc, lua_tostring(L, -1)) < 0)
WILL_LJMP(luaL_error(L, "Can't use the 'dst' argument"));
} else if (strcmp(lua_tostring(L, -2), "url") == 0) {
if (lua_type(L, -1) != LUA_TSTRING)
WILL_LJMP(luaL_error(L, "invalid parameter in 'url', must be a string"));
url_str = lua_tostring(L, -1);
} else if (strcmp(lua_tostring(L, -2), "timeout") == 0) {
if (lua_type(L, -1) != LUA_TNUMBER)
WILL_LJMP(luaL_error(L, "invalid parameter in 'timeout', must be a number"));
httpclient_set_timeout(hlua_hc->hc, lua_tointeger(L, -1));
} else if (strcmp(lua_tostring(L, -2), "headers") == 0) {
if (lua_type(L, -1) != LUA_TTABLE)
WILL_LJMP(luaL_error(L, "invalid parameter in 'headers', must be a table"));
hdrs = hlua_httpclient_table_to_hdrs(L);
} else if (strcmp(lua_tostring(L, -2), "body") == 0) {
if (lua_type(L, -1) != LUA_TSTRING)
WILL_LJMP(luaL_error(L, "invalid parameter in 'body', must be a string"));
body_str = lua_tolstring(L, -1, &buf_len);
} else {
WILL_LJMP(luaL_error(L, "'%s' invalid parameter name", lua_tostring(L, -2)));
}
/* removes 'value'; keeps 'key' for next iteration */
lua_pop(L, 1);
}
if (!url_str) {
WILL_LJMP(luaL_error(L, "'get' need a 'url' argument"));
return 0;
}
hlua_hc->sent = 0;
istfree(&hlua_hc->hc->req.url);
hlua_hc->hc->req.url = istdup(ist(url_str));
hlua_hc->hc->req.meth = meth;
/* update the httpclient callbacks */
hlua_hc->hc->ops.res_stline = hlua_httpclient_cb;
hlua_hc->hc->ops.res_headers = hlua_httpclient_cb;
hlua_hc->hc->ops.res_payload = hlua_httpclient_cb;
hlua_hc->hc->ops.res_end = hlua_httpclient_cb;
/* a body is available, it will use the request callback */
if (body_str && buf_len) {
hlua_hc->hc->ops.req_payload = hlua_httpclient_cb;
}
ret = httpclient_req_gen(hlua_hc->hc, hlua_hc->hc->req.url, meth, hdrs, IST_NULL);
/* free the temporary headers array */
hdrs_i = hdrs;
while (hdrs_i && isttest(hdrs_i->n)) {
istfree(&hdrs_i->n);
istfree(&hdrs_i->v);
hdrs_i++;
}
ha_free(&hdrs);
if (ret != ERR_NONE) {
WILL_LJMP(luaL_error(L, "Can't generate the HTTP request"));
return 0;
}
if (!httpclient_start(hlua_hc->hc))
WILL_LJMP(luaL_error(L, "couldn't start the httpclient"));
MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_httpclient_snd_yield, TICK_ETERNITY, 0));
return 0;
}
/*
* Sends an HTTP HEAD request and wait for a response
*
* httpclient:head(url, headers, payload)
*/
__LJMP static int hlua_httpclient_head(lua_State *L)
{
return hlua_httpclient_send(L, HTTP_METH_HEAD);
}
/*
* Send an HTTP GET request and wait for a response
*
* httpclient:get(url, headers, payload)
*/
__LJMP static int hlua_httpclient_get(lua_State *L)
{
return hlua_httpclient_send(L, HTTP_METH_GET);
}
/*
* Sends an HTTP PUT request and wait for a response
*
* httpclient:put(url, headers, payload)
*/
__LJMP static int hlua_httpclient_put(lua_State *L)
{
return hlua_httpclient_send(L, HTTP_METH_PUT);
}
/*
* Send an HTTP POST request and wait for a response
*
* httpclient:post(url, headers, payload)
*/
__LJMP static int hlua_httpclient_post(lua_State *L)
{
return hlua_httpclient_send(L, HTTP_METH_POST);
}
/*
* Sends an HTTP DELETE request and wait for a response
*
* httpclient:delete(url, headers, payload)
*/
__LJMP static int hlua_httpclient_delete(lua_State *L)
{
return hlua_httpclient_send(L, HTTP_METH_DELETE);
}
/* Registers the HTTPClient Lua class and exposes core.httpclient constructor.
* Called for each new lua_State created by hlua_init_state().
*/
static int hlua_http_client_init_state(lua_State *L, char **errmsg)
{
/* Register HTTPClientRequest */
lua_newtable(L);
/* Register the garbage collector entry. */
lua_pushstring(L, "__gc");
lua_pushcclosure(L, hlua_httpclient_gc, 0);
lua_settable(L, -3);
class_httpclient_request_ref = hlua_register_metatable(L, CLASS_HTTPCLIENT_REQ);
/* Register HTTPClient */
lua_newtable(L);
lua_pushstring(L, "__index");
lua_newtable(L);
hlua_class_function(L, "get", hlua_httpclient_get);
hlua_class_function(L, "head", hlua_httpclient_head);
hlua_class_function(L, "put", hlua_httpclient_put);
hlua_class_function(L, "post", hlua_httpclient_post);
hlua_class_function(L, "delete", hlua_httpclient_delete);
lua_settable(L, -3); /* Sets the __index entry. */
class_httpclient_ref = hlua_register_metatable(L, CLASS_HTTPCLIENT);
lua_getglobal(L, "core");
hlua_class_function(L, "httpclient", hlua_httpclient_factory_new);
lua_pop(L, 1);
return ERR_NONE;
}
REGISTER_HLUA_STATE_INIT(hlua_http_client_init_state);
#endif /* USE_LUA */

View file

@ -998,8 +998,12 @@ static int cli_parse_del_map(char **args, char *payload, struct appctx *appctx,
/* Lookup the reference in the maps. */
ctx->ref = pat_ref_lookup_ref(args[2]);
if (!ctx->ref ||
!(ctx->ref->flags & ctx->display_flags))
return cli_err(appctx, "Unknown map identifier. Please use #<id> or <file>.\n");
!(ctx->ref->flags & ctx->display_flags)) {
if (ctx->display_flags == PAT_REF_MAP)
return cli_err(appctx, "Unknown map identifier. Please use #<id> or <file>.\n");
else
return cli_err(appctx, "Unknown ACL identifier. Please use #<id> or <file>.\n");
}
/* If the entry identifier start with a '#', it is considered as
* pointer id

View file

@ -53,11 +53,13 @@ struct fcgi_conn {
uint32_t streams_limit; /* maximum number of concurrent streams the peer supports */
uint32_t flags; /* Connection flags: FCGI_CF_* */
uint32_t drl; /* demux record length (if dsi >= 0) */
int16_t dsi; /* dmux stream ID (<0 = idle ) */
uint16_t drl; /* demux record length (if dsi >= 0) */
uint8_t drt; /* demux record type (if dsi >= 0) */
uint8_t drp; /* demux record padding (if dsi >= 0) */
uint32_t term_evts_log; /* Termination events log: first 4 events reported */
struct buffer dbuf; /* demux buffer */
struct buffer mbuf[FCGI_C_MBUF_CNT]; /* mux buffers (ring) */
@ -68,8 +70,6 @@ struct fcgi_conn {
unsigned int nb_reserved; /* number of reserved streams */
unsigned int stream_cnt; /* total number of streams seen */
uint32_t term_evts_log; /* Termination events log: first 4 events reported */
struct proxy *proxy; /* the proxy this connection was created for */
struct fcgi_app *app; /* FCGI application used by this mux */
struct task *task; /* timeout management task */

View file

@ -5986,7 +5986,24 @@ static int cfg_parse_h1_headers_case_adjust_file(char **args, int section_type,
return -1;
}
free(hdrs_map.name);
hdrs_map.name = strdup(args[1]);
if (args[1][0] != '/') {
char *curpath;
char *fullpath = NULL;
/* filename is provided using relative path, store the absolute path
* to take current chdir into account for other threads file load
* which occur later
*/
curpath = getcwd(trash.area, trash.size);
if (!curpath) {
memprintf(err, "failed to retrieve cur path");
return -1;
}
hdrs_map.name = memprintf(&fullpath, "%s/%s", curpath, args[1]);
}
else
hdrs_map.name = strdup(args[1]);
if (!hdrs_map.name) {
memprintf(err, "'%s %s' : out of memory", args[0], args[1]);
return -1;

View file

@ -1268,10 +1268,11 @@ static int qcs_transfer_rx_data(struct qcs *qcs, struct qc_stream_rxbuf *rxbuf)
rxbuf->off_end = qcs->rx.offset + b_data(&b) + to_copy;
eb64_insert(&qcs->rx.bufs, &rxbuf->off_node);
/* Increment next rxbuf offset. This must not exceed off_end. */
rxbuf_next->off_node.key += to_copy;
BUG_ON(rxbuf_next->off_node.key > rxbuf_next->off_end);
if (rxbuf_next->off_node.key == rxbuf_next->off_end) {
/* Now reinsert next rxbuf unless it has been completely truncated. */
if (rxbuf_next->off_node.key < rxbuf_next->off_end) {
eb64_insert(&qcs->rx.bufs, &rxbuf_next->off_node);
}
else {
@ -2333,6 +2334,9 @@ int qcc_recv_reset_stream(struct qcc *qcc, uint64_t id, uint64_t err, uint64_t f
goto err;
}
qcs->flags |= QC_SF_SIZE_KNOWN|QC_SF_RECV_RESET;
qcs_close_remote(qcs);
/* RFC 9000 3.2. Receiving Stream States
*
* An
@ -2340,14 +2344,15 @@ int qcc_recv_reset_stream(struct qcc *qcc, uint64_t id, uint64_t err, uint64_t f
* data that was not consumed, and signal the receipt of the
* RESET_STREAM.
*/
qcs->flags |= QC_SF_SIZE_KNOWN|QC_SF_RECV_RESET;
qcs_close_remote(qcs);
while (!eb_is_empty(&qcs->rx.bufs)) {
b = container_of(eb64_first(&qcs->rx.bufs),
struct qc_stream_rxbuf, off_node);
qcs_free_rxbuf(qcs, b);
}
/* Remove stream from recv_list if present. */
LIST_DEL_INIT(&qcs->el_recv);
out:
if (qcc->glitches != prev_glitches && !(qcc->flags & QC_CF_IS_BACK))
session_add_glitch_ctr(qcc->conn->owner, qcc->glitches - prev_glitches);
@ -3315,6 +3320,7 @@ static int qcc_io_recv(struct qcc *qcc)
qcc_qmux_recv(qcc);
}
next_recv:
while (!LIST_ISEMPTY(&qcc->recv_list)) {
qcs = LIST_ELEM(qcc->recv_list.n, struct qcs *, el_recv);
/* No need to add an uni local stream in recv_list. */
@ -3322,12 +3328,26 @@ static int qcc_io_recv(struct qcc *qcc)
while (qcs_rx_avail_data(qcs) && !(qcs->flags & QC_SF_DEM_FULL)) {
ret = qcc_decode_qcs(qcc, qcs);
LIST_DEL_INIT(&qcs->el_recv);
if (ret <= 0) {
LIST_DEL_INIT(&qcs->el_recv);
/* Interrupt all receive if connection on error. */
if (qcc->flags & QC_CF_ERRL)
goto done;
/* Decode next entry if stream on error. */
goto next_recv;
}
if (ret <= 0)
goto done;
total += ret;
}
/* Always remove QCS from recv_list to prevent infinite loop.
* This is performed even if inner loop was not executed : QCS
* has nothing to do in recv_list if no avail Rx data or demux
* is blocked. Next decoding will be performed on new data read
* unless demux is blocked. In this case QCS will be reinserted
* in recv_list on unblocking to execute decode here again.
*/
LIST_DEL_INIT(&qcs->el_recv);
}
done:

View file

@ -370,6 +370,13 @@ struct pool_head *create_pool_from_reg(const char *name, struct pool_registratio
return NULL;
}
if (invalid_char(name)) {
ha_alert("Pool '%s' declared at %s:%u contains invalid chars in its name and "
"cannot be registered. Please report to developers. Aborting.\n",
name, reg->file, reg->line);
return NULL;
}
extra_mark = (pool_debugging & POOL_DBG_TAG) ? POOL_EXTRA_MARK : 0;
extra_caller = (pool_debugging & POOL_DBG_CALLER) ? POOL_EXTRA_CALLER : 0;
extra = extra_mark + extra_caller;

View file

@ -58,6 +58,7 @@ void quic_transport_params_init(struct quic_transport_params *p, int server)
server ? quic_tune.fe.stream_max_concurrent : quic_tune.be.stream_max_concurrent;
/* TODO value used to conform with HTTP/3, should be derived from app_ops */
const int max_streams_uni = 3;
uint64_t max_stream_data_bidi;
/* Set RFC default values for unspecified parameters. */
quic_dflt_transport_params_cpy(p);
@ -81,21 +82,30 @@ void quic_transport_params_init(struct quic_transport_params *p, int server)
p->initial_max_data = stream_rxbuf ?
stream_rxbuf : max_streams_bidi * stream_rx_bufsz;
/* Set remote streams flow-control data limit. This is calculated as a
* ratio from max-data, then rounded up to bufsize.
/* Calculate the limit for the Rx capability of bidirectional streams.
* This is a ratio from max-data rounded up to bufsize.
*/
p->initial_max_stream_data_bidi_remote = server ?
max_stream_data_bidi = server ?
p->initial_max_data * quic_tune.fe.stream_data_ratio / 100 :
p->initial_max_data * quic_tune.be.stream_data_ratio / 100;
p->initial_max_stream_data_bidi_remote =
stream_rx_bufsz * ((p->initial_max_stream_data_bidi_remote + (stream_rx_bufsz - 1)) / stream_rx_bufsz);
max_stream_data_bidi =
stream_rx_bufsz * ((max_stream_data_bidi + (stream_rx_bufsz - 1)) / stream_rx_bufsz);
/* Set remaining flow-control data limit. Local bidi streams are unused
* on server side. Uni streams are only used for control exchange, so
* only a single buffer for in flight data should be enough.
/* Apply bidirectional streams Rx cap. This depends on the connection
* side. On FE side, exchange will occur on remote streams; on BE side,
* exchange will occur on local streams.
*/
p->initial_max_stream_data_bidi_local = stream_rx_bufsz;
p->initial_max_stream_data_uni = stream_rx_bufsz;
if (server) {
p->initial_max_stream_data_bidi_remote = max_stream_data_bidi;
p->initial_max_stream_data_bidi_local = stream_rx_bufsz;
}
else {
p->initial_max_stream_data_bidi_remote = stream_rx_bufsz;
p->initial_max_stream_data_bidi_local = max_stream_data_bidi;
}
/* Unidirectional streams exchange should be minimal. */
p->initial_max_stream_data_uni = stream_rx_bufsz;
if (server) {
p->with_stateless_reset_token = 1;

View file

@ -2029,8 +2029,8 @@ static int qc_do_build_pkt(unsigned char *pos, const unsigned char *end,
/* Handle Initial packet padding if necessary. */
if (padding && dglen < QUIC_INITIAL_PACKET_MINLEN) {
padding_len = QUIC_INITIAL_PACKET_MINLEN - dglen;
len += padding_len;
/* Update size of packet length field with new PADDING data. */
if (pkt->type != QUIC_PACKET_TYPE_SHORT) {
size_t len_sz_diff = quic_int_getsize(len) - len_sz;
@ -2038,6 +2038,7 @@ static int qc_do_build_pkt(unsigned char *pos, const unsigned char *end,
padding_len -= len_sz_diff;
len_sz += len_sz_diff;
dglen += len_sz_diff;
len -= len_sz_diff;
}
}
}
@ -2074,6 +2075,7 @@ static int qc_do_build_pkt(unsigned char *pos, const unsigned char *end,
len += padding_len;
}
/* Encode length field : length of PN and payload (frames + TLS AEAD tag). */
if (pkt->type != QUIC_PACKET_TYPE_SHORT && !quic_enc_int(&pos, end, len))
goto no_room;

View file

@ -442,7 +442,7 @@ static void regex_register_build_options(void)
INITCALL0(STG_REGISTER, regex_register_build_options);
#ifdef USE_PCRE2
static int init_pcre2_per_thread(void)
static int init_pcre2_one_thread(void)
{
local_pcre2_match = pcre2_match_data_create(MAX_MATCH, NULL);
if (!local_pcre2_match) {
@ -452,13 +452,32 @@ static int init_pcre2_per_thread(void)
return 1;
}
/* per-thread init for the next threads (first one already done) */
static int init_pcre2_per_thread(void)
{
if (!tid)
return 1;
return init_pcre2_one_thread();
}
/* per-thread deinit for the next threads */
static void deinit_pcre2_per_thread(void)
{
if (tid)
pcre2_match_data_free(local_pcre2_match);
}
/* late deinit for the first thread */
static void deinit_pcre2_first_thread(void)
{
pcre2_match_data_free(local_pcre2_match);
}
/* early init for the first thread */
INITCALL0(STG_INIT, init_pcre2_one_thread);
REGISTER_PER_THREAD_INIT(init_pcre2_per_thread);
REGISTER_PER_THREAD_DEINIT(deinit_pcre2_per_thread);
REGISTER_POST_DEINIT(deinit_pcre2_first_thread);
#endif
/*

View file

@ -76,9 +76,8 @@ struct srv_kw_list srv_keywords = {
.list = LIST_HEAD_INIT(srv_keywords.list)
};
__decl_thread(HA_SPINLOCK_T idle_conn_srv_lock);
struct eb_root idle_conn_srv = EB_ROOT;
struct task *idle_conn_task __read_mostly = NULL;
struct eb_root idle_conn_srv[MAX_THREADS];
struct task *idle_conn_task[MAX_THREADS] __read_mostly = {};
struct mt_list servers_list = MT_LIST_HEAD_INIT(servers_list);
static struct task *server_atomic_sync_task = NULL;
static event_hdl_async_equeue server_atomic_sync_queue;
@ -2978,6 +2977,19 @@ void srv_settings_cpy(struct server *srv, const struct server *src, int srv_tmpl
srv->check.alpn_len = src->check.alpn_len;
}
}
if (src->check.tcpcheck && src->check.tcpcheck->healthcheck) {
struct tcpcheck *tcpcheck = NULL;
tcpcheck = calloc(1, sizeof(*tcpcheck));
if (tcpcheck) {
LIST_INIT(&tcpcheck->preset_vars);
tcpcheck->healthcheck = strdup(src->check.tcpcheck->healthcheck);
if (tcpcheck->healthcheck == NULL)
ha_free(&tcpcheck);
}
if (tcpcheck)
srv->check.tcpcheck = tcpcheck;
}
if (!(srv->flags & SRV_F_RHTTP))
srv->check.reuse_pool = src->check.reuse_pool;
@ -3268,6 +3280,8 @@ struct server *srv_drop(struct server *srv)
/* This BUG_ON() is invalid for now as server released on deinit will
* trigger it as they are not properly removed from their tree.
* This is even more relevant now, as we would need to check the
* idle_node for each thread
*/
//BUG_ON(ceb_intree(&srv->addr_node) ||
// srv->idle_node.node.leaf_p ||
@ -6115,6 +6129,7 @@ static int srv_init_per_thr(struct server *srv)
srv->per_thr[i].idle_conns = NULL;
srv->per_thr[i].safe_conns = NULL;
srv->per_thr[i].avail_conns = NULL;
srv->per_thr[i].srv = srv;
MT_LIST_INIT(&srv->per_thr[i].sess_conns);
MT_LIST_INIT(&srv->per_thr[i].streams);
@ -6206,7 +6221,6 @@ static int cli_parse_add_server(char **args, char *payload, struct appctx *appct
struct server *srv;
char *be_name, *sv_name, *errmsg;
int errcode, argc;
int next_id;
const int parse_flags = SRV_PARSE_DYNAMIC|SRV_PARSE_PARSE_ADDR;
usermsgs_clr("CLI");
@ -6373,6 +6387,19 @@ static int cli_parse_add_server(char **args, char *payload, struct appctx *appct
if (errcode)
goto out;
/* Generate the server ID if not manually specified. This must be
* performed before the server queuing in LB tree (srv_alloc_lb()).
* Proxy tree ID insertion though is only done when all fallible
* operation are completed.
*/
if (!srv->puid) {
srv->puid = server_get_next_id(be, 1);
if (!srv->puid) {
ha_alert("Cannot attach server : no id left in proxy\n");
goto out;
}
}
if (!srv_alloc_lb(srv, be)) {
ha_alert("Failed to initialize load-balancing data.\n");
goto out;
@ -6392,16 +6419,7 @@ static int cli_parse_add_server(char **args, char *payload, struct appctx *appct
if (errcode)
goto out;
/* generate the server id if not manually specified */
if (!srv->puid) {
next_id = server_get_next_id(be, 1);
if (!next_id) {
ha_alert("Cannot attach server : no id left in proxy\n");
goto out;
}
srv->puid = next_id;
}
/* All fallible operations completed, the server can now be made visible. */
/* insert the server in the backend trees */
server_index_id(be, srv);
@ -6638,7 +6656,8 @@ static int cli_parse_delete_server(char **args, char *payload, struct appctx *ap
cebuis_item_delete(&be->used_server_addr, addr_node, addr_key, srv);
/* remove srv from idle_node tree for idle conn cleanup */
eb32_delete(&srv->idle_node);
for (ret = 0; ret < global.nbthread; ret++)
eb32_delete(&srv->per_thr[ret].idle_node);
/* set LSB bit (odd bit) for reuse_cnt */
srv_id_reuse_cnt |= 1;
@ -7531,20 +7550,16 @@ int srv_add_to_idle_list(struct server *srv, struct connection *conn, int is_saf
HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock);
_HA_ATOMIC_INC(&srv->curr_idle_thr[tid]);
if (HA_ATOMIC_LOAD(&srv->idle_node.node.leaf_p) == NULL) {
HA_SPIN_LOCK(OTHER_LOCK, &idle_conn_srv_lock);
if (_HA_ATOMIC_LOAD(&srv->idle_node.node.leaf_p) == NULL) {
srv->idle_node.key = tick_add(srv->pool_purge_delay,
if (srv->per_thr[tid].idle_node.node.leaf_p == NULL) {
srv->per_thr[tid].idle_node.key = tick_add(srv->pool_purge_delay,
now_ms);
eb32_insert(&idle_conn_srv, &srv->idle_node);
if (!task_in_wq(idle_conn_task) && !
task_in_rq(idle_conn_task)) {
task_schedule(idle_conn_task,
srv->idle_node.key);
}
BUG_ON_STRESS(!mt_list_isempty(&conn->toremove_list));
eb32_insert(&idle_conn_srv[tid], &srv->per_thr[tid].idle_node);
if (!task_in_wq(idle_conn_task[tid]) &&
!task_in_rq(idle_conn_task[tid])) {
task_schedule(idle_conn_task[tid],
srv->per_thr[tid].idle_node.key);
}
HA_SPIN_UNLOCK(OTHER_LOCK, &idle_conn_srv_lock);
BUG_ON_STRESS(!mt_list_isempty(&conn->toremove_list));
}
return 1;
}
@ -7566,24 +7581,26 @@ struct task *srv_cleanup_idle_conns(struct task *task, void *context, unsigned i
{
struct server *srv;
struct eb32_node *eb;
int i;
unsigned int next_wakeup;
int mytid = tid;
next_wakeup = TICK_ETERNITY;
HA_SPIN_LOCK(OTHER_LOCK, &idle_conn_srv_lock);
while (1) {
struct srv_per_thread *per_thr;
int exceed_conns;
int to_kill;
int curr_idle;
int max_conn;
int removed;
eb = eb32_lookup_ge(&idle_conn_srv, now_ms - TIMER_LOOK_BACK);
eb = eb32_lookup_ge(&idle_conn_srv[mytid], now_ms - TIMER_LOOK_BACK);
if (!eb) {
/* we might have reached the end of the tree, typically because
* <now_ms> is in the first half and we're first scanning the last
* half. Let's loop back to the beginning of the tree now.
*/
eb = eb32_first(&idle_conn_srv);
eb = eb32_first(&idle_conn_srv[mytid]);
if (likely(!eb))
break;
}
@ -7592,7 +7609,8 @@ struct task *srv_cleanup_idle_conns(struct task *task, void *context, unsigned i
next_wakeup = eb->key;
break;
}
srv = eb32_entry(eb, struct server, idle_node);
per_thr = eb32_entry(eb, struct srv_per_thread, idle_node);
srv = per_thr->srv;
/* Calculate how many idle connections we want to kill :
* we want to remove half the difference between the total
@ -7605,47 +7623,41 @@ struct task *srv_cleanup_idle_conns(struct task *task, void *context, unsigned i
exceed_conns = srv->curr_used_conns + curr_idle - MAX(srv->max_used_conns, srv->est_need_conns);
exceed_conns = to_kill = exceed_conns / 2 + (exceed_conns & 1);
srv->est_need_conns = (srv->est_need_conns + srv->max_used_conns) / 2;
/*
* It is acceptable not to lock anything before modifying
* est_need_conns and max_used_conns, even if multiple threads
* are running that task at the same time, we don't need a
* very high precision here, it will converge over time.
*/
HA_ATOMIC_STORE(&srv->est_need_conns, (srv->est_need_conns + srv->max_used_conns) / 2);
if (srv->est_need_conns < srv->max_used_conns)
srv->est_need_conns = srv->max_used_conns;
HA_ATOMIC_STORE(&srv->est_need_conns, srv->max_used_conns);
HA_ATOMIC_STORE(&srv->max_used_conns, srv->curr_used_conns);
if (exceed_conns <= 0)
goto remove;
/* check all threads starting with ours */
for (i = tid;;) {
int max_conn;
int removed;
max_conn = (exceed_conns * srv->curr_idle_thr[mytid]) / curr_idle + 1;
max_conn = (exceed_conns * srv->curr_idle_thr[i]) /
curr_idle + 1;
HA_SPIN_LOCK(IDLE_CONNS_LOCK, &idle_conns[mytid].idle_conns_lock);
removed = srv_migrate_conns_to_remove(srv, mytid, max_conn);
HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[mytid].idle_conns_lock);
HA_SPIN_LOCK(IDLE_CONNS_LOCK, &idle_conns[i].idle_conns_lock);
removed = srv_migrate_conns_to_remove(srv, i, max_conn);
HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[i].idle_conns_lock);
if (removed)
task_wakeup(idle_conns[i].cleanup_task, TASK_WOKEN_OTHER);
if ((i = ((i + 1 == global.nbthread) ? 0 : i + 1)) == tid)
break;
}
if (removed)
task_wakeup(idle_conns[mytid].cleanup_task, TASK_WOKEN_OTHER);
remove:
eb32_delete(&srv->idle_node);
eb32_delete(&srv->per_thr[mytid].idle_node);
if (srv->curr_idle_conns) {
if (!LIST_ISEMPTY(&srv->per_thr[mytid].idle_conn_list)) {
/* There are still more idle connections, add the
* server back in the tree.
*/
srv->idle_node.key = tick_add(srv->pool_purge_delay, now_ms);
eb32_insert(&idle_conn_srv, &srv->idle_node);
next_wakeup = tick_first(next_wakeup, srv->idle_node.key);
srv->per_thr[mytid].idle_node.key = tick_add(srv->pool_purge_delay, now_ms);
eb32_insert(&idle_conn_srv[mytid], &srv->per_thr[mytid].idle_node);
next_wakeup = tick_first(next_wakeup, srv->per_thr[mytid].idle_node.key);
}
}
HA_SPIN_UNLOCK(OTHER_LOCK, &idle_conn_srv_lock);
task->expire = next_wakeup;
return task;
}

View file

@ -30,7 +30,7 @@
DECLARE_TYPED_POOL(pool_head_session, "session", struct session);
DECLARE_TYPED_POOL(pool_head_sess_priv_conns, "session priv conns list", struct sess_priv_conns);
DECLARE_TYPED_POOL(pool_head_sess_priv_conns, "sess_priv_conns", struct sess_priv_conns);
int conn_complete_session(struct connection *conn);

View file

@ -439,6 +439,22 @@ static int ha_ssl_read(BIO *h, char *buf, int size)
}
#ifdef HA_USE_KTLS
static int ktls_enable_ulp(struct ssl_sock_ctx *ctx)
{
int ret = 0;
if (!(ctx->flags & SSL_SOCK_F_KTLS_ULP)) {
ret = setsockopt(ctx->conn->handle.fd, SOL_TCP, TCP_ULP, "tls",
sizeof("tls"));
if (ret == 0)
ctx->flags |= SSL_SOCK_F_KTLS_ULP;
else
ctx->flags &= ~SSL_SOCK_F_KTLS_ENABLED;
}
return ret;
}
/* Returns 0 on success, -1 on failure */
static int ktls_set_key(struct ssl_sock_ctx *ctx, void *info, size_t info_len, int is_tx)
{
@ -485,6 +501,9 @@ static long ha_ssl_ctrl(BIO *h, int cmd, long arg1, void *arg2)
if (!(ctx->flags & SSL_SOCK_F_KTLS_ENABLED))
return 0;
if (ktls_enable_ulp(ctx) == -1)
return 0;
/*
* As OpenSSL doesn't export struct tls_crypto_info_all,
* and it puts the size at the end of the struct,
@ -5670,14 +5689,6 @@ static int ssl_sock_start(struct connection *conn, void *xprt_ctx)
if (ret < 0)
return ret;
}
#ifdef HA_USE_KTLS
/*
* Make the socket usable for kTLS. That does not mean that we will
* use kTLS, though, just that the socket will be able to do it.
*/
if ((ctx->flags & SSL_SOCK_F_KTLS_ENABLED) && setsockopt(conn->handle.fd, SOL_TCP, TCP_ULP, "tls", sizeof("tls")) != 0)
ctx->flags &= ~SSL_SOCK_F_KTLS_ENABLED;
#endif
tasklet_wakeup(ctx->wait_event.tasklet);
return 0;
@ -6617,6 +6628,9 @@ static void ssl_sock_setup_ktls(struct ssl_sock_ctx *ctx)
if (!(ctx->flags & SSL_SOCK_F_KTLS_ENABLED))
return;
if (ktls_enable_ulp(ctx) == -1)
return;
switch (SSL_version(ctx->ssl)) {
case TLS_1_2_VERSION:
is_tls_12 = 1;
@ -7078,8 +7092,12 @@ static size_t ssl_sock_to_buf(struct connection *conn, void *xprt_ctx, struct bu
memcpy(b_tail(buf), b_head(&ctx->early_buf), try);
b_add(buf, try);
b_del(&ctx->early_buf, try);
if (b_data(&ctx->early_buf) == 0)
b_free(&ctx->early_buf);
if (b_data(&ctx->early_buf) == 0) {
if (!(ctx->conn->flags & CO_FL_EARLY_SSL_HS))
b_free(&ctx->early_buf);
else
b_reset(&ctx->early_buf);
}
TRACE_STATE("read early data", SSL_EV_CONN_RECV|SSL_EV_CONN_RECV_EARLY, conn, &try);
return try;
}

View file

@ -91,6 +91,9 @@ void se_shutdown(struct sedesc *sedesc, enum se_shut_mode mode)
struct se_abort_info *reason = NULL;
unsigned int flags = 0;
/* Should never happen, placed here to be sure we forgot nothing */
BUG_ON(!(mode & (SE_SHW_SILENT|SE_SHW_NORMAL)));
if ((mode & (SE_SHW_SILENT|SE_SHW_NORMAL)) && !se_fl_test(sedesc, SE_FL_SHW)) {
se_report_term_evt(sedesc, se_tevt_type_shutw);
flags |= (mode & SE_SHW_NORMAL) ? SE_FL_SHWN : SE_FL_SHWS;

View file

@ -35,18 +35,15 @@ DECLARE_TYPED_POOL(pool_head_tasklet, "tasklet", struct tasklet, 0, 64);
*/
DECLARE_TYPED_POOL(pool_head_notification, "notification", struct notification);
/* The lock protecting all wait queues at once. For now we have no better
* alternative since a task may have to be removed from a queue and placed
* into another one. Storing the WQ index into the task doesn't seem to be
* sufficient either.
*/
__decl_aligned_rwlock(wq_lock);
/* used to detect if the scheduler looks stuck (for warnings) */
static struct {
int sched_stuck THREAD_ALIGNED();
} sched_ctx[MAX_THREADS];
#if !defined(HA_CAS_IS_8B) && !defined(HA_HAVE_CAS_DW)
__decl_thread(HA_SPINLOCK_T task_state_tid);
#endif
/* Flags the task <t> for immediate destruction and puts it into its first
* thread's shared tasklet list if not yet queued/running. This will bypass
* the priority scheduling and make the task show up as fast as possible in
@ -82,7 +79,9 @@ void task_kill(struct task *t)
* Note: that's a task so it must be accounted for as such. Pick
* the task's first thread for the job.
*/
thr = t->tid >= 0 ? t->tid : tid;
thr = __task_get_current_owner(t->tid);
if (thr == -1)
thr = tid;
/* Beware: tasks that have never run don't have their ->list empty yet! */
MT_LIST_APPEND(&ha_thread_ctx[thr].shared_tasklet_list,
@ -216,7 +215,13 @@ struct list *__tasklet_wakeup_after(struct list *head, struct tasklet *tl)
void __task_wakeup(struct task *t)
{
struct eb_root *root = &th_ctx->rqueue;
int thr __maybe_unused = t->tid >= 0 ? t->tid : tid;
/*
* At this point the task tid should always be set to the relevant
* thread, so we can just use __task_get_current_owner();
*/
int thr __maybe_unused = __task_get_current_owner(t->tid);
BUG_ON(t->tid == -1);
#ifdef USE_THREAD
if (thr != tid) {
@ -275,18 +280,38 @@ void __task_wakeup(struct task *t)
* at all about locking so the caller must be careful when deciding whether to
* lock or not around this call.
*/
void __task_queue(struct task *task, struct eb_root *wq)
void __task_queue(struct task *task)
{
#ifdef USE_THREAD
BUG_ON((wq == &tg_ctx->timers && task->tid >= 0) ||
(wq == &th_ctx->timers && task->tid < 0) ||
(wq != &tg_ctx->timers && wq != &th_ctx->timers));
#endif
int old_state, new_state;
int old_tid;
int cur_owner;
/* if this happens the process is doomed anyway, so better catch it now
* so that we have the caller in the stack.
*/
BUG_ON(task->expire == TICK_ETERNITY);
do {
new_state = old_state = _HA_ATOMIC_LOAD(&task->state);
if (old_state & TASK_KILLED)
return;
old_tid = _HA_ATOMIC_LOAD(&task->tid);
cur_owner = __task_get_current_owner(old_tid);
if (old_tid != -1 && cur_owner != tid)
new_state |= TASK_WOKEN_WQ;
} while (!(__task_set_state_and_tid(task, old_tid, __task_get_new_tid_field(old_tid), old_state, new_state)));
if (cur_owner != tid && cur_owner != -1) {
/*
* If the task has already been woken up to be added in the
* wait queue, nothing left to do, the target thread will
* eventually do the right thing.
*/
if (!(old_state & TASK_WOKEN_WQ))
_task_wakeup(task, 0, NULL);
return;
}
if (likely(task_in_wq(task)))
__task_unlink_wq(task);
@ -298,7 +323,7 @@ void __task_queue(struct task *task, struct eb_root *wq)
return;
#endif
eb32_insert(wq, &task->wq);
eb32_insert(&th_ctx->timers, &task->wq);
}
/*
@ -311,7 +336,6 @@ void wake_expired_tasks()
int max_processed = global.tune.runqueue_depth;
struct task *task;
struct eb32_node *eb;
__decl_thread(int key);
while (1) {
if (max_processed-- <= 0)
@ -348,9 +372,53 @@ void wake_expired_tasks()
task = eb32_entry(eb, struct task, wq);
if (tick_is_expired(task->expire, now_ms)) {
int set_running = 0;
/* expired task, wake it up */
__task_unlink_wq(task);
_task_wakeup(task, TASK_WOKEN_TIMER, 0);
/*
* If it's a shared task, see whether we should hand it
* to a less loaded thread.
*/
if (task->tid < 0) {
int attempts = MIN(global.nbthread, 3);
while (attempts-- > 0) {
uint new_tid = statistical_prng_range(global.nbthread);
if (new_tid == tid)
continue;
if (ha_thread_ctx[new_tid].rq_total * 2 < th_ctx->rq_total) {
int cur_state;
do {
cur_state = _HA_ATOMIC_LOAD(&task->state);
/*
* Okay the task is already in our runqueue,
* or somebody owns the
* TASK_RUNNING flag because
* it is calling task_schedule(), give up.
*/
if (cur_state & (TASK_QUEUED | TASK_RUNNING))
break;
/*
* Make sure we have TASK_RUNNING set
* so that the task don't
* immediately run on the
* new thread and gets
* freed.
*/
if (__task_set_state_and_tid(task, task->tid, -2 - new_tid, cur_state, cur_state | TASK_RUNNING)) {
set_running = 1;
break;
}
} while (1);
break;
}
}
}
if (set_running)
task_drop_running(task, TASK_WOKEN_TIMER);
else
_task_wakeup(task, TASK_WOKEN_TIMER, 0);
}
else if (task->expire != eb->key) {
/* task is not expired but its key doesn't match so let's
@ -358,7 +426,7 @@ void wake_expired_tasks()
*/
__task_unlink_wq(task);
if (tick_isset(task->expire))
__task_queue(task, &tt->timers);
__task_queue(task);
}
else {
/* task not expired and correctly placed. It may not be eternal. */
@ -366,105 +434,12 @@ void wake_expired_tasks()
break;
}
}
#ifdef USE_THREAD
if (eb_is_empty(&tg_ctx->timers))
goto leave;
HA_RWLOCK_RDLOCK(TASK_WQ_LOCK, &wq_lock);
eb = eb32_lookup_ge(&tg_ctx->timers, now_ms - TIMER_LOOK_BACK);
if (!eb) {
eb = eb32_first(&tg_ctx->timers);
if (likely(!eb)) {
HA_RWLOCK_RDUNLOCK(TASK_WQ_LOCK, &wq_lock);
goto leave;
}
}
key = eb->key;
if (tick_is_lt(now_ms, key)) {
HA_RWLOCK_RDUNLOCK(TASK_WQ_LOCK, &wq_lock);
goto leave;
}
/* There's really something of interest here, let's visit the queue */
if (HA_RWLOCK_TRYRDTOSK(TASK_WQ_LOCK, &wq_lock)) {
/* if we failed to grab the lock it means another thread is
* already doing the same here, so let it do the job.
*/
HA_RWLOCK_RDUNLOCK(TASK_WQ_LOCK, &wq_lock);
goto leave;
}
while (1) {
lookup_next:
if (max_processed-- <= 0)
break;
eb = eb32_lookup_ge(&tg_ctx->timers, now_ms - TIMER_LOOK_BACK);
if (!eb) {
/* we might have reached the end of the tree, typically because
* <now_ms> is in the first half and we're first scanning the last
* half. Let's loop back to the beginning of the tree now.
*/
eb = eb32_first(&tg_ctx->timers);
if (likely(!eb))
break;
}
task = eb32_entry(eb, struct task, wq);
/* Check for any competing run of the task (quite rare but may
* involve a dangerous concurrent access on task->expire). In
* order to protect against this, we'll take an exclusive access
* on TASK_RUNNING before checking/touching task->expire. If the
* task is already RUNNING on another thread, it will deal by
* itself with the requeuing so we must not do anything and
* simply quit the loop for now, because we cannot wait with the
* WQ lock held as this would prevent the running thread from
* requeuing the task. One annoying effect of holding RUNNING
* here is that a concurrent task_wakeup() will refrain from
* waking it up. This forces us to check for a wakeup after
* releasing the flag.
*/
if (HA_ATOMIC_FETCH_OR(&task->state, TASK_RUNNING) & TASK_RUNNING)
break;
if (tick_is_expired(task->expire, now_ms)) {
/* expired task, wake it up */
HA_RWLOCK_SKTOWR(TASK_WQ_LOCK, &wq_lock);
__task_unlink_wq(task);
HA_RWLOCK_WRTOSK(TASK_WQ_LOCK, &wq_lock);
task_drop_running(task, TASK_WOKEN_TIMER);
}
else if (task->expire != eb->key) {
/* task is not expired but its key doesn't match so let's
* update it and skip to next apparently expired task.
*/
HA_RWLOCK_SKTOWR(TASK_WQ_LOCK, &wq_lock);
__task_unlink_wq(task);
if (tick_isset(task->expire))
__task_queue(task, &tg_ctx->timers);
HA_RWLOCK_WRTOSK(TASK_WQ_LOCK, &wq_lock);
task_drop_running(task, 0);
goto lookup_next;
}
else {
/* task not expired and correctly placed. It may not be eternal. */
BUG_ON(task->expire == TICK_ETERNITY);
task_drop_running(task, 0);
break;
}
}
HA_RWLOCK_SKUNLOCK(TASK_WQ_LOCK, &wq_lock);
#endif
leave:
return;
}
/* Checks the next timer for the current thread by looking into its own timer
* list and the global one. It may return TICK_ETERNITY if no timer is present.
* list. It may return TICK_ETERNITY if no timer is present.
* Note that the next timer might very well be slightly in the past.
*/
int next_timer_expiry()
@ -472,7 +447,6 @@ int next_timer_expiry()
struct thread_ctx * const tt = th_ctx; // thread's tasks
struct eb32_node *eb;
int ret = TICK_ETERNITY;
__decl_thread(int key = TICK_ETERNITY);
/* first check in the thread-local timers */
eb = eb32_lookup_ge(&tt->timers, now_ms - TIMER_LOOK_BACK);
@ -487,19 +461,6 @@ int next_timer_expiry()
if (eb)
ret = eb->key;
#ifdef USE_THREAD
if (!eb_is_empty(&tg_ctx->timers)) {
HA_RWLOCK_RDLOCK(TASK_WQ_LOCK, &wq_lock);
eb = eb32_lookup_ge(&tg_ctx->timers, now_ms - TIMER_LOOK_BACK);
if (!eb)
eb = eb32_first(&tg_ctx->timers);
if (eb)
key = eb->key;
HA_RWLOCK_RDUNLOCK(TASK_WQ_LOCK, &wq_lock);
if (eb)
ret = tick_first(ret, key);
}
#endif
return ret;
}
@ -652,6 +613,19 @@ unsigned int run_tasks_from_lists(unsigned int budgets[])
goto next;
}
if (state & TASK_WOKEN_WQ) {
/* We should add this task to our wait queue */
task_queue(t);
/*
* If this is the only reason the task got scheduled,
* then we don't actually have ot run it.
*/
if ((state & TASK_WOKEN_ANY) == TASK_WOKEN_WQ) {
task_drop_running(t, 0);
goto next;
}
state &= ~TASK_WOKEN_WQ;
}
/* OK now the task or tasklet is well alive and is going to be run */
if (state & TASK_F_TASKLET) {
/* this is a tasklet */
@ -680,7 +654,8 @@ unsigned int run_tasks_from_lists(unsigned int budgets[])
__task_free(t);
}
else {
task_queue(t);
if (__task_get_current_owner(t->tid) == tid)
task_queue(t);
task_drop_running(t, 0);
}
}
@ -949,13 +924,6 @@ void mworker_cleantasks()
tmp_rq = eb32_next(tmp_rq);
task_destroy(t);
}
/* cleanup the timers queue */
tmp_wq = eb32_first(&tg_ctx->timers);
while (tmp_wq) {
t = eb32_entry(tmp_wq, struct task, wq);
tmp_wq = eb32_next(tmp_wq);
task_destroy(t);
}
#endif
/* clean the per thread run queue */
for (i = 0; i < global.nbthread; i++) {
@ -980,9 +948,6 @@ static void init_task()
{
int i, q;
for (i = 0; i < MAX_TGROUPS; i++)
memset(&ha_tgroup_ctx[i].timers, 0, sizeof(ha_tgroup_ctx[i].timers));
for (i = 0; i < MAX_THREADS; i++) {
for (q = 0; q < TL_CLASSES; q++)
LIST_INIT(&ha_thread_ctx[i].tasklets[q]);

View file

@ -4198,7 +4198,7 @@ int check_server_tcpcheck(struct server *srv)
}
srv->check.tcpcheck->rs = rs;
srv->check.tcpcheck->flags = rs->conf.flags;
srv->check.type = PR_O2_TCPCHK_CHK;
err_code = check_tcpcheck_ruleset(srv->proxy, rs);
}
@ -5970,7 +5970,7 @@ static int srv_parse_healthcheck(char **args, int *cur_arg, struct proxy *curpx,
goto out;
}
if (srv->check.tcpcheck->healthcheck) {
if (srv->check.tcpcheck && srv->check.tcpcheck->healthcheck) {
/* a healthcheck section was already defined. Replace it */
ha_free(&srv->check.tcpcheck->healthcheck);
}

View file

@ -434,7 +434,6 @@ const char *lock_label(enum lock_label label)
{
switch (label) {
case TASK_RQ_LOCK: return "TASK_RQ";
case TASK_WQ_LOCK: return "TASK_WQ";
case LISTENER_LOCK: return "LISTENER";
case PROXY_LOCK: return "PROXY";
case SERVER_LOCK: return "SERVER";
@ -1667,7 +1666,9 @@ void thread_detect_count(void)
char *err __maybe_unused;
int thr_forced = 0;
int tgrp_forced = 0;
#ifdef USE_THREAD
int cpus_detected = 0;
#endif
thr_min = 1; thr_max = MAX_THREADS;
grp_min = 1; grp_max = MAX_TGROUPS;
@ -1810,6 +1811,7 @@ void thread_detect_count(void)
if (!global.nbtgroups)
global.nbtgroups = grp_min;
#ifdef USE_THREAD
if (tgrp_forced && !thr_forced && !cpu_map_in_conf &&
(!global.thread_limit || (global.nbthread < global.thread_limit)) &&
global.nbthread < MIN(cpus_detected, thread_cpus_enabled_at_boot) &&
@ -1828,6 +1830,7 @@ void thread_detect_count(void)
ha_notice("%d usable CPUs detected but 'nbthread' forced to %d (%d%%). Remove 'nbthread' for optimal tuning.\n",
nbcpus, global.nbthread, global.nbthread * 100 / nbcpus);
}
#endif
if (global.nbthread > global.maxthrpertgroup * global.nbtgroups) {
ha_diag_warning("nbthread too large or not set, found %d CPUs, limiting to %d threads (maximum is %d per thread group and %d groups). Please set nbthreads and/or increase thread-groups in the global section to silence this warning.\n",

View file

@ -350,6 +350,22 @@ static void xprt_qmux_close(struct connection *conn, void *xprt_ctx)
pool_free(xprt_qmux_ctx_pool, ctx);
}
/* Retrieve the ssl_sock_ctx of the lower layer. Contrary to most XPRTs, QMux
* is stacked on top of the SSL layer (and not the other way around), so during
* the QMux handshake conn->xprt points to xprt_qmux. Without this delegation,
* conn_get_ssl_sock_ctx() would return NULL for any code inspecting the SSL
* layer of the connection (sample fetches, logging, info callback, ...) while
* the QMux handshake is in progress.
*/
static struct ssl_sock_ctx *xprt_qmux_get_ssl_sock_ctx(struct connection *conn)
{
struct xprt_qmux_ctx *ctx = conn->xprt_ctx;
if (ctx && ctx->ops_lower == xprt_get(XPRT_SSL))
return ctx->ctx_lower;
return NULL;
}
static int xprt_qmux_get_alpn(const struct connection *conn, void *xprt_ctx,
const char **str, int *len)
{
@ -371,6 +387,7 @@ struct xprt_ops xprt_qmux = {
.start = xprt_qmux_start,
.close = xprt_qmux_close,
.get_alpn = xprt_qmux_get_alpn,
.get_ssl_sock_ctx = xprt_qmux_get_ssl_sock_ctx,
.name = "qmux",
};