]> git.ipfire.org Git - thirdparty/haproxy.git/log
thirdparty/haproxy.git
6 days agoBUG/MINOR: debug: only dump the trace once in __BUG_ON_ONCE()
Willy Tarreau [Fri, 7 Aug 2026 13:06:53 +0000 (15:06 +0200)] 
BUG/MINOR: debug: only dump the trace once in __BUG_ON_ONCE()

Amaury found that CHECK_IF() was dumping the stack trace on each call,
which was not the intent. The reason is that complain() performs the
check on the counter, but the decision to dump the stack later is based
on the choice between aborting and dumping. Let's break out of this when
the count is > 1. Under high concurrency it can cause a trace never to
be emitted but this is a detail. The whole thing needs to be redone
more cleanly anyway.

This should be backported to 2.6.

6 days agoBUG/MINOR: lb-chash: bound the walk when the saved cursor changed tree
Willy Tarreau [Thu, 6 Aug 2026 07:31:15 +0000 (09:31 +0200)] 
BUG/MINOR: lb-chash: bound the walk when the saved cursor changed tree

chash.last is not reset when the tree in use switches from the backup
servers back to the active ones. The next call then starts walking the
active tree while keeping a backup node as the stop condition, which it can
never reach, so it cycles over the active tree forever as soon as no server
can be picked: all of them saturated (maxconn reached with a queue, or
served >= the dynamic maxconn), or the only one left being the server to
avoid on a redispatch. In this case, if chash.last (assigned to <stop>)
isn't in the current tree, the loop will run forever.

Let's just count the number of times we wrap and stop at the second,
which indicates that the stop server is not in the tree.

Note that this is more theoretical than practical: this needs
"hash-type consistent" with requests not carrying the hash key so that
this fallback is used at all, backup servers with "option allbackups"
(without it lbprm.fbck is returned directly and the backup tree is never
walked), and all active servers going down then one coming back while
the remaining ones are saturated. In practice it has been there since
consistent hash was introduced in 1.4 by commit 6b2e11be1 ("[MEDIUM]
backend: implement consistent hashing variation") and was never
reported. It may be backported to all stable versions.

Reported-by: Claude (ANT-2026-98TCHHRD)
6 days agoBUG/MEDIUM: session: don't release a reversed connection twice on error
Willy Tarreau [Thu, 6 Aug 2026 07:27:02 +0000 (09:27 +0200)] 
BUG/MEDIUM: session: don't release a reversed connection twice on error

A reversed rhttp connection already has its mux installed when it
reaches session_accept_fd(). If an error occurs after setup (e.g. a
"tcp-request connection reject" rule, or conn_xprt_start() failing),
the error path calls session_free(), which destroys the mux and thus
releases the connection. Control then reaches out_free_conn, which
calls conn_release() on it a second time, causing a crash.

Stop after session_free() when the connection has a mux, since it's
already handled. The listener still needs explicit release.

This dates back to reverse-http's introduction in 3.0 (12c40c25a,
"MEDIUM: rhttp: create session for active preconnect"). Must be
backported to 3.0.

Reported-by: Claude (ANT-2026-WNWQ4RGC)
6 days agoBUG/MINOR: connection: reserve the whole CRC32C TLV before saving its pointer
Willy Tarreau [Thu, 6 Aug 2026 07:27:02 +0000 (09:27 +0200)] 
BUG/MINOR: connection: reserve the whole CRC32C TLV before saving its pointer

make_proxy_line_v2() saves a pointer to the CRC32C TLV value so the
checksum can be appended once the whole header is known. It only checked
for 3 bytes (the TLV header) instead of the 7 needed for the full TLV, so
with 3 to 6 bytes left, make_tlv() emitted nothing and returned 0, yet
tlv_crc32c_p was still set, and the final write_u32() wrote 4 bytes up to
3 bytes past the end of the header buffer.

Not only this theoretically only affects servers using "send-proxy-v2"
with "proxy-v2-options crc32c" combined with "set-proxy-v2-tlv-fmt" TLVs,
but in addition in practice the proxy protocol is designed to be way
shorter than a regular buffer, and the only practical way to reach that
is to purposely write a config to demonstrate this, so it cannot happen.

This has been there since crc32c support was added in 1.9 by commit
4399c75f6 ("MINOR: proxy-v2-options: add crc32c"). It should be backported
to all stable versions.

Reported-by: Claude (ANT-2026-H10QWEV6)
6 days agoBUG/MEDIUM: fd: release the port range entry in host byte order
Dragan Dosen [Fri, 7 Aug 2026 11:08:45 +0000 (11:08 +0000)] 
BUG/MEDIUM: fd: release the port range entry in host byte order

The port recovered with getsockname() in _fd_delete_orphan() was given
back to the port range without being converted to host byte order. The
range then fills up with byte-swapped values which are handed out on the
next pass. Both TCP and QUIC are affected.

It is now released using get_host_port(), which takes care of the
conversion for both address families.

The issue was introduced with commit 02b7685013 ("MEDIUM: fd: Remove
fdinfo").

No backport needed.

7 days agoMEDIUM: pipes: Never allocate more than maxpipes
Olivier Houchard [Fri, 7 Aug 2026 10:20:05 +0000 (12:20 +0200)] 
MEDIUM: pipes: Never allocate more than maxpipes

Commit c81d794822cf8fa3c2e163cbf4cba80ad0dd9a47 made it so we could have
one pipe pool per thread group, however it did not limit how many pipes
we'd allocate per thread group, so one thread group could end up having
all the pipes. Prevent that by allowing a maximum of maxpipes / nbtgroup
per thread group.

7 days agoDOC: Document limitations of tune.fd.tables
Olivier Houchard [Thu, 6 Aug 2026 12:39:39 +0000 (14:39 +0200)] 
DOC: Document limitations of tune.fd.tables

Document that when multi-queue is used with tune.fd.tables set to
per-thread-group, then connections will only be load-balanced across
threads from the same thread group.

7 days agoMINOR: backend: Deprecate tune.takeover-other-tg-connections.
Olivier Houchard [Thu, 6 Aug 2026 10:55:39 +0000 (12:55 +0200)] 
MINOR: backend: Deprecate tune.takeover-other-tg-connections.

Deprecate tune.takeover-other-tg-connections, as we're now supposed to
use tune.idle-pool.shared.

7 days agoMINOR: backend: Do not always allow takeover across thread groups
Olivier Houchard [Thu, 6 Aug 2026 10:49:54 +0000 (12:49 +0200)] 
MINOR: backend: Do not always allow takeover across thread groups

Instead of silently ignoring tune.idle-pool.shared. full
if tune.fd.tables per-thread-group is set, have an error at startup if
both are set, as they are incompatible.

7 days agoBUG/MINOR: acme: restrict the permissions of the generated account key
William Lallemand [Thu, 6 Aug 2026 07:46:19 +0000 (09:46 +0200)] 
BUG/MINOR: acme: restrict the permissions of the generated account key

When no ACME account key exists yet, haproxy generates one and writes it
through a plain BIO_new_file(), so the file is created with the process'
umask applied. With the common 022 umask the unencrypted private key ends
up on disk as 0644, readable by every local user.

Whoever reads that key can authenticate to the CA as this haproxy ACME
account, and from there deactivate it or manipulate the orders and
revocations for the domains it has validated.

Let's restrict the file to 0600 right after creating it and before writing
anything into it.

This was introduced in 3.2 by commit 856b6042d ("MEDIUM: acme: generate
the account file when not found"). It must be backported to 3.2.

Reported-by: Claude (ANT-2026-2TZ0NDHX)
7 days agoBUG/MINOR: ssl: reject server certificate names containing a NUL byte
William Lallemand [Thu, 6 Aug 2026 07:44:59 +0000 (09:44 +0200)] 
BUG/MINOR: ssl: reject server certificate names containing a NUL byte

ssl_sock_srv_verifycbk() decodes each SAN dNSName and each CN of the
server certificate with ASN1_STRING_to_UTF8(), which returns the decoded
length, then passes the result to the hostname matcher as a plain
NUL-terminated C string and throws the length away. A name encoded as
"victim.com\0.attacker.com" is therefore compared as "victim.com" and
matches.

This defeats the point of "verify required" together with "verifyhost" or
SNI on a server line: an attacker holding a certificate with such a name,
and able to intercept the connection to the backend, passes the name check
and can read and alter all the proxied traffic. This is the CVE-2009-2408
class of bug. It requires a CA to issue such a certificate, which modern
CAs refuse to do, so the practical risk is low, but the check is cheap.

Let's compare the decoded length with strlen() and ignore any name that
does not match.

This has been there since "verifyhost" was introduced in 1.5 by commit
be55431f9 ("MINOR: ssl: Add statement 'verifyhost' to "server"
statements"). It may be backported to all stable versions.

Reported-by: Claude (ANT-2026-SNXPSVKX)
7 days agoBUG/MEDIUM: acme: don't delete a NULL token from the map
William Lallemand [Thu, 6 Aug 2026 07:29:07 +0000 (09:29 +0200)] 
BUG/MEDIUM: acme: don't delete a NULL token from the map

When an ACME task ends, acme_del_acme_ctx_map() walks the list of
authorizations and removes each challenge token from the configured map.
But an authorization is created with only its URL set, and its token is
only filled in once the CA's answer for that authorization has been
successfully parsed. The "dns-persist-01" challenge type never sets one at
all. Every termination path of the task goes through that cleanup, so a
NULL token was passed to pat_ref_delete(), which compares it against the
keys of the reference and dereferences it.

For users this means that an ACME configuration using a "map" crashes the
worker whenever the certificate renewal fails early, for instance when the
CA is unreachable and the retries are exhausted, or when it answers
something unexpected. The very component that is supposed to keep the
service running then takes it down.

Let's simply ignore authorizations without a token, they have nothing
registered in the map anyway.

This was introduced in 3.2 by commit 5555926fd ("MEDIUM: acme: use a map
to store tokens and thumbprints"). It must be backported to 3.2.

Reported-by: Claude (ANT-2026-YXC5HJZS)
7 days agoBUG/MEDIUM: ssl: require a full-length AEAD tag when decrypting with AES-GCM
Remi Tricot-Le Breton [Thu, 6 Aug 2026 07:34:24 +0000 (09:34 +0200)] 
BUG/MEDIUM: ssl: require a full-length AEAD tag when decrypting with AES-GCM

aes_process() passes the caller-provided tag length straight to
EVP_CTRL_AEAD_SET_TAG, and OpenSSL accepts and verifies GCM tags as short
as one byte. In the "aes_gcm_dec" converter the tag argument is documented
as coming from a variable, which in practice is populated from request
data, and in the JWE AES-GCM key-wrap path it comes directly from the
token. In both cases the party submitting the ciphertext also chooses how
many bytes of the authentication tag get checked.

An attacker submitting a one-byte tag therefore only needs about 256
attempts, instead of 2^128, to have arbitrary ciphertext accepted as
authentic. For a configuration relying on these converters to validate a
signed or encrypted token, this is a full authentication bypass.

The encrypt side always emits a 16-byte tag, so let's simply require
exactly that on the decrypt side.

This has been there since these converters were introduced, the shared
helper coming from commit f0e64de75 ("MINOR: ssl: Factorize AES GCM data
processing") in 3.4. It must be backported to all stable versions
providing "aes_gcm_dec".

Reported-by: Claude (ANT-2026-15HD08AS)
7 days agoBUG/MINOR: jwt: don't take an extra reference on the certificate public key
Remi Tricot-Le Breton [Thu, 6 Aug 2026 07:35:42 +0000 (09:35 +0200)] 
BUG/MINOR: jwt: don't take an extra reference on the certificate public key

X509_get_pubkey() already returns an owned reference, so the extra
EVP_PKEY_up_ref() on the cert-store path leaked one reference per
verification, since only one EVP_PKEY_free() is done afterwards. Over
time this means key objects accumulate and are never released, even
across certificate reloads.

Drop the extra reference. The "jwt_cert_tree" path below still needs
its up_ref, since it only holds a borrowed pointer.

Introduced in 3.3 by 522bca98e ("MAJOR: jwt: Allow certificate instead
of public key in jwt_verify converter"). Must be backported to 3.3.

Reported-by: Claude (ANT-2026-PKPCQ3ZN)
7 days agoBUG/MEDIUM: jwe: validate the secret length against the algorithm of the token
Remi Tricot-Le Breton [Thu, 6 Aug 2026 07:33:59 +0000 (09:33 +0200)] 
BUG/MEDIUM: jwe: validate the secret length against the algorithm of the token

The "alg" field of the JOSE header of a JWE token selects the key-wrapping
algorithm, hence the AES key size, while the key itself is the secret
configured by the operator for the "jwt_decrypt_secret" converter. That
secret is stored in an exact-size heap allocation, and neither
decrypt_cek_aeskw() nor aes_process() (used by the AES-GCM key wrap
variant) checked that it was large enough for the selected cipher before
handing its address to OpenSSL.

So a client sending a token that declares A256KW or A256GCMKW while the
configured secret is only 16 bytes long makes OpenSSL read 16 bytes past
the end of that allocation and use them as key material. This is a
remotely triggered heap over-read which may crash the worker, and whose
bytes influence the decryption result.

Let's check the secret length against the cipher's key length in both
paths before initializing the cipher.

Both were introduced in 3.4, by commits f0e64de75 ("MINOR: ssl: Factorize
AES GCM data processing") and 416b87d5d ("MINOR: jwe: Add new
jwt_decrypt_secret converter"). This must be backported to 3.4.

Reported-by: Claude (ANT-2026-TS9WFQ5T)
Reported-by: Claude (ANT-2026-9T34RNDD)
7 days agoBUG/MEDIUM: log: always reserve room for trailing 0 when using CBOR encoding helpers
Aurelien DARRAGON [Thu, 6 Aug 2026 19:15:36 +0000 (21:15 +0200)] 
BUG/MEDIUM: log: always reserve room for trailing 0 when using CBOR encoding helpers

Logging helpers leveraged by sess_build_logline_orig() can be split in
two different groups. Although they all look similar in their construction
as they take pretty much the same parameters and return the address where
following bytes can be appended, some will always try to append the
terminating NULL byte and return the address of the terminating NULL
byte, while others (which are not specifically text oriented) will simply
use all available space (they don't reserve space for the terminating NULL
byte) and return the address of the byte following the last byte written.
But since they don't try to write the \0 themselves, they will in practise
output one extra byte compared to other helpers. If no precaution is taken
and they are used as drop-in replacement to text oriented ones, this can
cause invalid writes later in the code because sess_build_logline_orig()
will always append the terminating NULL byte (even if it is already set),
thus it is mandatory that the output pointer never reaches the stopmark.

Fortunately, most pitfalls were already avoided in log generation path,
but recent commit c614fd3b ("MINOR: log: add +cbor encoding option")
made use of several encoding helpers which were not text oriented as
text oriented ones. Let's fix that by always securing 1 byte for the
terminating NULL byte when calling them (even if it not used by the
endpoint X format, sess_build_logline_orig() will append it not matter
what, so we have to live with that).

Reported-by: Claude (ANT-2026-QQ17FDX1)
It should be backported up to 3.0.

7 days agoMINOR: log/tools: fix ambiguous comments for some log encoding helpers
Aurelien DARRAGON [Thu, 6 Aug 2026 17:20:04 +0000 (19:20 +0200)] 
MINOR: log/tools: fix ambiguous comments for some log encoding helpers

Some log encoding helpers are not text oriented, and they will use all
available space since they will not try to write a terminating NULL
byte at the end of the produced output themselves. But since they
work similarly to text oriented ones, they return the address of
the byte immediately following the payload (where we expect
following bytes to be written). For text oriented ones this corresponds
in fact to the terminating NULL byte which was accounted in the
available space, while for non text-oriented ones, which doesn't reserve
space for the terminating NULL byte this corresponds to 1 byte past the
payload. If available space is strictly the size of the produced output,
then it means the returned address will be 1 byte PAST the stop limit so
no extra bytes could be written anymore. When using this helpers, callers
have to be very careful to reserve bytes (ie: terminating NULL byte) if
they need to.

It may be backported up to 3.0. Before that such ambiguities didn't exist
as logging features were strictly text-oriented and log encoders were not
available.

7 days agoBUG/MINOR: mux-fcgi: sanitize the STDERR records before logging them
Olivier Houchard [Thu, 6 Aug 2026 11:54:52 +0000 (13:54 +0200)] 
BUG/MINOR: mux-fcgi: sanitize the STDERR records before logging them

fcgi_strm_handle_stderr() emits one log line per STDERR record, appending
its own newline, but passes the record payload to app_log() with a bare
"%s", and neither app_log() nor __send_log() escape anything. FastCGI
applications routinely echo parts of the request in their warnings, so a
client whose input is reflected there can insert CR/LF and turn one record
into several log lines, or insert ESC sequences which the operator's
terminal interprets when reading the log. Verified with an application
writing "bad input 'x\r\nFAKE-INJECTED-LINE: ...\033[31m...'": the syslog
datagram carries all of it verbatim.

Escaping data emitted to logs is normally a configuration matter, but this
path bypasses the log-format machinery entirely, and the intent here is
clearly one record per line. Let's replace the control characters with a
dot before logging, which is a single pass over a record that is only ever
produced when the application writes to its stderr. Only controls are
replaced, so that the UTF-8 messages commonly found in such warnings are
left intact.

This has been there since the FCGI mux was introduced in 2.1 by commit
99eff65f4 ("MEDIUM: mux-fcgi: Add the FCGI multiplexer"). It may be
backported to all stable versions.

Reported-by: Claude (ANT-2026-9JD79F3M)
7 days agoBUG/MEDIUM: http-ana: check the cookie rewrite result before moving the offsets
Olivier Houchard [Thu, 6 Aug 2026 07:47:15 +0000 (09:47 +0200)] 
BUG/MEDIUM: http-ana: check the cookie rewrite result before moving the offsets

In the "rewrite" and "prefix" cookie modes,
http_manage_server_side_cookies() calls http_replace_header_value() and
ignores its return value. When the expansion doesn't fit it returns 0 and
leaves ctx.value untouched, yet the code still computes <delta> from
srv->cklen, advances next and hdr_end by it, and in "prefix" mode writes
"val_beg[srv->cklen] = COOKIE_DELIM" over data that was never moved. The
delimiter thus lands in whatever follows the cookie and the response is
forwarded with that byte corrupted, while the cookie is left unprefixed,
silently losing persistence.

The maxrewrite reserve normally covers the expansion, so this needs the
inserted string to be larger than it. Reproduced both with a 1100-byte
server cookie name and the default tune.maxrewrite, and with a 61-byte one
and "tune.maxrewrite 16": the Set-Cookie comes back untouched and a '~'
appears in the middle of the next header's value.

Let's check the return value like every other rewrite site does: on
failure nothing is touched, the failed_rewrites counters are incremented
and the parsing of that response's cookies stops there.

This has been there since the HTX cookie handling was added in 1.9 by
commit fcda7c685 ("MINOR: proto_htx: Add functions to manage cookies on
HTX messages"). It may be backported to all stable versions.

Reported-by: Claude (ANT-2026-FVC9MZEJ)
7 days agoBUG/MEDIUM: sock: bound the recvmsg() length when receiving old sockets
Olivier Houchard [Thu, 6 Aug 2026 07:32:10 +0000 (09:32 +0200)] 
BUG/MEDIUM: sock: bound the recvmsg() length when receiving old sockets

sock_get_old_sockets() sizes tmpbuf from the number of FDs announced by the
old process, but passes a fixed iov_len of MAX_SEND_FD entries to every
recvmsg() and loops as long as fewer FDs than announced were received,
without ever comparing curoff to the size of the allocation. A peer
announcing a single FD (4118 bytes allocated) and then streaming plain data
with no SCM_RIGHTS makes the kernel write up to 252*4118 bytes per recvmsg()
past the end of the buffer, and the loop never ends. Reproduced with a fake
old process: glibc aborts on "free(): invalid next size" after ~320 kB. Only
the peer of the -x transfer socket can do this, so it is not reachable from
the network, but it happens before privileges are dropped.

Let's clamp each recvmsg() to the room really left in the allocation and
abort the transfer when the peer sends more. Legitimate transfers are
unaffected, they use at most 1+255+1+255+4 bytes per FD.

This has been there since commit f73629d23 ("MINOR: global: Add an option to
get the old listening sockets.") in 1.8, which already sized tmpbuf on fd_nb
and the iovec on MAX_SEND_FD. It may be backported to all stable versions.

Reported-by: Claude (ANT-2026-Q363CKEH)
7 days agoBUG/MEDIUM: stick-tables: use the same bucket for string keys with a NUL
Olivier Houchard [Thu, 6 Aug 2026 07:30:13 +0000 (09:30 +0200)] 
BUG/MEDIUM: stick-tables: use the same bucket for string keys with a NUL

String stick-table keys are stored NUL-terminated and looked up in a string
ebtree, so an entry is identified by the bytes preceding the first NUL.
Accordingly stksess_kill(), __stksess_kill_if_expired(), stktable_lookup(),
stktable_requeue_exp() and stktable_set_entry() all derive the bucket from
strlen() of the stored key, but stktable_lookup_key() and
stktable_get_entry() derive it from the raw sample length. A key carrying an
embedded NUL is thus inserted in one bucket and later killed or requeued
while holding the lock of another one, so that bucket's tree ends up
modified without its lock while other threads look it up: corrupted tree,
hence a crash, a lost or duplicated entry, or a use-after-free on a stksess.

Embedded NULs are not exotic: url_decode() turns "%00" into one and keeps
going, while smp_to_stkey() passes the sample length as-is. Tracking
"url_param(q),url_dec" into a string table and sending "GET /?q=AB%00CD"
yields key_len 5 but strlen 2, hence two different buckets. The peers
protocol also transports raw key bytes.

Let's make the two remaining places stop at the first NUL as well, so that
a single canonical length is used everywhere.

The bucket split was introduced in 3.0 by commit 1a088da7c ("MAJOR:
stktable: split the keys across multiple shards to reduce contention").
This must be backported to 3.0.

Reported-by: Claude (ANT-2026-TNFHK5ZG)
7 days agoBUG/MEDIUM: http-ana: don't crash on "keep-query" in a response redirect
Olivier Houchard [Thu, 6 Aug 2026 07:28:26 +0000 (09:28 +0200)] 
BUG/MEDIUM: http-ana: don't crash on "keep-query" in a response redirect

http_apply_redirect_rule() always takes its HTX from the request channel
(htxbuf(&s->req.buf)), and the "keep-query" option rebuilds the query string
from the request start line. This works for a request redirect, but the
option is also accepted on "http-response redirect", and by then the request
has usually been forwarded and its buffer is empty, so http_get_stline()
returns NULL and htx_sl_req_uri() dereferences it. Thus a rule such as
"http-response redirect location /moved keep-query" crashes on the first
request that matches it (reproduced with a plain "GET /foo?a=b").

There is no query-string to preserve once the request is gone, so let's skip
that part when the start line is no longer available and emit the location
as-is. Scheme- and prefix-based redirects are rejected on the response path
by the parser, so they are left untouched.

This was introduced in 3.1 by commit b2877db47 ("MINOR: http-ana: Add
option to keep query-string on a localtion-based redirect"). It must be
backported to 3.1.

Reported-by: Claude (ANT-2026-7AZMS41X)
7 days agoBUG/MINOR: mux-fcgi: don't call fcgi_strm_destroy() on a NULL stream
Olivier Houchard [Thu, 6 Aug 2026 07:26:05 +0000 (09:26 +0200)] 
BUG/MINOR: mux-fcgi: don't call fcgi_strm_destroy() on a NULL stream

fcgi_stconn_new() has three "goto out" taken before the stream is allocated
(streams limit reached, no stream left) or when the allocation failed, and
the out label unconditionally calls fcgi_strm_destroy(), which dereferences
<fstrm> right away. So a failure to allocate the stream or its tasklet
crashes instead of returning a clean error. The first two paths are
normally prevented by the reuse layer which checks avail_streams first.

Let's just skip the destruction when the stream is NULL.

This came with commit 070b91bc1 ("MEDIUM: conn-stream: Be prepared to fail
to attach a cs to a mux") in 2.6, which added this destroy call for the new
sc_attach_mux() failure path without protecting the pre-existing ones (the
other muxes did it right). It must be backported to all stable versions.

Reported-by: Claude (ANT-2026-VN29N97G)
7 days agoBUG/MEDIUM: spoe: clear the applet pointer when the applet fails to start
Olivier Houchard [Thu, 6 Aug 2026 07:26:05 +0000 (09:26 +0200)] 
BUG/MEDIUM: spoe: clear the applet pointer when the applet fails to start

spoe_create_appctx() assigns the freshly allocated spoe_appctx to
ctx->spoe_appctx before creating and initializing the applet, both of which
may fail. On these error paths the spoe_appctx is released but the pointer
is left in the SPOE context, and the caller reports the failure through
spoe_stop_processing(), which reads it back, writes into it, then performs
appctx_strm(sa->owner)->parent = NULL and appctx_wakeup(sa->owner). As
<owner> sits at offset 0, right where pool_free() stores its cache linkage,
it is not even NULL but points into the pool cache, so these two writes go
through a bogus appctx. An allocation failure is needed to reach this,
either the appctx itself or the session/stream set up by spoe_init_appctx().

Let's simply clear ctx->spoe_appctx before releasing the applet context.

This was introduced in 3.1 by commit 07cf7769c ("MEDIUM: spoe: Directly
xfer NOTIFY frame when SPOE applet is created"). It must be backported to
3.1.

Reported-by: Claude (ANT-2026-KBZN81X2)
7 days agoBUG/MEDIUM: mux-fcgi: check the room left before appending the index
Olivier Houchard [Thu, 6 Aug 2026 07:21:38 +0000 (09:21 +0200)] 
BUG/MEDIUM: mux-fcgi: check the room left before appending the index

In fcgi_set_default_param(), when the decoded path ends with a '/' and the
fcgi-app declares an "index", params->scriptname is set to span the path
plus the index before both are appended to the trash chunk, and the return
value of these appends is ignored. If the chunk is full, nothing is written
but scriptname still points past its tail, so SCRIPT_NAME and
SCRIPT_FILENAME are encoded from memory located past the end of the chunk
and sent to the FastCGI application. The copy of the path a few lines above
was not checked either. Since the URI is copied into the chunk first and
the path copied again, a path larger than about a third of a buffer is
enough to reach this.

Let's check both appends and only publish the script name once its content
is really there.

Reported-by: Claude (ANT-2026-WPM4GZPQ)
7 days agoDEV: haring: bound the ring geometry to the size of the file
Willy Tarreau [Thu, 6 Aug 2026 16:33:19 +0000 (18:33 +0200)] 
DEV: haring: bound the ring geometry to the size of the file

Truncated ring files are not exceptional and the tool wasn't very robust
against them and could easily crash when trying to read past the end of
the mapping. Let's bound the sizes and offsets announced in the header
to the real file size. This also allows to relax some checks that would
reject some malformed files that are now handled as best effort ring v1.

It can be helpful to backport this to stable releases, though no issue
was really reported outside of the dev team.

Reported-by: Claude (ANT-2026-TCDNPXKV)
7 days agoBUG/MINOR: hlua: use a local buffer to format the socket addresses
Willy Tarreau [Thu, 6 Aug 2026 07:38:41 +0000 (09:38 +0200)] 
BUG/MINOR: hlua: use a local buffer to format the socket addresses

hlua_socket_info() formats the peer or local address of a Lua socket into
a function-static buffer shared by all threads. But there's no reason for
this buffer to be static, and it can cause inter-thread corruption. Let's
just drop the static modifier so that the address lies in the stack.

It can be backported to all versions since it's been there since 1.6
when sockets were introduced to Lua.

Reported-by: Claude (ANT-2026-W66XVDTK)
7 days agoBUG/MINOR: stats-file: reject tgid 0 when preloading shm objects
Willy Tarreau [Thu, 6 Aug 2026 07:25:13 +0000 (09:25 +0200)] 
BUG/MINOR: stats-file: reject tgid 0 when preloading shm objects

When preloading the shared memory stats file at startup,
shm_stats_file_preload() only checked that the tgid read from each
object was not greater than the number of configured thread groups,
but it forgot to also check for zero, which can correspond to an
entry in the process of being reused, and that would cause the
process to crash on startup.

Let's just also test 0. This should be backported to 3.3.

Reported-by: Claude (ANT-2026-71A0JC6Q)
7 days agoBUG/MEDIUM: hpack: encode long methods and schemes using the long form
Willy Tarreau [Thu, 6 Aug 2026 07:19:58 +0000 (09:19 +0200)] 
BUG/MEDIUM: hpack: encode long methods and schemes using the long form

hpack_encode_scheme() and hpack_encode_method() document that they're
limited to 127 chars since they only rely on hpack_encode_short_idx()
for literals, but the H2 mux doesn't check this. This results in only
the bytes modulo 256 being advertised on a backend connection. As such
the remaining bytes will be confused with other HPACK opcodes. Note
that in practice, the ability to exploit this to inject headers from a
front H1 connection is very limited due to the strict alphabet enabled
in schemes and methods which limits usable codes to literal headers with
indexing for absent pseudo-headers (i.e. no :scheme, :method, :path,
possibly one :authority when none is provided, but it must then match
the host), and whose value will be of 43 chars minimum.

The real impact in practice is to provoke protocol errors and cause
shared H2 backend connections to be abruptly closed in environments
using http-reuse always.

Let's simply make both encoders fall back to hpack_encode_long_idx()
for value larger than 127 bytes, like hpack_encode_path() does. The
bug has been present since 1.9 with commit 39c80ebff ("MINOR: hpack:
provide a function to encode an HTTP method").

Reported-by: Claude (ANT-2026-9SVV6W3Q)
This fix must be backported to all stable versions.

7 days agoREGTESTS: proxy: complete "del backend" test
Amaury Denoyelle [Thu, 6 Aug 2026 15:18:08 +0000 (17:18 +0200)] 
REGTESTS: proxy: complete "del backend" test

A recent fix was introduced for "del backend" on 3.4 when deleting the
first proxy declared in the configuration. This issue does not affect
the current development tree.

To ensure this case is safe, complete backend deletion reg-test with a
deletion on the first declared proxy. This could prevent any future
regression for this particular case.

This should be backported up to 3.4.

7 days agoBUG/MINOR: proxy: release watcher in "show default-server" on CLI abort
Amaury Denoyelle [Thu, 6 Aug 2026 14:19:32 +0000 (16:19 +0200)] 
BUG/MINOR: proxy: release watcher in "show default-server" on CLI abort

This patch is similar to the previous one. This time, it fixes the
command "show default-server", implemented in the current release
branch.

No need to backport.

7 days agoBUG/MINOR: proxy: release watcher in various commands on CLI abort
Amaury Denoyelle [Thu, 6 Aug 2026 14:11:53 +0000 (16:11 +0200)] 
BUG/MINOR: proxy: release watcher in various commands on CLI abort

Watcher has been added to protect several commands in src/proxy.c which
iterate over either the proxies or servers list against a runtime
deletion.

However, no io_release has been defined for such commands. This may
cause issue in case the command has yielded and is aborted before its
full completion, for example on client early disconnect. Related applet
context is freed while still subscribed into a proxy or server instance.
When this instance is deleted, a crash occurs on freed applet context
access.

To prevent this, define a dedicated io_release for the following
commands : "show servers state|conn", "show backend" and "show errors".
Any watcher is detached there.

Reported-by: Claude (ANT-2026-6Q55R82E)
This must be backported up to 3.4.

8 days agoMINOR: config: support "all" and "none" on "tune.defaults.purge"
Amaury Denoyelle [Thu, 6 Aug 2026 07:20:29 +0000 (09:20 +0200)] 
MINOR: config: support "all" and "none" on "tune.defaults.purge"

Extends "tune.defaults.purge" to support new arguments value. The values
"all" is equivalent to "proxies,servers".

The value "none" instructs to preserve all instances, which is similar
to the default behavior when the keyword is absent. It is added mainly
to simplify configuration by external APIs.

Both these values are exclusive : they cannot be mixed into the
comma-delimited list of tokens.

8 days ago[RELEASE] Released version 3.5-dev4 quic-interop flx04/quic-interop v3.5-dev4
Willy Tarreau [Thu, 6 Aug 2026 07:00:35 +0000 (09:00 +0200)] 
[RELEASE] Released version 3.5-dev4

Released version 3.5-dev4 with the following main changes :
    - CLEANUP: mux_quic: remove unused prototype
    - BUG/MEDIUM: proxy: protect "show errors" against backend deletion
    - MINOR: proxy: stress "show errors" handler
    - BUG/MINOR: haload: fix use-after-free upon updating task expiration
    - BUG/MINOR: haload: set default thread count to 1
    - BUG/MINOR: haload: fix display glitches by flushing stdout in summary
    - MINOR: haload: add rate limiting support using -R option
    - MINOR: log: use curproxy during config parsing
    - MINOR: config: define wrapper for proxies loop during check config
    - MINOR: log: convert list to standard doubly linked one
    - MINOR: sink: convert list to standard doubly linked one
    - MINOR: proxy: centralize proxies_list insert during config parsing
    - MINOR: proxy: define proxies_list iteration functions
    - MAJOR: proxy: convert proxies_list to a doubly linked struct list
    - OPTIM/MEDIUM: proxy: avoid main proxies list reordering on startup
    - CLEANUP: proxy/config: clean up after proxies list conversion
    - MINOR: proxy: rename proxies list to all_proxies
    - BUG/MEDIUM: ssl: Spell HAVE_VANILLA_OPENSSL correctly
    - BUG/MEDIUM: ssl: Handle non-application data record while splicing
    - MEDIUM: ssl: Add a way to rate-limit TLSv1.3 KeyUpdate
    - DOC: ssl: Document tune.ssl.keyupdate-rate-limit
    - BUG/MEDIUM: ssl: Put CO_ER_SSL_KEYUPDATE at the right place
    - BUILD: ssl: Do not use SSL3_MT_KEY_UPDATE, hardcode 24 instead
    - MINOR: server: rename global servers_list to all_servers
    - MINOR: server: do not return next server on srv_drop()
    - MINOR: proxy: define server list iteration functions
    - MAJOR: proxy: convert server list to a doubly linked struct list
    - OPTIM/MEDIUM: proxy/server: avoid server list reordering on startup
    - OPTIM: tools: keep a cache of recent localtime() and gmtime()
    - DEBUG: fd: catch access attempts to closed FDs
    - MINOR: halog: Add reusable function to extract the value of header captures
    - CLEANUP: halog: Clean up naming for variables related to `-hdr` processing
    - MINOR: halog: Add support filtering on header capture values using -hdr-match
    - REORG: h1-htx: Move h1 headers map in h1-htx
    - BUG/MEDIUM: mux-h1: Always adjust case for all outgoing headers as expected
    - MINOR: mux-h1: Lower the case for Sec-Websocket-* headers when manually added
    - MINOR: mux-h1: Use htx version to send default low-level errors
    - BUG/MINOR: haload: fix CPU topology detection by omitting forced "nbthread"
    - CLEANUP: haload: use <arg_thrd> instead of <global.nbthread> where applicable
    - BUG/MINOR: http-htx: fix the length moved when removing a header value
    - BUG/MEDIUM: http-fetch: don't parse a non-HTTP check buffer as an HTX message
    - BUG/MEDIUM: http-fetch: reject a negative capture id in capture.{req,res}.hdr
    - BUG/MINOR: http-fetch: fix a NULL channel dereference in smp_fetch_body()
    - BUG/MINOR: http: fix an out-of-bounds read in http_get_host_port() on empty host
    - BUG/MINOR: http-htx: check the trash allocation in http_scheme_based_normalize()
    - BUG/MINOR: h1: report the right error position on authority/host mismatch
    - BUG/MINOR: h2: don't use a block pointer to roll back a partial HTX conversion
    - BUG/MINOR: h3: don't use a block pointer to roll back a partial HTX conversion
    - BUG/MINOR: http-ana: fix a one-byte over-read in the client-side cookie parser
    - CLEANUP: htx: remove the unreachable "append_data" label in htx_reserve_max_data()
    - CLEANUP: flt-comp: remove a no-op http_remove_header() call
    - BUG/MINOR: http-act: fix a double free of the regex on a rule parsing error
    - BUG/MINOR: http-act: fix a double free of the map reference on a parsing error
    - BUG/MINOR: http-act: restore the response buffer state in the early-hint action
    - BUG/MINOR: http-act: work on a copy of the sample in del-headers-bin
    - BUG/MINOR: http-act: reject a negative capture id in the capture actions
    - CLEANUP: http-conv: index the captures array with hdr->index in the converters
    - BUG/MINOR: http-htx: check the strdup() of the "lf-string" http reply argument
    - BUG/MEDIUM: tools: make string encoding possible to fail instead of truncating
    - CLEANUP: http-conv: Remove useless enc_type init to ENC_QUERY
    - CLEANUP: http-conf: rename local trash variable
    - BUG/MINOR: htx: Perform raw copy for messages of same size in htx_copy_msg()
    - BUG/MINOR: htx: Transfer HTX_FL_EOM flag on success in htx_append_msg()
    - BUG/MINOR: http-rules: fix release of a failed "set-cookie-fmt" redirect rule
    - BUG/MINOR: slz: do not read past the end of the input around the match loop
    - CLEANUP: slz: fix the documented worst case size of flush() and finish()
    - BUG/MINOR: slz: use the exact switch cost for the last literals of a block
    - BUG/MEDIUM: slz: bound the bits wasted by the 9-bit literals
    - BUG/MINOR: slz: do not append a block to an already finished stream
    - BUG/MINOR: slz: fix the adler32 accumulators signedness on 32-bit
    - BUG/MINOR: slz: avoid undefined shifts when building the word byte by byte
    - CLEANUP: slz: clarify that the size promise applies to the stream, not to a call
    - BUG/MEDIUM: peers: check the available room before encoding dict values
    - BUG/MEDIUM: sample: reject the deprecated protobuf group wire types
    - BUG/MAJOR: ssl/ocsp: lock the OCSP response around reads in the stapling callback
    - MINOR: server: improve parsing error for server-template
    - BUG/MINOR: server: fix QUIC on server-template
    - BUG/MINOR: server: duplicate server alt_proto in srv_settings_cpy()
    - MINOR: server: ensure check-reuse-pool is init in srv_settings_init()
    - BUG/MINOR: server: fix check reuse-pool in srv_settings_cpy()
    - IMPORT: cebtree: private: fix the duplicate detection in the lookup shortcut
    - BUG/MINOR: cli: use the current argument to parse the FD spec in "show fd"
    - BUG/MINOR: cli: do not reject the "/<fd>" form of "show fd"
    - CLEANUP: haload: embed rate_task into hld_thr_info structure
    - CLEANUP: haload: factor out user scheduling into hld_usr_schedule()
    - BUG/MINOR: haload: fix rate limit bypass during stream errors
    - MEDIUM: fd: Remove fdinfo
    - MEDIUM: fd: Make it possible to have one fdtab per thread-group
    - MEDIUM: pollers: Allow one polled_mask per thread group
    - MEDIUM: pollers: Create the poller pipes before we create the thread
    - MEDIUM: listeners: Don't always balance connections across thread groups
    - MEDIUM: backend: Do not always allow takeover across thread groups
    - MEDIUM: listener: Properly handle unshared fd tables between tgroups
    - MEDIUM: cli: Transfer sockets with unshared file descriptor tables
    - MEDIUM: pollers: Only allow epoll when each tgroup has its fd table
    - MEDIUM: pipes: Have one pool of free pipes per thread group
    - MINOR: cli: Make "show fd" aware of per-thread-group FD tables
    - MINOR: debug: Report the current tgid in "debug dev fd"
    - MINOR: cli: Report the tgid along the FD in "show sess"
    - MEDIUM: dns: Stick the TCP nameserver tasks to the resolvers' thread
    - MEDIUM: server: Do not close other thread groups' connections at deinit
    - MEDIUM: resolvers: Do not close another thread group's socket at deinit
    - MEDIUM: quic: Do not use another thread group's listener FD
    - MINOR: connection: Do not retrieve src/dst on another thread group's FD
    - MEDIUM: fd: Add the tune.fd.tables option
    - BUILD: listener: Fix the build on platforms without MSG_CMSG_CLOEXEC
    - DOC: config: Document the tune.fd.tables option
    - BUG/MINOR: ech: propagate error from load_echkeys()
    - BUG/MINOR: ech: reject an ECH store with no usable private key
    - BUG/MEDIUM: counters: preserve shared.tg pointer on 'clear counters all'
    - MINOR: counters: add max-only reset helpers and use them for clear counters
    - MINOR: server: add 'clear counters server <backend>/<server>' CLI command
    - REGTESTS: stats: add test for 'clear counters server'
    - DOC: management: document 'clear counters server'
    - BUG/MEDIUM: filter: Disable auto-close on channel during TCP payload filtering
    - CLEANUP: haload: drop unused flags field from struct hld_url
    - BUG/MINOR: haload: fix stale global variables affecting URL allocations
    - MINOR: haload: support HTTP status code by version
    - DEV: patchbot: add an "O" filter to hide original lines without new notes
    - DEV: patchbot: retrieve the shared state on page load, with a timeout
    - DEV: patchbot: support passing the page settings in the URL fragment
    - OPTIM: pattern: try literal IPv6 parsing before DNS resolution in pat_parse_ip
    - OPTIM: tools/str2net: only duplicate the string when a slash is present
    - BUILD: tools: fix C23 incompatible strrchr usage
    - CLEANUP: server: remove wrong comments about server-template ID
    - MINOR: errors: further improve parsing error for server-template
    - BUG/MINOR: server: check strdup return value on server ID
    - MINOR: server: do not ignore errors during server-template init
    - BUG/MINOR: server: check strdup return on server-template ID generation
    - MINOR: server: detect name conflict earlier during parsing
    - MINOR: server: treat proxy server tree as without duplicate
    - BUG/MINOR: ech: fix label at end of compound statement
    - MINOR: ech: introduce an ech_store type and helpers in load_echkeys()
    - MEDIUM: ech: implement a lighter ECH feature for AWS-LC
    - CI: github: add USE_ECH=1 in OpenSSL and AWS-LC jobs
    - DOC: stop supporting OpenSSL version < 1.1.1
    - CI: github: remove OpenSSL 1.0.2 job
    - BUG/MINOR: proxy: fix default-server leak on post-parsing cleanup
    - MINOR: proxy: implement unpublished backend keyword
    - MINOR: server: define _srv_parse_from() for server "from" keyword
    - MINOR: server: implement "from none"
    - MINOR: proxy: keep default-server unless empty setting
    - MINOR: proxy: extend global tune.defaults.purge for default-server
    - MEDIUM: server: implement from be:
    - MINOR: server: set default-server id to NULL
    - MEDIUM: proxy: implement named default-server
    - MINOR: server: prevent name collision with a default-server
    - MEDIUM: server: implement "from srv:"
    - CLEANUP: xprt_quic: remove dead callbacks prepare_srv/destroy_srv
    - MINOR: sample: make the param converter support control characters
    - DOC: explain better that named defaults are preserved
    - DOC: better explain that default-server are now preserved
    - BUG/MINOR: proxy: fix "show backend"
    - MINOR: list: define watcher_is_attached()
    - MINOR: proxy: implement "show default-server"
    - MINOR: proxy: complete "add backend" reg-test
    - MINOR: proxy: implement "show defaults"

9 days agoMINOR: proxy: implement "show defaults"
Amaury Denoyelle [Tue, 4 Aug 2026 15:33:24 +0000 (17:33 +0200)] 
MINOR: proxy: implement "show defaults"

Implement a new command "show defaults" whose purpose is to list the
existing named defaults section. This may be needed when adding a new
backend via the CLI.

For now, only the names of the defaults instances are listed. In the
future, it could be useful to have at least some details about their
configuration.

Command does not take any argument and is implemented via a single
io_handler. No need for a watcher as iteration is performed over
defaults instances which cannot be removed at runtime.

9 days agoMINOR: proxy: complete "add backend" reg-test
Amaury Denoyelle [Wed, 5 Aug 2026 08:37:59 +0000 (10:37 +0200)] 
MINOR: proxy: complete "add backend" reg-test

Add new tests for "add backend" command, most notably a check on "mode"
argument in case defaults section does not define an explicit mode.

9 days agoMINOR: proxy: implement "show default-server"
Amaury Denoyelle [Thu, 30 Jul 2026 12:42:43 +0000 (14:42 +0200)] 
MINOR: proxy: implement "show default-server"

Define a new CLI command "show default-server". This lists all the
default-server instances, both unnamed and named. By default, all
backends are displayed. A single instance only can be requested.

Command is protected against runtime backend deletion via a watcher. It
is only attached for a full iteration. If only a single instance is
requested, watcher_is_attached() will detects this and interrupt the for
loop.

9 days agoMINOR: list: define watcher_is_attached()
Amaury Denoyelle [Tue, 4 Aug 2026 14:54:19 +0000 (16:54 +0200)] 
MINOR: list: define watcher_is_attached()

Define an utility function to report if a watcher is currently attached
on a target. This will notably be used to implement a new CLI command
"show default-server".

9 days agoBUG/MINOR: proxy: fix "show backend"
Amaury Denoyelle [Tue, 4 Aug 2026 14:17:39 +0000 (16:17 +0200)] 
BUG/MINOR: proxy: fix "show backend"

During the conversion for the main proxies list, "show backend" was
broken as no entry would be displayed. This patch restores the iteration
over the backends list.

The exact patch which introduces the regression is the following one.

  164d05706132107a405d23663dbca29916910f55
  MINOR: proxy: define proxies_list iteration functions

No need to backport unless the above patch is.

9 days agoDOC: better explain that default-server are now preserved
Amaury Denoyelle [Wed, 5 Aug 2026 09:24:58 +0000 (11:24 +0200)] 
DOC: better explain that default-server are now preserved

Complete default-server documentation by mentionning that default-server
unnamed and named are now preserved after configuration parsing.

This is already mentionned in the related keyword "tune.defaults.purge".
However, as this is a major change, it is necessary to remind it
directly in default-server section.

No need to backport.

9 days agoDOC: explain better that named defaults are preserved
Amaury Denoyelle [Wed, 5 Aug 2026 09:19:33 +0000 (11:19 +0200)] 
DOC: explain better that named defaults are preserved

Named defaults section are now kept after configuration by default. This
behavior change has already been explained in "tune.defaults.purge".

Mention again this change directly in "defaults" documentation. This is
the main entry point documentation for "defaults" sections, so the
change is now more visible to users.

This should be backported up to 3.4.

10 days agoMINOR: sample: make the param converter support control characters
Willy Tarreau [Tue, 4 Aug 2026 09:04:06 +0000 (11:04 +0200)] 
MINOR: sample: make the param converter support control characters

Sometimes it can be convenient to support delimiting of the param()
converter using control characters that can be found in some request
bodies. Since the converter only supports a single character, we can
easily make an exception for "0xHH". That's what this patch does. The
reg-test was updated to include one 0x26.

10 days agoCLEANUP: xprt_quic: remove dead callbacks prepare_srv/destroy_srv
Amaury Denoyelle [Tue, 28 Jul 2026 17:08:39 +0000 (19:08 +0200)] 
CLEANUP: xprt_quic: remove dead callbacks prepare_srv/destroy_srv

QUIC xprt defines prepare_srv callback. The main objective of this
callback is to setup server XPRT to QUIC.

This should be called once the server is fully configured, with related
code blocks in proxy_finalize() and cli_parse_add_server(). However,
this is in fact dead code as prepare_srv is in fact called through
XPRT_SSL. This is still functional though because SSL and QUIC share the
same prepare_srv, so this issue is not visible. This is the same
situation for destroy_srv callback.

This patch removes the dead code to ensure there is no ambiguity here.
There is still a design issue which should be fixed later as it's not
expected for QUIC code to rely on XPRT_SSL layer.

10 days agoMEDIUM: server: implement "from srv:"
Amaury Denoyelle [Thu, 9 Jul 2026 09:41:50 +0000 (11:41 +0200)] 
MEDIUM: server: implement "from srv:"

Implement "srv:" notation for the "from" server keyword. This allows to
specify a server or default-server by its name. It may be optionnaly
prefixed by a backend name using a slash separator. If this is not the
case, lookup is performed under the backend where the current newly
created server instance is attached.

This is implemented by extending _srv_parse_from() to support "from
srv:" argument value. An internal function lookup_srv_be_arg() is
defined to parse the argument of the form "[<be>/]<srv>". This is
similar to already existing lookup functions such as cli_find_server(),
however there is some differences which forces to have duplicated code
for the moment.

10 days agoMINOR: server: prevent name collision with a default-server
Amaury Denoyelle [Fri, 31 Jul 2026 14:23:08 +0000 (16:23 +0200)] 
MINOR: server: prevent name collision with a default-server

The previous patch has introduced the support for named default-server.
It is not possible though to declare a default-server with a name if it
collides with an already existing server instance.

This patch implements a similar check but on the other side : it ensures
that a newly created server instance does not collide with an already
existing default-server.

To implement this, a new function server_find_by_name2() is defined. It
is similar to server_find_by_name() except it also lookup in the named
default-server tree. Checks are performed on several places :
* _srv_parse_init() for a server parsing
* _srv_parse_tmpl_init() for a server-template parsing
* srv_update_server_name() for the CLI command "set server name"
* cli_parse_add_server() for the CLI command "add server"

10 days agoMEDIUM: proxy: implement named default-server
Amaury Denoyelle [Thu, 9 Jul 2026 08:41:59 +0000 (10:41 +0200)] 
MEDIUM: proxy: implement named default-server

Implement the support for named default-server. In a single backend, it
is now possible to define different default-server instances identified
by a name. Along to them, it's still possible to use the anonymous
default-server.

To define a named default-server, a new "name" argument can be used just
after the "default-server" token. It is positional to ensure extra
server settings are always set after it. This is necessary to continue
to allow to define a default-server on multiple lines, concatenating the
settings with the previous line.

Named default-server are stored in a new dedicated compact tree
<defsrv_by_name> in proxy struct.

Name must not conflict with server instances already defined in the same
backend. This is necessary for the future implementation of name
addressing on both servers and default-servers without having to specify
the lookup list.

10 days agoMINOR: server: set default-server id to NULL
Amaury Denoyelle [Thu, 9 Jul 2026 10:00:07 +0000 (12:00 +0200)] 
MINOR: server: set default-server id to NULL

Previously, default-server <id> was set to "default-server" static
string. With this patch, it is now set to NULL.

This has no noticeable impact, including on the configuration error
messages, as default-server ID is not used there. This is because
register_parsing_obj() is only used for standard server and
server-template instances, but not for default-server.

A nice side-effect of this change is that it's possible to free server
<id> member in srv_free_params(), which is less error prone (even more
with named default server future implementation).

This is a prealable for the future named default-server feature, as it
could conflict with a default-server explicitely named "default-server".
Also, for named default-server, it will be necessary to free <id>
member. Thus it's better to remove a static string reference to prevent
any issue with free() usage.

10 days agoMEDIUM: server: implement from be:
Amaury Denoyelle [Thu, 30 Jul 2026 13:25:52 +0000 (15:25 +0200)] 
MEDIUM: server: implement from be:

Implement "from be:" syntax. This instructs that the server should reuse
parameters from the default-server already defined in the same proxy
instance.

This behavior is already the current one in the configuration parser,
thus there is no visible change here. The main usage is on "add server"
command, which previously always ignored a default-server instance.

This is implemented by extending _srv_parse_from() to parse "be:"
argument value. If found, the designated server is returned via <from>
output parameter.

10 days agoMINOR: proxy: extend global tune.defaults.purge for default-server
Amaury Denoyelle [Wed, 8 Jul 2026 08:30:51 +0000 (10:30 +0200)] 
MINOR: proxy: extend global tune.defaults.purge for default-server

Previous patch changes behavior for default-server. They are now
preserved after configuration during the whole process lifetime as
dynamic servers may need them. A default-server is still purge though if
it does not contain any particular setting.

This relation between dynamic and default-server is similar to the one
between dynamic backends and named default proxies sections, which are
also kept by default at runtime.

This patch extends tune.defaults.purge keyword which was previously used
to force cleanup of named defaults section on post parsing. This now
support an extra argument to instruct the type of elements to remove :
supported values are "proxies" for named defaults sections and "servers"
for default-server.

Without any argument, the option only forces clean up of named defaults
sections. This is the simplest method to preserve backward compatibility
with previous releases.

This new setting requires a dedicated code block for default-server
purgeing in check_config_validity(). This cannot be performed during
post-section parsing as this is a global setting which can be defined
later.

10 days agoMINOR: proxy: keep default-server unless empty setting
Amaury Denoyelle [Fri, 17 Jul 2026 08:19:36 +0000 (10:19 +0200)] 
MINOR: proxy: keep default-server unless empty setting

Previously, default-server were removed as soon as a proxy section
parsing was over. This patch changes this behavior to now keep
default-server during haproxy runtime. This will allow to reuse them for
dynamic servers, which will be later implemented via "from" keyword as a
distinct feature.

Default-server may consume a noticeable amount of memory, so it can
still be desirable to clean up these elements on post-parsing. Thus, if
a default-server is reset, it will still be purged on section post
parsing. This can be achived by using a "default-server from none" final
line.

To detect if a default-server should be preserved, a new flag
SRV_F_UMODIFIED has been defined. Such flag is set as soon as a server
keyword has been parsed. It is reset when srv_settings_init() is used.
Thus, if this flag is present, default-server purge on post parsing is
skipped.

10 days agoMINOR: server: implement "from none"
Amaury Denoyelle [Wed, 29 Jul 2026 13:42:16 +0000 (15:42 +0200)] 
MINOR: server: implement "from none"

Implement "from none" on a server line. This instructs to not preset
server settings via another instance, instead relying on documented
default values.

This is useful to change the default behavior for static servers to
prevent them from reusing a default-server instance. A secondary usage
of this value is on a default-server. This reset the default-server to
the documented default settings.

This is implemented by extending _srv_parse_from() to parse "none"
value. In this case, <from> output return is set to NULL, which will
cause the server to be initialized via srv_settings_init().

10 days agoMINOR: server: define _srv_parse_from() for server "from" keyword
Amaury Denoyelle [Wed, 8 Jul 2026 08:05:21 +0000 (10:05 +0200)] 
MINOR: server: define _srv_parse_from() for server "from" keyword

Prepare the support for a new server keyword "from".

This keyword has special constraints : it is a positional one as it can
only be specified once, after the server address and before the other
parameters.

The purpose of this keyword will be to define server settings
inheritance outside of the default-server of the current backend. It
will also be useful for dynamic servers which currently do not inherit
from a default-server.

10 days agoMINOR: proxy: implement unpublished backend keyword
Amaury Denoyelle [Mon, 6 Jul 2026 13:51:41 +0000 (15:51 +0200)] 
MINOR: proxy: implement unpublished backend keyword

Add a new "unpublished" proxy keyword. This allows to start a backend
instance in unpublished state. This can be reverted via "publish
backend" on the CLI.

This keyword is restricted to backend and listen sections.

This patch is a simple feature implementation, however it can be
considered as a must-have when using dynamic backends. As such, it
should be backported up to 3.4.

10 days agoBUG/MINOR: proxy: fix default-server leak on post-parsing cleanup
Amaury Denoyelle [Fri, 17 Jul 2026 14:12:42 +0000 (16:12 +0200)] 
BUG/MINOR: proxy: fix default-server leak on post-parsing cleanup

A proxy with BE capabilities may define a default-server instance. This
instance is freed when config parser switch to another section. Beside
the server object, <conf.file> is also freed.

This causes a memleak if the default-server configuration is expanded
with keywords triggering dynamic allocations. For example, this is the
case if a cookie name is defined.

To fix this, use srv_free_params() on default-server deletion. This
function is designed to work both for server and default-server. It
ensures that every dynamic elements in it are freed. Only <id> member is
not freed by srv_free_params() : this is expected as it is a static
value for default-server instances.

Note that srv_free_params() is already used when default-server is freed
in deinit_proxy() since the following patch. However, post-parsing
cleanup has been added after it without reusing srv_free_params().

  899b547840c340cd129c3831d3858d8d2e5b452a
  BUG/MINOR: proxy/server: free default-server on deinit

This should be backported up to 2.8. Prior to it, srv_free_params() does
not exists, so it cannot be picked as is.

10 days agoCI: github: remove OpenSSL 1.0.2 job
William Lallemand [Mon, 3 Aug 2026 12:40:16 +0000 (14:40 +0200)] 
CI: github: remove OpenSSL 1.0.2 job

We don't support anymore versions this old, minimum version supported is
1.1.1.

10 days agoDOC: stop supporting OpenSSL version < 1.1.1
William Lallemand [Mon, 3 Aug 2026 12:38:34 +0000 (14:38 +0200)] 
DOC: stop supporting OpenSSL version < 1.1.1

Remove versions of OpenSSL before 1.1.1 from the documentation, 1.1.1 is
the minimal requirement.

10 days agoCI: github: add USE_ECH=1 in OpenSSL and AWS-LC jobs
William Lallemand [Mon, 3 Aug 2026 10:01:22 +0000 (12:01 +0200)] 
CI: github: add USE_ECH=1 in OpenSSL and AWS-LC jobs

Add USE_ECH=1 in standard jobs when supported.

AWS-LC and OpenSSL > 4.0 supports ECH.

Remove the specific openssl-ech job.

10 days agoMEDIUM: ech: implement a lighter ECH feature for AWS-LC
William Lallemand [Fri, 31 Jul 2026 15:59:46 +0000 (15:59 +0000)] 
MEDIUM: ech: implement a lighter ECH feature for AWS-LC

This patch implements ECH with AWS-LC. AWS-LC supports a different ECH
API than OpenSSL 4.0.

AWS-LC does not implement an API to load a PEM ECH file, the ECHCONFIG
section is parsed manually using PEM_read_bio() to feed the SSL_ECH_KEYS
object.

Runtime ECH store management ('show/add/set/del ssl ech') and ECH
status/outer-SNI reporting  are disabled under AWS-LC for now.

Should fix issue #3333.

10 days agoMINOR: ech: introduce an ech_store type and helpers in load_echkeys()
William Lallemand [Fri, 31 Jul 2026 15:26:21 +0000 (15:26 +0000)] 
MINOR: ech: introduce an ech_store type and helpers in load_echkeys()

Alias OSSL_ECHSTORE as ech_store, and wrap OSSL_ECHSTORE_new(),
OSSL_ECHSTORE_free(), SSL_CTX_set1_echstore() and the per-file PEM
loading loop behind ech_store_new(), ech_store_free(),
ech_store_set_ctx() and ech_store_load_file(). Use them in
load_echkeys() instead of the OpenSSL calls directly.

ech_store_load_file() no longer reads strerror(errno) to explain a
BIO_new_file() failure: that function records the fopen() failure
reason in the crypto library's error queue, not in errno.
load_echkeys() already drains that queue into *err on any failure, so
just keeping the message filename-only here is enough to get the real
reason appended.

This is pure preparation with no functional change otherwise: a
following commit will give ech_store and these four helpers an
AWS-LC-specific body, so that load_echkeys() can be shared between the
two SSL libraries.

10 days agoBUG/MINOR: ech: fix label at end of compound statement
William Lallemand [Mon, 3 Aug 2026 12:33:06 +0000 (12:33 +0000)] 
BUG/MINOR: ech: fix label at end of compound statement

The "ignore_entry" label used to skip non ".ech" directory entries in
load_echkeys() was placed right before the closing brace of the "for"
loop, with no statement attached to it. A label with no following
statement is only valid since C23, so strict compilers reject it:

  src/ech.c:96:2: error: label at end of compound statement is a C23
  extension [-Werror,-Wc23-extensions]

Add a null statement after the label.

11 days agoMINOR: server: treat proxy server tree as without duplicate
Amaury Denoyelle [Mon, 3 Aug 2026 07:37:55 +0000 (09:37 +0200)] 
MINOR: server: treat proxy server tree as without duplicate

Previous commit changes the unicity checks on server name inside a
proxy. This is now done directly during the parsing, prior to the tree
insertion.

Thus, the tree can now be considered without duplicate. Replace cebis_*
function by their equivalent cebuis_*. Also, insertion is now performed
via utility function _srv_register_name() which contains a new BUG_ON()
to ensure server name unicity principle is not violated.

11 days agoMINOR: server: detect name conflict earlier during parsing
Amaury Denoyelle [Fri, 31 Jul 2026 14:00:32 +0000 (16:00 +0200)] 
MINOR: server: detect name conflict earlier during parsing

Since 3.3, server name unicity is strictly enforced, even in case of
different numerical IDs.

  d7fad1320eae7efba98135628d0c04af6e15495e
  MAJOR: cfgparse: make sure server names are unique within a backend

This was implemented as a post parsing check via
check_config_validity(). Now, this is performed directly during the
parsing of each new server instances. The main benefit is that it is a
much clearer operation.

Multiple checks are required : in _srv_parse_init() for standard server
and in _srv_parse_tmpl_init() for server-template. Note that the latter
check requires the following patch to ensure errors are properly
reported when duplicating a server-template instance.

  commit 467440a34f81c635ccb33d434328799b027cbf03
  MINOR: server: do not ignore errors during server-template init

11 days agoBUG/MINOR: server: check strdup return on server-template ID generation
Amaury Denoyelle [Fri, 31 Jul 2026 13:45:28 +0000 (15:45 +0200)] 
BUG/MINOR: server: check strdup return on server-template ID generation

Check strdup() return value when a server-template prefix is expanded
into the final server name. Now, an alert is reported in case of error
and server parsing is interrupted.

Along with this change, the server prefix expansion function has been
renamed and its documentation adjusted.

This could be backported to all stable releases. However, this depends
on a previous patch which prevents ignoring of errors during
server-template generation. As this is a minor fix which is only useful
after memory allocation failure, it is probably better to not pick them.
If backporting is considered necessary, the following patch is required
first.

  commit 467440a34f81c635ccb33d434328799b027cbf03
  MINOR: server: do not ignore errors during server-template init

11 days agoMINOR: server: do not ignore errors during server-template init
Amaury Denoyelle [Thu, 30 Jul 2026 16:16:11 +0000 (18:16 +0200)] 
MINOR: server: do not ignore errors during server-template init

_srv_parse_tmpl_init() is used to duplicate a server several times
according to its template declaration. If an error was encountered
during the duplication of one of these instance, the function was
interrupted. However, no error/warning message is displayed and the
error is silently ignored by caller parse_server().

The objective of this patch is to ensure an error occuring during a
server template allocation is now properly reported. First, an alert is
reported for every failure cases. Also, _srv_parse_tmpl_init() return
type is changed to use an error code. This is used to interrupt server
parsing.

The real benefit of this patch is still pretty limited. The only real
case of error which can happen is an allocation memory issue, likely
detected by a later operation. Also, server_parse_exprs() is also
faillible. However, this was already invoked for the first server
instance via _srv_parse_finalize(). Thus it is unlikely that an error is
reported for the duplicated instances during duplication. Despite this,
the current patch is an improvment as it will allow to extend
_srv_parse_tmpl_init(), for example to check for server name duplication
during parsing on every template instances.

11 days agoBUG/MINOR: server: check strdup return value on server ID
Amaury Denoyelle [Fri, 31 Jul 2026 09:34:17 +0000 (11:34 +0200)] 
BUG/MINOR: server: check strdup return value on server ID

Add missing checks on strdup() return value in _srv_parse_init() when
generating server ID or server-template tmpl_info.prefix.

This must be backported to all stable releases.

11 days agoMINOR: errors: further improve parsing error for server-template
Amaury Denoyelle [Fri, 31 Jul 2026 12:09:28 +0000 (14:09 +0200)] 
MINOR: errors: further improve parsing error for server-template

The following patch improved error messages for server-template lines.
This properly used <tmpl_info.prefix> instead of the <id> field for
them, which prevents to display an empty string.

  94024a60dc197b26a13751a6a0eaa3071f023cfa
  MINOR: server: improve parsing error for server-template

This patch relied on server-template instance detection by checking if
<id> field is NULL. However, this is not appropriate once a
server-template has its ID assigned during _srv_parse_tmpl_init(). The
expanded name is displayed, which can be a misleading information as
this is not present directly in the configuration.

This patch fixes this by now detecting a server-template by checking if
its <tmpl_info.prefix> is non NULL. Thus, a server-template remains
detected as such even after its ID has been generating. Its template
name is used on error messages instead of the expanded prefix.

11 days agoCLEANUP: server: remove wrong comments about server-template ID
Amaury Denoyelle [Fri, 31 Jul 2026 09:26:55 +0000 (11:26 +0200)] 
CLEANUP: server: remove wrong comments about server-template ID

This misleading comment suggests that server <id> field is set to the
prefix for a server-template. In fact, this field remains NULL and
prefix is set to <tmpl_info.prefix>.

11 days agoBUILD: tools: fix C23 incompatible strrchr usage
Amaury Denoyelle [Mon, 3 Aug 2026 09:00:19 +0000 (11:00 +0200)] 
BUILD: tools: fix C23 incompatible strrchr usage

Fix the following warning by ensuring strrchr() return type is set to
'const char *' if its input is declared as such.

src/tools.c: In function ‘str2net’:
src/tools.c:1618:16: error: assignment discards ‘const’ qualifier from pointer target type [-Werror=discarded-qualifiers]
 1618 |         if ((c = strrchr(str, '/')) != NULL) {
      |                ^

This build warning was introduced by the following patch.

  9bc6c4d18c2ea9836c171977c1c7c2979ecbb132
  OPTIM: tools/str2net: only duplicate the string when a slash is present

This change is required since C23, as strrchr(), along other functions
which accept a const-qualified pointer without returning a const type
are now declared as macro with generics to preserve the const qualifier.

See https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3020.pdf for
details.

No need to backport, unless the above patch is.

11 days agoOPTIM: tools/str2net: only duplicate the string when a slash is present
Willy Tarreau [Mon, 3 Aug 2026 07:52:58 +0000 (09:52 +0200)] 
OPTIM: tools/str2net: only duplicate the string when a slash is present

The str2net() and str62net() functions systematically strdup() the
input pattern before parsing it, while this is only needed when a '/'
is present. Given that this '/' is already looked up, better move the
allocation inside that block so that no allocation is performed when
none is present.

Doing so saves ~5% load time on plain IPv4 addresses and ~8% on plain
IPv6 addresses, while not changing anything for addresses with masks.

11 days agoOPTIM: pattern: try literal IPv6 parsing before DNS resolution in pat_parse_ip
Andre Brait [Wed, 22 Jul 2026 20:53:17 +0000 (22:53 +0200)] 
OPTIM: pattern: try literal IPv6 parsing before DNS resolution in pat_parse_ip

pat_parse_ip() calls str2net() with DNS resolution enabled before trying
str62net(), so every IPv6 entry of a pattern file pays a blocking DNS
resolution attempt (which necessarily fails) before being parsed as an
IPv6 literal. On a configuration loading a 20k-entry IPv6 acl file on a
host with a resolv.conf search domain, this added two DNS round trips
per entry and made "haproxy -c" take 14 seconds instead of about 1.

Try both literal parsers first, then fall back to the resolving call of
str2net(). An IPv6 literal can never be a valid host name (colons are
not permitted in DNS labels) and host names keep resolving exactly as
before, so no working configuration changes behaviour.

Diagnosed on pfSense, where large pfBlockerNG IP block lists are loaded
through "acl ... -f" files:
https://github.com/pfsense/FreeBSD-ports/pull/1450

This patch was authored with Claude Fable 5 and reviewed by GPT-5.6 Sol.

13 days agoDEV: patchbot: support passing the page settings in the URL fragment
Willy Tarreau [Fri, 31 Jul 2026 17:54:47 +0000 (19:54 +0200)] 
DEV: patchbot: support passing the page settings in the URL fragment

It's difficult to point a coworker to a set of patches resulting from a
selection. Some browser-based tools such as slides pass information in
the URL as a fragment that the code in the browser is able to retrieve.
Conversely it's possible to update it without causing a page reload
using history.replaceState(). This means that we can instantly update
the URL bar with extra params passed as fragments as we click on the
page, so it's possible to copy-paste a current state so that someone
else gets the same view (without local edits).

13 days agoDEV: patchbot: retrieve the shared state on page load, with a timeout
Willy Tarreau [Wed, 29 Jul 2026 16:52:29 +0000 (18:52 +0200)] 
DEV: patchbot: retrieve the shared state on page load, with a timeout

I got trapped a few times reviewing patches without clicking the
"Get Updates" button (not yet being used to it). After checking with
AI, it's not complicate to implement a fetch timeout, so let's do that
directly on page load. It also means that a reload will automatically
retrieve updates. In case of failure (offline, server dead), after 2s
the fetch is abandonned and the page can be used normally. For browsers
that wouldn't implement the timeout, it would just act as if the button
had been clicked, so likely that pressing Esc would stop it. Tested in
firefox 140.

13 days agoDEV: patchbot: add an "O" filter to hide original lines without new notes
Willy Tarreau [Wed, 29 Jul 2026 16:52:29 +0000 (18:52 +0200)] 
DEV: patchbot: add an "O" filter to hide original lines without new notes

Now that we can save notes, it appears that it becomes convenient to
leave new backport notes there, e.g. flag the already backported patch
as "wait" and add a note "left in 3.2 for now, sensitive backport". But
rediscovering these notes later happens to be difficult.

This patch adds a new checkbox with "O" for "original notes", which
is checked by default, and which when unchecked allows to only list
lines with edited notes.

An initial test was made to also consider a changed state in this reduced
listing but too many are changed (~10%) and they add pollution for no
reason (many Y<->N). Let's just keep lines with extra notes.

2 weeks agoMINOR: haload: support HTTP status code by version
Frederic Lecaille [Fri, 31 Jul 2026 09:58:58 +0000 (11:58 +0200)] 
MINOR: haload: support HTTP status code by version

Add support for tracking and displaying HTTP status code distribution
broken down by HTTP version (h0, h1, h2, h3) via the new -hsv option.
Building upon -hs, -hsv retains the global status code distribution and
appends additional columns detailed by HTTP version.

Each URL configuration (struct hld_url_cfg) stores its targeted HTTP version in
->http_ver. Thread statistics track status code counts per HTTP
version in thrs_info.vtot_sc[][], which are then aggregated and displayed
alongside global status codes in hld_summary().

Also remove deprecated --show-status-codes, which is a too long long option,
in favor of -hs / -hsv.

2 weeks agoBUG/MINOR: haload: fix stale global variables affecting URL allocations
Frederic Lecaille [Thu, 30 Jul 2026 12:43:19 +0000 (14:43 +0200)] 
BUG/MINOR: haload: fix stale global variables affecting URL allocations

hld_alloc_url() relied on global variables (alpn, h2c) set during
command-line parsing. When processing multiple URLs, values retained in
these globals by previous URLs persisted and affected subsequent
allocations. This corrupted the configuration of newly allocated
URLs—especially QUIC URLs, which require "h3" ALPN and no h2c—causing
connection failures.

Fix this by passing is_quic, ALPN, and h2c explicitly as parameters
from haproxy_init_args() to hld_alloc_url(), ensuring each URL is
constructed with its appropriate parameters instead of inheriting
stale global state.

2 weeks agoCLEANUP: haload: drop unused flags field from struct hld_url
Frederic Lecaille [Thu, 30 Jul 2026 09:10:12 +0000 (11:10 +0200)] 
CLEANUP: haload: drop unused flags field from struct hld_url

Remove the dead flags member from struct hld_url and clean up its
unused initialization in hld_new_usr().

2 weeks agoBUG/MEDIUM: filter: Disable auto-close on channel during TCP payload filtering
Christopher Faulet [Thu, 30 Jul 2026 14:20:44 +0000 (16:20 +0200)] 
BUG/MEDIUM: filter: Disable auto-close on channel during TCP payload filtering

While a filter is registered on the payload filter in TCP, it is important
to disable the auto-cloes on the corresponding channel. It is already
performed in HTTP. It is only an issue when the ->tcp_payload callback
function is defined.

Without this patch, the data blocked in a filter may be lost when the
producer shut its connection. In that case, we must take care to wait the
filter flushes pending data.

This patch should fix the issue #3457. It must be backported to all
supported versions.

2 weeks agoDOC: management: document 'clear counters server'
Alexander Stephan [Mon, 6 Jul 2026 09:26:58 +0000 (09:26 +0000)] 
DOC: management: document 'clear counters server'

Add a section for the new 'clear counters server <backend>/<server>
[force]' CLI command in doc/management.txt, alongside 'clear counters'
and 'clear counters all'.

The entry documents the admin-only permission level (it clears
accumulated counters, like 'clear counters all') and the fact that both
accumulated counters and max values are reset, along with the clearable
module-registered extra counters, while the server's runtime state
(address, weight, admin state) and health checks are left untouched. It
describes the optional 'force' argument required to clear counters
stored in a shared-memory stats file, and calls out the Kubernetes
slot-recycle use case that motivated the command (per-entity
attribution after 'set server <b>/<s> name').

2 weeks agoREGTESTS: stats: add test for 'clear counters server'
Alexander Stephan [Mon, 6 Jul 2026 09:22:30 +0000 (09:22 +0000)] 
REGTESTS: stats: add test for 'clear counters server'

Cover the new 'clear counters server <backend>/<server>' CLI command:

  - error cases:
      * unknown backend
      * unknown server
  - success case: drive two requests pinned to a single server (the
    second server is a backup, so it stays idle), verify via 'show stat'
    that the served server accumulated sessions while the backup stayed
    at zero, clear only the served server's counters, then verify its
    cumulative sessions dropped back to zero while the backup is still
    untouched.
  - the optional 'force' argument is accepted (a no-op override here,
    since these process-local counters are not backed by a shared-memory
    stats file) and the command is discoverable via 'help'.

The success path also implicitly exercises the counters_be_reset()
helper: if the reset were unsafe against the shared.tg pointer, the
vtest run would segfault on the next scheduled scrape or health check.

2 weeks agoMINOR: server: add 'clear counters server <backend>/<server>' CLI command
Alexander Stephan [Mon, 6 Jul 2026 09:20:48 +0000 (09:20 +0000)] 
MINOR: server: add 'clear counters server <backend>/<server>' CLI command

Add a CLI command to reset the statistics counters of a single server:

  clear counters server <backend>/<server> [force]

Motivation: 'clear counters all' resets counters across every proxy and
server in the process, which is too blunt for common operational needs.
In particular, when a server slot is being reused to represent a
different logical entity (e.g. a different Kubernetes pod occupying the
same slot after 'set server <b>/<s> name'), the operator needs per-slot
counter attribution and cannot afford to wipe the entire process' stats.

The command requires ACCESS_LVL_ADMIN, like 'clear counters all'. It
resets cumulative counters (not just the max gauges cleared by the base
'clear counters'), so restricting it to admin prevents an operator-level
socket from hiding accumulated activity. Like 'clear counters' / 'clear
counters all', it is not gated by the server's administrative state: the
reset only zeroes counter values and does not touch the server object or
its runtime state, so it is safe to issue on a live server (a concurrent
counter increment races on a value exactly as it already does for 'clear
counters all').

When the server's counters are registered in a shared-memory stats file
object (COUNTERS_SHARED_F_LOCAL not set), clearing them breaks the
monotonicity that monitoring tools consuming the shared stats rely on
and affects every process attached to the object. Such a clear is
therefore refused unless the optional 'force' argument is given.

The reset covers both the native server counters, via counters_be_reset()
introduced earlier in this series (which safely zeroes the per-tgroup
accumulated counter contents while preserving the shared.tg pointer
array), and the module-registered extra counters, via the new
srv_stats_clear_extra_counters() helper.

Note on the dispatch: 'clear counters' is a two-word CLI keyword, and
cli_find_kw() matches the first keyword whose tokens are all consumed,
so a separate three-word 'clear counters server' keyword would be
shadowed (or would shadow) depending on registration order. To avoid
that ambiguity the new sub-command is dispatched from within
cli_parse_clear_counters() when args[2] == "server", mirroring how the
existing "all" argument is handled. The per-server worker lives in
server.c (cli_clear_counters_server()) where the server lookup and
lock helpers are available.

2 weeks agoMINOR: counters: add max-only reset helpers and use them for clear counters
Alexander Stephan [Thu, 16 Jul 2026 12:40:51 +0000 (12:40 +0000)] 
MINOR: counters: add max-only reset helpers and use them for clear counters

The plain "clear counters" command (OPER level) resets only the max/peak
gauges of each object's counters, leaving cumulative counters intact,
while "clear counters all" (ADMIN) performs a full reset via the
counters_{fe,be}_reset() helpers introduced earlier in this series.

The max-only reset was open-coded inline in proxy_stats_clear_counters()
with a hand-maintained list of fields, duplicated across the backend,
server and listener branches. This is fragile: a new max gauge added to
struct {fe,be}_counters can silently be forgotten here.

Factor the max-only reset into counters_fe_reset_max() and
counters_be_reset_max(), living next to the existing reset helpers, and
call them from proxy_stats_clear_counters(). Unlike the full-reset
helpers these cannot use a blanket memset because the max fields are
interleaved with live cumulative fields, so the gauges are zeroed
individually.

While consolidating, the two open-coded lists turned out to disagree:
the backend branch cleared conn_max / cps_max / rps_max but not
cur_sess_max, while the server branch cleared cur_sess_max but none of
the former. Both used the same struct be_counters, so these were latent
gaps rather than intentional differences. counters_be_reset_max() now
clears the union of all peak gauges, so plain "clear counters"
consistently resets every max gauge for both backends and servers.

No functional change for the full-reset ("all") path.

2 weeks agoBUG/MEDIUM: counters: preserve shared.tg pointer on 'clear counters all'
Alexander Stephan [Fri, 3 Jul 2026 08:35:47 +0000 (08:35 +0000)] 
BUG/MEDIUM: counters: preserve shared.tg pointer on 'clear counters all'

proxy_stats_clear_counters() did a blanket memset() on the be_counters /
fe_counters / listener counter structs when clearing all counters. This
zeroed the embedded shared.tg pointer array, which every hot path
dereferences with no NULL check (for example
_HA_ATOMIC_INC(&srv->counters.shared.tg[tgid-1]->cum_lbconn) in
process_srv_queue()). After 'clear counters all' the next request on
that server would segfault.

The memset pattern predates the switch to per-thread-group shared
counters (commit 5495c8844 "MEDIUM: counters: Dynamically allocate
per-thread group counters") and wasn't updated when shared.tg gained
pointer-array semantics. Additionally, memset'ing only the outer struct
never actually reset the accumulated counters in the pointed-at
per-tgroup structs, so 'clear counters all' silently failed to match
its documented "same effect as restarting" behaviour for cumulative
counters (bytes, sessions, requests, ...).

Introduce two helpers in src/counters.c:

  void counters_fe_reset(struct fe_counters *counters);
  void counters_be_reset(struct be_counters *counters);

Both iterate shared.tg[0 .. nbtgroups-1] and zero the *contents* of each
per-tgroup struct via memset, then zero the local (non-shared) fields of
the outer struct. The shared.tg pointer array, each shared.tg[it]
pointer, and shared.flags (COUNTERS_SHARED_F_LOCAL is set at boot and
reflects allocation ownership, not counter state) are preserved.

Refactor proxy_stats_clear_counters() to call these helpers instead of
the inline memset. This fixes both the segfault after 'clear counters
all' and the silent no-op on cumulative counters, and provides a shared
primitive for a subsequent 'clear counters server <b>/<s>' command.

In SHM stats-file mode, zeroing the per-tgroup structs affects every
process attached to the same object; this matches the intended
"reset everywhere" semantic of clear counters and is unchanged from
the outer-memset intent.

This bug should be backported wherever the per-thread-group shared
counter refactor is present.

2 weeks agoBUG/MINOR: ech: reject an ECH store with no usable private key
William Lallemand [Thu, 30 Jul 2026 15:56:37 +0000 (15:56 +0000)] 
BUG/MINOR: ech: reject an ECH store with no usable private key

OSSL_ECHSTORE_read_pem() accepts a PEM file containing only an
ECHConfig with no private key. OSSL_ECHSTORE_num_keys() returns 1
on success regardless of the count it writes back, so the existing
"!= 1" check only ever caught the call itself failing, never a
resulting count of zero: a directory containing only such
config-only ".ech" files loaded "successfully" and got installed via
SSL_CTX_set1_echstore(), while unable to decrypt a single
ECH-protected ClientHello, with nothing pointing at the cause.

This should be backported to 3.3 and 3.4.

2 weeks agoBUG/MINOR: ech: propagate error from load_echkeys()
William Lallemand [Thu, 30 Jul 2026 13:41:12 +0000 (13:41 +0000)] 
BUG/MINOR: ech: propagate error from load_echkeys()

load_echkeys() was not emitting any error messagw, so a failed load only
ever produced "failed to load ECH keys". Add a char **err parameter to
the function so it can emit TLS library errors or system errors.

This should be backported to 3.3 and 3.4.

2 weeks agoDOC: config: Document the tune.fd.tables option
Olivier Houchard [Thu, 30 Jul 2026 13:59:06 +0000 (15:59 +0200)] 
DOC: config: Document the tune.fd.tables option

Add documentation for tune.fd.tables.

2 weeks agoBUILD: listener: Fix the build on platforms without MSG_CMSG_CLOEXEC
Olivier Houchard [Thu, 30 Jul 2026 12:03:55 +0000 (14:03 +0200)] 
BUILD: listener: Fix the build on platforms without MSG_CMSG_CLOEXEC

Some OSes, such as macos, do not provide MSG_CMSG_CLOEXEC, so provide a
fallback mechanism using fdcntl(FD_CLOEXEC) instead.
It doesn't matter right now, that code will never be used on such OS,
because you can't use per-thread-group fd tables in there, but maybe it
will change one day.

2 weeks agoMEDIUM: fd: Add the tune.fd.tables option
Olivier Houchard [Thu, 9 Jul 2026 11:25:54 +0000 (13:25 +0200)] 
MEDIUM: fd: Add the tune.fd.tables option

Add a new experimental option, tune.fd.tables, that can be set to
either "shared" (the default) or "per-thread-group", to enable
per-thread group file descriptor tables.
This can give a nice performance boost when running with a lot of
threads, as we observe a lot of contention on the file descriptor table
lock in the kernel.

2 weeks agoMINOR: connection: Do not retrieve src/dst on another thread group's FD
Olivier Houchard [Wed, 29 Jul 2026 23:04:50 +0000 (01:04 +0200)] 
MINOR: connection: Do not retrieve src/dst on another thread group's FD

conn_get_src() and conn_get_dst() lazily fetch the connection's
addresses with getsockname()/getpeername() when they were not known
yet. They may be called on a foreign connection by observability code
running on any thread, for example "show peers", which dumps the peers
sessions' connections from whatever thread serves the CLI. With
per-thread-group FD tables, the FD of a connection owned by another
group is not usable from the calling thread.
So just fail if we attempt to access a connection that is owned by
another thread group.

2 weeks agoMEDIUM: quic: Do not use another thread group's listener FD
Olivier Houchard [Wed, 29 Jul 2026 23:03:51 +0000 (01:03 +0200)] 
MEDIUM: quic: Do not use another thread group's listener FD

A datagram may be read by a thread of another group than the one owning
the connection it is for, and with per-thread-group file descriptor
tables, we can't assume that we can use that file descriptor, so find a
more fitting one if needed.

2 weeks agoMEDIUM: resolvers: Do not close another thread group's socket at deinit
Olivier Houchard [Wed, 29 Jul 2026 22:59:04 +0000 (00:59 +0200)] 
MEDIUM: resolvers: Do not close another thread group's socket at deinit

resolvers_destroy() runs at deinit, on the last remaining thread, and
closes the UDP nameserver sockets, which were created by the thread
their resolvers section's task was pinned to. With per-thread-group FD
tables, when that thread belongs to another group, its group's FD
table died with its last thread and already closed the socket, and the
stored number is only meaningful in that table, closing it from here
would actually close whatever FD carries the same number in this
thread's own table. So in that case, leave the FD alone.

2 weeks agoMEDIUM: server: Do not close other thread groups' connections at deinit
Olivier Houchard [Wed, 29 Jul 2026 22:59:04 +0000 (00:59 +0200)] 
MEDIUM: server: Do not close other thread groups' connections at deinit

srv_close_idle_conns() runs at deinit, on the last remaining thread,
and closes the FDs of every thread's idle connections. With
per-thread-group FD tables, the connections of the other thread groups
were already closed by the kernel when the last thread of their group
exited, and their numbers are only meaningful in those groups' tables,
closing them from here would in fact close whatever FD carries the
same number in this thread's own table, and let sock_conn_ctrl_close()
rewrite an unrelated entry of this group's FD table. So in that case,
only release the memory and leave the FDs alone.

2 weeks agoMEDIUM: dns: Stick the TCP nameserver tasks to the resolvers' thread
Olivier Houchard [Wed, 29 Jul 2026 22:26:58 +0000 (00:26 +0200)] 
MEDIUM: dns: Stick the TCP nameserver tasks to the resolvers' thread

The tasks handling the TCP nameservers (task_req, task_rsp, task_idle)
are created with no thread affinity, but with thread groups having their
own file descriptors table, it has to run on the same thread as the
one who created the socket, or maybe the other thread will not be able
to use the socket.
In practice, it was not a problem, because the scheduler will wake the
task on the running thread. But that may change in the future, so make
sure we always run on the current thread from start to finish.

2 weeks agoMINOR: cli: Report the tgid along the FD in "show sess"
Olivier Houchard [Wed, 29 Jul 2026 21:37:32 +0000 (23:37 +0200)] 
MINOR: cli: Report the tgid along the FD in "show sess"

With per-thread-group FD tables, an FD number alone is ambiguous: it is
only meaningful within the thread group of the stream that uses it. So
display the FDs of the "show sess" output in the "tgid/fd" form already
used by "show fd", both in the one-line format and in the detailed one.
In the detailed output, the FD state, update and thread masks are also
read from the owner group's table rather than from the calling
thread's one, which would report an unrelated entry when the tables
are not shared.

2 weeks agoMINOR: debug: Report the current tgid in "debug dev fd"
Olivier Houchard [Wed, 29 Jul 2026 21:28:48 +0000 (23:28 +0200)] 
MINOR: debug: Report the current tgid in "debug dev fd"

"debug dev fd" scans the FDs of the process as the calling thread sees
them, and reports the ones unknown to fdtab. With per-thread-group FD
tables, both that scan and the fdtab lookups only reflect the view of
the calling thread's group: the same command may report different FDs
depending on the thread that serves it. Let's print the current tgid in
a header line so the output states which view it shows.

2 weeks agoMINOR: cli: Make "show fd" aware of per-thread-group FD tables
Olivier Houchard [Wed, 29 Jul 2026 20:35:02 +0000 (22:35 +0200)] 
MINOR: cli: Make "show fd" aware of per-thread-group FD tables

The <tgid>/<fd> argument forms of "show fd" were parsed but the tgid was
ignored so far, the dump always reading the local thread's FD table.
Now that each thread group can have its own table, give the tgid its
intended meaning: a specific tgid dumps that group's table only, while
the wildcard and unspecified forms visit every group in turn, so that
"show fd /<fd>" shows what each group knows about a given FD number.
Each entry's group remains visible in the gid= field. The polled_mask
words are read from the dumped group's slice as well, and the dump can
resume from the right group when the output buffer fills up.
When the tables are shared, all the groups alias the same table, so a
single pass is performed as before and the tgid makes no difference.
The tgid is now also validated against the number of configured thread
groups rather than the maximum supported.

2 weeks agoMEDIUM: pipes: Have one pool of free pipes per thread group
Olivier Houchard [Tue, 28 Jul 2026 11:37:12 +0000 (13:37 +0200)] 
MEDIUM: pipes: Have one pool of free pipes per thread group

The pipes used for splicing are kept in a shared pool of free pipes, so
that they can be reused by any thread. However, with per-thread-group
FD tables, a pipe created by one group only exists in that group's
kernel table, so its FDs must never be handed over to a thread of
another group.
To fix that, replace the global list of free pipes with one list per
thread group if the fd tables are not shared across thread groups.

2 weeks agoMEDIUM: pollers: Only allow epoll when each tgroup has its fd table
Olivier Houchard [Wed, 8 Jul 2026 14:01:36 +0000 (16:01 +0200)] 
MEDIUM: pollers: Only allow epoll when each tgroup has its fd table

Getting pollers such as select and poll to work when thread groups
unshared their file descriptors would require to have per-thread groups
arrays of file descriptors. Given making each thread group its own file
descriptor table is done for performance reasons, and those pollers will
never perform very well, this does not seem to be worth it.
kqueue and evports would work, except that they are only implemented on
OSes that do not provide the equivalent of the unshare() system call.
So effectively, only epoll can be used as a poller when fds are not
shared across thread groups.

2 weeks agoMEDIUM: cli: Transfer sockets with unshared file descriptor tables
Olivier Houchard [Wed, 8 Jul 2026 09:20:40 +0000 (11:20 +0200)] 
MEDIUM: cli: Transfer sockets with unshared file descriptor tables

Teach the _getsocks command how to use the new rx agent facilities to
obtain the file descriptors from other thread groups when each one has
its own file descriptor table.

2 weeks agoMEDIUM: listener: Properly handle unshared fd tables between tgroups
Olivier Houchard [Tue, 7 Jul 2026 08:55:57 +0000 (10:55 +0200)] 
MEDIUM: listener: Properly handle unshared fd tables between tgroups

With different FD tables for each thread group, a new mechanism is
needed to get each thread group to close/open their fds, and to transfer
fds from one thread group to another.
So we add the concept of rx agents. Each thread group has a tasklet
dedicated to that, bound to the first thread of each thread group, and
are used to get those operations done.
Each receiver contains context bits so that the tasklet will know which
action to do when woken up.
A new socketpair is also created per group, to be able to send fds to other
thread groups.

2 weeks agoMEDIUM: backend: Do not always allow takeover across thread groups
Olivier Houchard [Thu, 2 Jul 2026 16:49:48 +0000 (18:49 +0200)] 
MEDIUM: backend: Do not always allow takeover across thread groups

When we're running with separate file descriptor tables across thread
groups, do not allow connection takeover across thread groups, as the
connection would not be usable for that thread, anyway.