Compare commits

...

118 commits

Author SHA1 Message Date
Willy Tarreau
e8c9aabd62 BUG/MINOR: haterm: fix the random suffix multiplication
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
Passing a size or anything with suffix "r" is supposed to apply a
random factor form 0 to 1. However due to the replacement of random()
with ha_random64(), all 64 bits are random before the divide, so the
end result is a random 32-bit value. In addition, ha_random64() is
slow since shared between threads.

Let's use statistical_prng() which is designed for this purpose and
is much cheaper. No backport is needed, this is only in 3.4.
2026-05-25 20:49:22 +02:00
Willy Tarreau
32fc35ef09 CLEANUP: resolvers: fix comment typos and wrong filenames in file headers
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
A few asorted comment fixes for resolvers (incorrect file name etc).
2026-05-25 10:57:14 +02:00
Willy Tarreau
6bb8cb51e6 CLEANUP: resolvers: remove pool_free(NULL) in SRV additional record matching
In resolv_validate_dns_response(), when matching an additional A/AAAA
record to an SRV record, the code checked tmp_record->ar_item == NULL
then called pool_free(resolv_answer_item_pool, tmp_record->ar_item).
This is a copy-paste mistake from similar patterns elsewhere since
the pointer is confirmed to be NULL a few lines above, so let's just
drop the confusing pool_free.
2026-05-25 10:57:14 +02:00
Willy Tarreau
8fe8d5fbe3 CLEANUP: resolvers: use read_n32() instead of open-coded big-endian read
In resolv_validate_dns_response(), the second DNS record parsing path
manually constructs a 32-bit big-endian TTL value from four individual
bytes using the expression:

  reader[0] * 16777216 + reader[1] * 65536 + reader[2] * 256 + reader[3]

We have read_n32() to do this, and it's more robust against unexpected
signedness surprises (which should not happen right here since reader is
unsigned char and we use -fwrapv so the result is defined). Also, let's
make the ttl an uint instead of an int. The TTL is only retrieved and not
used for now, so better clean it now.
2026-05-25 10:57:13 +02:00
Willy Tarreau
b78b023d55 BUG/MINOR: sample: limit the be2hex converter's chunk size
In 2.5, commit da0264a96 ("MINOR: sample: Add be2hex converter")
introduced the be2hex() converter, which reads input data of a given
chunk size, processes it as a big endian block and turns it to hex
output.

There's an issue if the configured chunk_size (2nd argument) is larger
than tune.bufsize/2, because the max_size calculation will underflow,
and the later loop will always match since it compares a size_t to an
int (BTW, compilers love to annoy us with useless warnings but I never
found how to see some for these ones). This can result in overflowing
the output trash if  the input sample is at least as large as half a
buffer.

Let's add an explicit check for this, and change the max_size type to
size_t so that the comparison is always right. While we're at it, let's
ask the trash buffer to be twice as large, just like bin2hex() does, as
it may result in offering a larger buffer in 3.4. thanks to the large
buffers support.

Despite the risk, this is marked as minor because a config with that
large an argument in the converter makes absolutely no sense.

This should be backported to 2.6. The *2 for the trash allocation will
conflict and have to be dropped in stable versions, which is safe.
2026-05-25 10:57:13 +02:00
Willy Tarreau
7d182a2ed5 BUG/MINOR: init: use more than ha_random64() for the cluster secret
When not set, the cluster secret is randomly generated by two
consecutive calls to ha_random64(). However, the random64 PRNG may be
partially observed on a fully idle machine (QUIC retry tokens, UUID,
WS key), and it could be rolled back to the initial call that produced
the secret. This is purely theoretical as a normally loaded system
wouldn't reveal meaningful sequences, but better address this while
it's still easy.

The first here consists in isolating the cluster_secret from the PRNG
sequence. When RAND_bytes() is available and works, it's used. Otherwise
ha_random64() is mixed with uncorrelated bits from random().

This could be backported to stable releases.
2026-05-25 10:52:42 +02:00
Willy Tarreau
c0e302fe79 BUG/MINOR: dict: fix refcount race on insert collision
In dict_insert(), when ebis_insert() returns an existing node n indicating
that another thread inserted the same key concurrently, the code freed its
own newly-allocated entry and returned the winner without bumping its
refcount. Both callers then held a reference with refcount=1 instead of 2,
so when one expires the other becomes a use-after-free or double-free.

The bug likely comes from the fact that new_dict_entry() creates an entry
with a refcount preset to 1 (saves an atomic op) and that because of this
there is no refcount increment upon a successful insertion in the tree,
resulting in requiring different code paths for collision and normal
insertion.

A simple fix consists in bumping the refcount under the lock and unlocking
only at the end, but this would mean performing two free() calls under a
lock, which we always try to avoid. The code was slightly rearranged so
that we can now bump the existing entry's refcount under the lock in case
of duplicate, or unlock immediately in the common case, so that the free()
call is done out of the lock.

The probably of the race is very low (at peers connection setup only),
reason why it's marked low. This should be backported to all versions.
2026-05-25 10:52:42 +02:00
Willy Tarreau
478e7e52cb BUG/MINOR: log: look for the end of priority before the end of the buffer
In parse_log_message(), the first loop looks for '>' that finishes the
priority field, and unfortunately it stops once it has checked the first
byte after the end of the buffer. This means that a priority made only
of digits for the whole buffer would read one extra byte. In practice
since pools have a tag at the end this is only detectable when using ASAN,
but this should be fixed nevertheless.

This can be backported to all versions.

It's worth noting that RFC5424 now says that the PRI field is 1..3
digits only, so maybe at some point we could seriously limit the
length as well.
2026-05-25 10:52:42 +02:00
Willy Tarreau
8e1d33a648 BUG/MINOR: mux-h2: validate HEADERS frame length before reading stream dep
When the PRIORITY flag is present on a HEADERS frame, the frame must
contain a stream dependency and a weight, for a total of 5 bytes. The
length is checked after reading the stream dep field so theoretically
such a frame could cause up to 4-byte OOB read at the end of the buffer,
though in practice buffers allocated from pools never end on a page
boundary (one extra word at the end) and the anomaly is still detected
after reading the stream ID and the connection aborted with the glitch
count incremented. Thus while not technically correct, practically
speaking it's harmless.

This should be backported to all stable releases.
2026-05-25 10:52:42 +02:00
Willy Tarreau
49d6306de3 BUG/MINOR: resolvers: fix risk of appending garbage past the domain name
The previous fix 75f72c2eb ("BUG/MEDIUM: resolvers: Fix test on dn label
size in resolv_dn_label_to_str()") may still leave garbage from the input
buffer into the response: if a component length is passed as zero, it
should mark the end, but instead a dot will be emitted, and whatever
follows it in the input buffer would continue to be appended as extra
components. While having no direct consequences beyond the domain not
being properly decoded, it could at least complicate troubleshooting.

This should be backported where the fix above is backported.
2026-05-25 10:52:42 +02:00
Willy Tarreau
01ebb668a4 BUG/MINOR: resolvers: fix room for trailing zero in resolv_dn_label_to_str()
The previous fix 75f72c2eb ("BUG/MEDIUM: resolvers: Fix test on dn label
size in resolv_dn_label_to_str()") can still be fooled by an input exactly
the size of str_len, in which case the trailing zero appended at the end
was not being accounted for. Let's add 1 to the condition to prepare for
it.

This needs to be backported wherever the fix above is backported.
2026-05-25 10:52:42 +02:00
Willy Tarreau
340cc86efb BUG/MINOR: log: free logformat expr on compile failure in cfg_parse_log_profile
When lf_expr_compile() fails in cfg_parse_log_profile, the code leaves
without freeing the previously strdup()'d strings in target_lf->str and
target_lf->conf.file. Let's add a call to lf_expr_deinit() there to
release it.

It was harmless anyway since the startup will abort when this happens,
but better clean it because with increasingly dynamic setups, one day
it could become a runtime leak.

No backport is needed.
2026-05-25 10:52:42 +02:00
Willy Tarreau
f62d020140 BUG/MEDIUM: cache: fix a refcount leak for missed secondary entries
When a primary cache hit has a Vary secondary_key_signature, the code calls
retain_entry() and shctx_row_detach() before performing the secondary lookup.
If get_secondary_entry() returns NULL (no stored variant matches), res is set
to NULL and the function falls through to return ACT_RET_CONT without calling
release_entry() or shctx_row_reattach(). Each such request leaks one refcount
and pins one shctx row permanently, eventually exhausting the cache if this
happens to all objects. This is visible when requesting a secondary key
covered by vary for an object that is already stored without that key.
"show cache" then shows the object's refcount increasing after each request.

In order to fix this we must do like when no secondary key could be built
and release everything. We only reattach to the row if we previously
detached.

The issue was introduced in 2.4 with commit 1785f3dd9 ("MEDIUM: cache: Add
the Vary header support"). The code changed a bit in 2.9 with commit
48f81ec09 ("MAJOR: cache: Delay cache entry delete in reserve_hot function"),
so in order to backport to 2.8 and older, the patch will have to be manually
applied (no test on detached).
2026-05-25 10:52:42 +02:00
Willy Tarreau
bbef74fb21 BUG/MEDIUM: tcpcheck/spoe: bound the SPOP error code to valid values
tcpcheck_spop_expect_hello() stores the SPOA agent-supplied status-code
varint directly into check->code (signed short) without range validation.
The code is later used as an index into spop_err_reasons[100]. Let's
just replace invalid status codes with SPOP_ERR_UNKNOWN to avoid any
problem.

The SPOP tcp-check was introduced in 3.1 so this fix must be backported
to 3.2.
2026-05-25 10:16:06 +02:00
Willy Tarreau
608951844e BUG/MEDIUM: regex: allocate a large enough pcre2 match for all matches
In 3.3 with commit fda6dc959 ("MINOR: regex: use a thread-local match
pointer for pcre2") we got a thread-local match that saves us from having
to allocate a match array with each match. However something was clearly
overlooked or misunderstood in the pcre2 API because the local match
array was initialized via pcre2_match_data_create() for MAX_MATCH-1
entries instead of MAX_MATCH, despite the commit message mentioning
MAX_MATCH entries. It was possibly confused with an index. Due to this
there is a risk of crash when matching more than 9 groups in a regex.

This fix must be backported to 3.3.
2026-05-25 10:16:06 +02:00
Willy Tarreau
f9088a5d75 BUG/MEDIUM: log-forward: make sure the month is unsigned
In 2.3, in preparation for log forwarding, commit 546488559 ("MEDIUM:
log/sink: re-work and merge of build message API.") extended the log
send API to be able to use metadata from an existing header. However
the month number is parsed from the passed meta-data and compared
against 11 but there's no check for negative values which could in
theory cause a negative monthname[] index.

It can be a problem when the date is received as RFC5424 and forced
to RFC3164 because certain characters in the month field could result
in a negative month value. Let's fix it by turning the month to unsigned
to make sure we only accept months 0..11.

This should be backported to all branches.
2026-05-25 10:16:06 +02:00
Willy Tarreau
007d5946b4 BUILD: intops: mask the fail value in array_size_or_fail()
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
Cross-compilation on m68k fails in ssl_sock_resize_passphrase_cache()
where the compiler noticed the SIZE_MAX passed to realloc() in the
error path and complained that it's larger than PTRDIFF_MAX. This can
be disabled with -Walloc-size-larger-than=SIZE_MAX but in practice we
can simply hide the value and keep the warning to detect real failures
elsewhere. Let's pass it through DISGUISE() and also take this
opportunity for doing that inside an unlikely() clause since it's never
supposed to happen.
2026-05-25 07:33:35 +02:00
CyberpsychoJacob
4db85fc53e BUG/MEDIUM: acme: NUL terminate response buffer before PEM parsing
acme_res_certificate() passes the httpclient response buffer to
ssl_sock_load_pem_into_ckch(), which will then call BIO_new_mem_buf(buf, -1).
The "-1" flag will make the OpenSSL PEM parser determine the length by
using strlen(). However, the httpclient populates the response buffer with
__b_putblk() without writing a trailing NUL to it. The byte at area[data]
is whatever data previously resided there in the memory pool.

Thus, a malicious or compromised ACME CA can perform an arbitrary-length
out-of-bounds read until hitting the first NULL byte past the response
body. The OpenSSL PEM loader will try to iterate to load the chain
certificates, thus the PEM-looking garbage found in freed memory chunks
can be erroneously loaded as additional intermediate certificates. The
presence of a single NUL inside the valid response body will result in
silent truncation of the certificate.

Make sure that the area[data] contains a terminating NULL before passing
the buffer to the parser. Fail on insufficient room for the NUL terminator.

No backport required: The ACME client has been added in 3.x and this
code path didn't exist in 2.x.
2026-05-23 18:09:59 +02:00
Christopher Faulet
41bb1c24f6 BUG/MEDIUM: cli: Fix parsing of pattern finishing a command payload
Some checks failed
Contrib / admin/halog/ (push) Has been cancelled
Contrib / dev/flags/ (push) Has been cancelled
Contrib / dev/haring/ (push) Has been cancelled
Contrib / dev/hpack/ (push) Has been cancelled
Contrib / dev/poll/ (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
When the dedidacted buffer to store the command payload was added (c5ae0da62
"MEDIUM: cli: Make a buffer for the command payload"), an bug was
introduced. When the pattern finishing the command payload is found, it is
removed from the buffer. A NULL-bytes is added before it, skipping the
previous newline character.

It worked well in all cases before the commit above, because the commandline
was already parsed and was placed at the beginning of the cmdline buffer.
So, there is always a line before the payload.

Now, the payload is stored in a dedicated buffer. So there is nothing
preceeding it in a buffer. If the payload is empty, we cannot rewind to the
previous line to set the NULL-byte character. We must handle this case to
avoid integer underflow on the payload buffer length.

It is a 3.4-specific bug. No backport needed.
2026-05-22 17:17:01 +02:00
Christopher Faulet
9091cfa617 BUG/MEDIUM: hlua: Fix integer underflow when receiving line from lua cosocket
In hlua_socket_receive_yield(), when we try to get a line, the trailing CRLF is
stripped by decrementing the block length. The '\n' is first skipped, then,
possible a preceeding '\r'. But the block lenght is never checked. If an empty
line is returned, this leads to an integer underflow and most probably to a
crash because this length is used to copy data into a LUA string.

To fix the issue, the block length is now properly tested against 0 before
decrementing it.

This patch must be backported to all stable versions.
2026-05-22 17:17:01 +02:00
Christopher Faulet
57b526e022 BUG/MINOR: tcpchecks: Limit parsing of agent-check reply to the buffer
When parsing the agent-check reply, we first loop on the response to find
the newline character, to add a NULL-byte at the end of the line. However,
this loop is not bounded to the data available in the buffer. So it is
possible to read bytes outside the buffer and eventually write a NULL-byte
ouside the buffer.

So let's check for the end of the buffer when looping on the agent-check
reply.

This patch must be backported to all stable versions.
2026-05-22 17:17:01 +02:00
Christopher Faulet
2644f9ddf9 BUG/MEDIUM: dict: hold lock while decrementing refcount in dict_entry_unref
In dict_entry_unref(), the write lock on d->rwlock was only acquired after
decrementing the refcount. However, between the decrement and the lock,
another thread could increment it by calling dict_insert(). That could lead
to a UAF.

To fix the issue, the call to HA_ATOMIC_SUB_FETCH is moved inside the write
lock.

This patch must be backported to all stable versions.
2026-05-22 17:17:01 +02:00
Amaury Denoyelle
7cab3a3c3a BUG/MINOR: quic: fix ODCID lookup from derived value
Some checks failed
Contrib / admin/halog/ (push) Has been cancelled
Contrib / dev/flags/ (push) Has been cancelled
Contrib / dev/haring/ (push) Has been cancelled
Contrib / dev/hpack/ (push) Has been cancelled
Contrib / dev/poll/ (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
In haproxy, when an Initial packet is received, a new connection may be
created and a DCID must be attributed. This CID is derived from the
original DCID used by the client in its first packet. This is an
optimization to avoid storing two CIDs values in the CID tree.

On CID lookup, if the DCID used is not found, derivation is performed
again. This should permit to retrieve the DCID node. However, this
operation is not performed as expected in quic_get_cid_tid(), as the
wrong value is used on the second lookup. Fix this function by using
derive CID for it. Note that retrieve_qc_conn_from_cid() performs the
same lookup but the bug was not present there.

The impact of this bug is relatively low as most clients send a single
Initial packet. Even in case of multiple packets in a single datagram,
this does not cause any issue as the current thread is assigned as
default.

This should be backported up to 2.8.
2026-05-22 16:03:10 +02:00
Christopher Faulet
04b9215a2e BUG/MEDIUM: ssl-gencert: Unlock LRU cache if failing to generate certificate
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
In ssl_sock_generate_certificate(), if the LRU cache for generated
certificates is used, the LRU tree is not unlocked on cache miss if the
certificate generation failed. So let's unlock it on error path.

The bug was introduced by the commit fbc98ebcd ("BUG/MEDIUM: ssl: fix error
path on generate-certificates"). So this patch must be backported with the
commit above, so to all stable versions.
2026-05-22 11:37:00 +02:00
Christopher Faulet
75f72c2eb9 BUG/MEDIUM: resolvers: Fix test on dn label size in resolv_dn_label_to_str()
In resolv_dn_label_to_str(), size for a dn label was stored into an integer
from a signed char without a cast to unsigned. So dn label with a size of
128 bytes or more become negative, skipping this way the copy loop and
desynchronizing input vs output.

In addition, the size of the destination string was only checked at the
begining, against the dn string length. But it must also be checked for
every dn label, to be sure. The dn string can be forged to copied more bytes
than expected.

This patch must be backported to all stable versions.
2026-05-22 11:13:33 +02:00
Christopher Faulet
1ed4ef6659 BUG/MEDIUM: applet: Properly handle receives of size 0
when appctx_rcv_buf() function was called to get data from the applet, but
to get zero bytes, nothing was performed and the function early
returned. However, we must at least take care to set SE_FL_WANT_ROOM if
necessary. Otherwise, if data are still blocked in the applet's output
buffer while the EOI/EOS are pending, the information can be reported to the
upper layer and remaining data can be lost.

Indeed, in such case, SE_FL_WANT_ROOM flag is here to specify the applet has
more data to deliver. Thanks to this flag, the stream will wait before
closing. But when appctx_rcv_buf() function is called, this flag is removed by
the stconn. It is the function responsibility to set it again when necessary.

This patch should fix second part of the issue #3366. It must be backported
to 3.0.
2026-05-22 08:45:57 +02:00
Amaury Denoyelle
3fab21ea42 MINOR: mux_quic: do not crash on unhandled QMux frame reception
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
Completes qmux_parse_frm() to ensure every frames allowed by QMux
protocol are listed. For now, nothing is implemented except a CHECK_IF()
to report such events.

This is necessary to prevent a crash on abort. Frames not supported by
QMux should already have been rejected prior via qmux_is_frm_valid().
2026-05-21 15:57:20 +02:00
Amaury Denoyelle
f9d4d659a4 MINOR: mux_quic: handle MAX_STREAMS for uni stream in QMux
Handle reception of a MAX_STREAMS frame for unidirectional stream usage
when using QMux. This simply consists in using qcc_recv_max_streams() as
with QUIC protocol.
2026-05-21 15:57:20 +02:00
Amaury Denoyelle
c0aa91a202 MINOR: mux_quic: handle STOP_SENDING in QMux
Ensure reception of STOP_SENDING via QMux protocol is properly handled.
This simply consists in using qcc_recv_stop_sending() which will update
the associated QCS if found.
2026-05-21 15:57:20 +02:00
Remi Tricot-Le Breton
e2c3cd9eb7 BUG/MINOR: ocsp: Manage date too far away in the future
The check on the OCSP response expire time is based on the "Next Update"
field of the response, converted by my_timegm function that returns a
time_t (signed long). It is then stored in the 'expire' field of the
certificate_ocsp structure which is typed as a signed long.
When loading an OCSP response, if the "Next Update" time is too far in
the future and we are running on a 32 bits machine, we might end up with
negative times ireturned by my_timegm, which make the comparison with
the current date fail and raises the "OCSP single response: no longer
valid." error message.

This problem typically happens in the ocsp_auto_update.vtc regtest since
the loaded OCSP response have a "Next Update" field in 2050.

This patch simply changes the type of the expire field to an unsigned
long since the 'my_timegm' function does not return '-1' in case of
error, contrary to the standard 'timegm' one.

Ths patch can be backported to all stable branches.
2026-05-21 15:43:49 +02:00
Amaury Denoyelle
6717531053 MINOR: backend: support QMux in clear for BE side
Some checks failed
Contrib / admin/halog/ (push) Has been cancelled
Contrib / dev/flags/ (push) Has been cancelled
Contrib / dev/haring/ (push) Has been cancelled
Contrib / dev/hpack/ (push) Has been cancelled
Contrib / dev/poll/ (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
Use xprt_add_l6hs() at the end of connect_server() if selected MUX layer
relies on a temporary handshake prior to its initialization. This
functions is noop is SSL layer is active.

This change is necessary to support clear QMux on the backend side.
Recently defined <init_xprt> from mux_proto_list is used to render the
code as generic as possible.
2026-05-21 15:09:10 +02:00
Amaury Denoyelle
812962d110 MINOR: session: support QMux in clear on FE side
Activates xprt_qmux layer if necessary via session_accept_fd(). This is
necessary to be able to support QMux in clear. This operation is noop if
SSL is active, as in this case xprt_qmux will be activated after the SSL
handshake completion.

To ensure MUX init is delayed when running with clear QMux, mask
CO_FL_WAIT_XPRT_L6 is added to test if the embryonic task must be
started instead.
2026-05-21 15:09:10 +02:00
Amaury Denoyelle
8fe8f78473 MINOR: connection: define mask CO_FL_WAIT_XPRT_L6
Define a new connection flag mask CO_FL_WAIT_XPRT_L6. This will be used
to indicate that a XPRT layer is running on top of layer 6. For now,
only xprt_qmux implements this method of operation.
2026-05-21 15:09:10 +02:00
Amaury Denoyelle
cdeb2aa4ef MINOR: xprt_qmux: define default value for get_alpn
Extend get_alpn() for xprt_qmux layer. If lower layer does not implement
ALPN negotiation, return a statically default protocol value. Currently
this is set to "h3".

This change is required to support QMux in clear without SSL. In the
future, it could be useful to configure the default protocol, for
example by extending the syntax for the "proto" keyword.
2026-05-21 15:09:10 +02:00
Amaury Denoyelle
9e6e0fd149 MINOR: connection: define xprt_add_l6hs()
When QMux protocol is used, xprt_qmux layer is setup after SSL handshake
completion but prior to the MUX initialization. Once transport
parameters exchange is successful, the layer is removed and the MUX is
started.

The layer setup operation was performed directly on ssl_sock_io_cb().
Simplify the code by extracting it in a dedicated function
xprt_add_l6hs(). The function is generic so the requested XPRT layer
must be passed as argument.

The code is mostly identical. One difference is that a check is
performed to ensure no SSL handshake is pending. If this is the case,
the function is a noop. This will become useful to support QMux
transparently both in clear or on top of SSL.

Another minor addition is that CO_FL_XPRT_READY flag is automatically
resetted by xprt_add_l6hs(). This allows the code to use
conn_xprt_start() standard function after XPRT init.
2026-05-21 15:09:10 +02:00
Amaury Denoyelle
e98595e4e5 MINOR: ssl_sock: remove unneeded check on QMux flags
A recent patch has introduced <init_xprt> mux_proto_list member. This
allows to activate QMux on SSL handshake completion without explicit
"proto qmux" setting.

Thanks to this change, on SSL handshake completion it is not necessary
anymore to check for CO_FL_QMUX_* flags.
2026-05-21 15:09:10 +02:00
Willy Tarreau
413f6f9a1f BUG/MEDIUM: net_helper: fix a remaining possibly infinite loop in converters
The various tcp_option_* converters rely on tcp_fullhdr_find_opt() to
find the option. However, the same bug as fixed in commit dbf471f99a
("BUG/MAJOR: net_helper: ip.fp infinite loop on malformed tcp options")
was also present there, by which an option of length 0 could be looped
over indefinitely. In practice this does not happen since such options
are not valid, but if passed encoded in an HTTP header for example, it
could possibly be passed.

While fixing it, let's check for length >1 in all 3 locations insteead
of only non-zero, since there's no point processing a malformed option
that wouldn't even be properly skipped.

This fix doesn't need to be backported, unless the ip.fp series is.

Thanks to @Vincent55 for reporting this issue.
2026-05-21 15:05:39 +02:00
Willy Tarreau
3475a5bb9f BUILD: proxy: unstatify the proxies_del_lock to avoid a warning without threads
When threads are disabled, "static __decl_spinlock(foo);" ends up as
"static;", causing a build warning when threads are disabled. We don't
need it to be static so let's drop "static" here. No backport is needed,
this is 3.4-only.
2026-05-21 09:03:03 +02:00
Willy Tarreau
050e06dd66 MINOR: config: shm-stats-file is no longer experimental
As confirmed by Aurlien, there isn't any point in keeping this feature
in experimental status, it's now stable.
2026-05-21 08:50:20 +02:00
Willy Tarreau
bcf768f157 [RELEASE] Released version 3.4-dev13
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
Released version 3.4-dev13 with the following main changes :
    - BUG/MINOR: backend: correct parameter value validation in get_server_ph_post()
    - BUG/MINOR: config/dns: properly fail on duplicate nameserver name detection
    - BUG/MEDIUM: dns: fix long loops in additional records parse on name failure
    - BUG/MEDIUM: resolvers: fix name compression pointer validation in resolv_read_name()
    - BUG/MEDIUM: dns: fix memory leak of sockaddr in dns_session_init() error path
    - CLEANUP: proxy: fix tiny mistakes in parse error messages
    - CLEANUP: dns: fix misleading error messages in dns_stream_init()
    - BUG/MINOR: server: better handling of OOM in srv_set_fqdn()
    - BUG/MINOR: servers: use proper source of pool_conn_name in srv_settings_cpy()
    - BUG/MEDIUM: server/cli: unlock server lock on failure in cli_parse_set_server
    - BUG/MINOR: resolvers: fix dangling list pointer in resolvers_new() error paths
    - BUG/MINOR: dns: fix dangling dgram pointer on dns_dgram_init() failure path
    - BUG/MINOR: proxy: use proxy_drop() in parse_new_proxy() error path
    - CLEANUP: resolvers: properly initialize the sample in resolv_action_do_resolve()
    - BUG/MINOR: resolvers: report the expression error in the do-resolve() action parser
    - BUG/MINOR: resolvers: fix leaked dgram and dns_ring struct in parse_resolve_conf()
    - BUG/MINOR: resolvers: fix leaked fields on cfg_parse_resolvers() error paths
    - BUG/MINOR: resolvers: fix missing task_idle destruction in resolvers_destroy()
    - CLEANUP: proxy: fix duplicate declaration of cli_find_frontend in proxy.h
    - CLEANUP: address a few typos and copy-paste errors in httpclient and dns
    - DOC: internal: add a few rules about internal core principles
    - BUG/MINOR: session/trace: use distinct flags for SESS_EV_END and _ERR
    - CLEANUP: stick-table: uniformize the different action_inc_gpc*()
    - REGTESTS: do not run quic/tls13_ssl_crt-list_filters in quic openssl compat mode
    - REGTESTS: quic/issuers_chain_path: do not forget to enable QUIC compat mode
    - BUG/MINOR: sock: store the connection error status
    - BUG/MINOR: check: properly report errno in chk_report_conn_err()
    - CLEANUP: tcpcheck: mention that we're a bit far for a sync errno
    - BUG/MINOR: jwt: fix possible memory leak in convert_ecdsa_sig() error path
    - CLEANUP: jwe: fix theoretical overflow in AAD length calculation
    - DOC: config: further clarify that resolvers "default" exists
    - MINOR: proxy: remove the experimental status on dynamic backends
    - BUG/MEDIUM: limits: properly account for global.maxpipes in compute_ideal_maxconn()
    - BUG/MINOR: jws: fix OpenSSL 3.0 version check from > to >=
    - BUG/MINOR: jws: Add missing return value check (EVP_PKEY_get_bn_param)
    - BUG/MINOR: server: Properly handle init-state value during haproxy startup
    - BUG/MINOR: httpclient-cli: Destroy http-client context if failing to start it
    - BUG/MEDIUM: h1: Skip all h2c values from Upgrade headers during parsing
    - BUG/MINOR: h1: Don't mask websocket protocol if multiple protocols used
    - MINOR: haterm: Don't init haterm master pipe if not used
    - CLEANUP: haterm: Remove "(too old kernel)" from warning message during init
    - BUG/MINOR: httpclient-cli: fix uninit variable in error label
    - MINOR: mux: Rename the "token" from mux_proto_list to mux_proto
    - MEDIUM: connections: Use both mux_proto and alpn to pick a mux
    - MINOR: connection: define conn_select_mux_fe()
    - MINOR: connection: define conn_select_mux_be()
    - MINOR: connection/mux_quic: add MUX <init_xprt> field for QMux handshake
    - MINOR: proxy/server: reject TCP ALPN h3 without experimental
    - MEDIUM: ssl: allow h3/QMux negotiation without explicit proto
    - BUG/MINOR: server: accept server IDs above 2^31 and clarify error message
    - BUG/MINOR: backend: fix balance hash calculation when using hash-type none
    - MINOR: server: support hash-key id32 for a cleaner distribution
    - MINOR: backend: support hash-key guid for a stabler distribution
    - MINOR: startup: support unprivileged chroot if possible
    - MEDIUM: startup: add automatic chroot feature
    - MINOR: h2: explain committed_extra_streams dec on h2_init() error
    - OPTIM: h2: do not update committed streams if elasticity disabled
    - MINOR: mux_quic: implement basic committed_extra_streams accounting
    - MINOR: quic: use stream elasticity value for initial advertisement
    - MINOR: mux_quic: define ms_bidi_rel QCC member
    - MAJOR: mux_quic: support stream elasticity during connection lifetime
    - BUG/MEDIUM: servers: Store the connection hash with the parameter cache
    - BUG/MINOR: prevent conn leak in case of xprt_qmux init failure
    - BUILD: traces: set a few __maybe_unused on vars used only for traces
    - BUILD: traces: add USE_TRACE allowing to disable traces
    - MINOR: startup: do not execute chroot() when "/"
    - MEDIUM: startup: warn when chroot is not set for root
    - BUG/MEDIUM: servers: Don't forget to set srv_hash when needed
    - DOC: fix typo on QUIC stream.max-concurrent reference
    - BUG/MINOR: mux_quic: do not exceed stream.max-concurrent on backend side
    - BUG/MINOR: htx: Fix value of HTX_XFER_HDRS_ONLY flag
    - MEDIUM: htx: Improve htx_xfer API to not count HTX meta-data
    - BUG/MEDIUM: applet: Fix transfer of HTX data to the applet
    - BUG/MEDIUM: htx: Alloc a chunk of right size in htx_replace_blk_value()
    - MEDIUM: stick-tables: Avoid freeing elements while holding a lock
    - MINOR: intops: add a multiply overflow detection for ulong and size_t
    - CLEANUP: tree-wide: use array_size_or_fail() in array size for allocations
    - DOC: update supported gcc and openssl versions in INSTALL
2026-05-20 17:46:36 +02:00
Willy Tarreau
897c5ddb8c DOC: update supported gcc and openssl versions in INSTALL
Gcc 16.1 was tested, clang 21 and OpenSSL 4.0. Let's mention this.
2026-05-20 17:45:23 +02:00
Willy Tarreau
f5477c8d45 CLEANUP: tree-wide: use array_size_or_fail() in array size for allocations
Instead of relying on malloc(n*size), we now pass array_size_or_fail(n,m)
so that it becomes possible to detect overflow. This is particularly
interesting for global settings that might be set large enough to cause
overflows on 32-bit systems for example, resulting in small values that
then cause trouble. Now the overflow will be detected at allocation time.
Around 25 locations were updated.
2026-05-20 17:05:19 +02:00
Willy Tarreau
b62ba7592a MINOR: intops: add a multiply overflow detection for ulong and size_t
Sometimes we'd like to know if some products overflow, so let's add a
pair of functions for this, for ulong and for size_t. For recent enough
compilers (gcc >= 5, clang >= 3.4) we just use __builtin_mul_overflow()
otherwise we rely on a division and a comparison before performing the
operation.

A third function, array_size_or_fail() computes the size of an array
of m elements of n bytes each, and returns the total size if it fits
in a size_t, otherwise ~0 if it does not so that passing this to
malloc() or any other variant would fail by trying to exhaust the
entire memory space.
2026-05-20 17:05:19 +02:00
Olivier Houchard
3e25104a9c MEDIUM: stick-tables: Avoid freeing elements while holding a lock
In stksess_trash_oldest(), and process_tables_expire(), avoid freeing
elements while holding two locks, as it could be very costly.
Instead, build a linked list of elements to be free'd, and do so once we
no longer hold any lock.

This may help with github issue #3380, and may be backported to 3.3.
2026-05-20 16:23:30 +02:00
Christopher Faulet
482b6763a3 BUG/MEDIUM: htx: Alloc a chunk of right size in htx_replace_blk_value()
Since support for large buffers was added, we must be careful when chunks
are allocated. Indeed, depending on the context a large chunks may be
required if data are copied from a large buffer.

In htx_replace_blk_value() function, when a defragmentation is necessary,
the data to be replaced are copied to a chunk before the
defragmentation. However, I forgot to get large chunk when necessary by
calling alloc_trash_chunk_sz() instead of alloc_trash_chunk(). Because of
this issue, it is possible to copy data to a too small chunk, leading to a
crash.

So let's fix the issue.

Thanks to Vincent55 for finding and reporting this.

No backport needed.
2026-05-20 16:21:02 +02:00
Christopher Faulet
2a87629052 BUG/MEDIUM: applet: Fix transfer of HTX data to the applet
appctx_htx_snd_buf() function is relying on htx_xfer() function to transfer
HTX blocks when a swap of buffers is not possible. However, it was not
properly using this function.

Indeed, originally htx_xfer() was designed to transfer blocks with a limit,
the <count> parameter, which included the blocks payload and the
meta-data. It was aligned with all calls, except for the transfer of HTX
data to the applet, in appctx_htx_snd_buf() function. In that case, the
<count> parameter is the amount of data forwarded by the stream to the
applet. So meta-data are not included.

Thanks to the previous commit ("MEDIUM: htx: Improve htx_xfer API to not count
HTX meta-data"), it is now possible to instruct htx_xfer() function that
<count> parameter does not include the meta-data.

Because of this bug, crashes can be experienced when transferring HTX data
to an applet. At first glance, lua HTTP applets and the http client are
concerned.

Stable versions from 3.3 to 3.0 are also affected. But this patch cannot be
backported as is because htx_xfer() function does not exist on these
versions.

Thaks to Yon Harlicaj for finding and reporting this.
(https://x.com/nvmb3r - https://www.linkedin.com/in/eljon-harlicaj/)
2026-05-20 16:21:02 +02:00
Christopher Faulet
56e7f8ef31 MEDIUM: htx: Improve htx_xfer API to not count HTX meta-data
This patch add the ability to the htx_xfer() function to transfer data
without acounting the meta-data. By default, the <count> variable includes
the meta-data. But by setting the flag HTX_XFER_NO_METADATA, It is possible
to transfer HTX blocks without count meta-data. In that case, <count> will
not contain the blocks meta-data and the return value will not include them.
2026-05-20 16:21:02 +02:00
Christopher Faulet
99d48c3aec BUG/MINOR: htx: Fix value of HTX_XFER_HDRS_ONLY flag
HTX_XFER_* flags must be declared as a bitfield. However, value of
HTX_XFER_HDRS_ONLY was set of 0x03 while it should be 0x04. So let's fix it.

This patch must be backported where the htx_xfer() function was backported
(5ead611cc "MEDIUM: htx: Add htx_xfer function to replace htx_xfer_blks").
2026-05-20 16:21:02 +02:00
Amaury Denoyelle
47a61eb86d BUG/MINOR: mux_quic: do not exceed stream.max-concurrent on backend side
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
Fix usage of stream.max-concurrent QUIC setting on the backend side.
Contrary to frontend connections, this limit must be enforced by QUIC
MUX directly. This is necessary as the peer may allow a larger number of
concurrent streams via its flow control.

First, QUIC TP initial max bidi streams value is now set to 0. This is
fine as only the HTTP/3 client is expected to open bidirectional
streams.

The most important changes is performed in qcm_avail_streams(). The
value first depends on the peer flow control. Now, it is further reduced
if necessary to not exceed the configured BE stream.max-concurrent.

Note that this new behavior may further increases current limitation on
QUIC BE reuse when a QCS instance is kept while its upper stream layer
is detached. In this case there is a risk that the connection is not
reinserted in the correct server pool, as an idle or avail one.

This is a breaking change as BE stream.max-concurrent keyword setting
meaning is changed in effect. However, this does not necessitate extra
warnings as the previous usage was in effect useless. Furthermore, QUIC
on the backend side is still considered as experimental.

This can be backported up to 3.3.
2026-05-20 14:42:03 +02:00
Amaury Denoyelle
b7c607e207 DOC: fix typo on QUIC stream.max-concurrent reference
Add a missing "fe" prefix for the QUIC keyword reference in
tune.streams-elasticity documentation.
2026-05-20 13:40:53 +02:00
Olivier Houchard
05e65489cb BUG/MEDIUM: servers: Don't forget to set srv_hash when needed
Commit 8aa854ab26a7daa613a17548f1fe1d0adb8cf61b made it so we'd store
the hash corresponding to the server parameters, so that we could detect
if we're still talking to the same server, and not use those parameters
if not.
However, when updating those parameters, we forgot to store the new
hash, which would result in the new parameters never be used, and
breakling 0RTT.
Fix that by properly update the hash when needed.
This should be backported when 8aa854ab26a7daa613a17548f1fe1d0adb8cf61b
is backported.
2026-05-20 12:32:19 +02:00
Willy Tarreau
b9acb4415f MEDIUM: startup: warn when chroot is not set for root
We're still regularly seeing insecure configs where chroot is missing.
Now that we have "chroot auto", there's no excuse for not knowing where
to chroot, so let's detect that we're starting as root, detect that the
process is allowed to chroot (i.e. no capability issue, or some hardened
containers), and if no chroot is set, let's emit a warning explaining how
to silence it, i.e. either "chroot auto" or "chroot /".

Most likely we'll start using "chroot auto" by default in 3.5 if no
usability issue is reported.
2026-05-20 11:51:45 +02:00
Willy Tarreau
3c35e7f137 MINOR: startup: do not execute chroot() when "/"
We'll recommend to use "chroot /" to explicitly disable chroot, however
there might be configurations where it would cause problems to just issue
the syscall (typically some hardened containers), so let's make sure that
"chroot /" is a nop in this case.
2026-05-20 11:46:43 +02:00
Willy Tarreau
d142c7f421 BUILD: traces: add USE_TRACE allowing to disable traces
This reduces the total code size by 6-10% and speeds up the build a
bit. It can be further reduced by disabling the trace decoding code
inside certain subsystems like muxes. But at least like this it will
help users on small systems to reduce the footprint when not needed
by explicitly passing USE_TRACE=0 (they remain enabled by default).
2026-05-20 11:46:43 +02:00
Willy Tarreau
8dd31dcd07 BUILD: traces: set a few __maybe_unused on vars used only for traces
Certain variables are used only for traces in mux, ssl and quic
essentially, and disabling traces emits warnings, so let's mark
them appropriately.
2026-05-20 11:46:43 +02:00
Amaury Denoyelle
f521581922 BUG/MINOR: prevent conn leak in case of xprt_qmux init failure
In case of XPRT_QMUX init failure on the frontend side, the connection
must immediately be released. This is not the case on the backend side
as a stream can supervize the connection lifetime.

This patch performs the connection free via conn_complete_session(). As
conn is flagged with CO_FL_ERROR, this will automatically fail and
invoke session_kill_embryonic(), which ensures the session and its
connection are both freed as wanted in this case.

No need to backport.
2026-05-20 11:13:56 +02:00
Olivier Houchard
de3f245df0 BUG/MEDIUM: servers: Store the connection hash with the parameter cache
When we store the negociated server parameters, such as the ALPN, also
store the calculated hash with the connection. If it is different, as
can happen because the IP address is different because set-dst was used,
we certainly do not want to reuse the information in the cache,
otherwise we could end up using the wrong ALPN and mux.
That means we already have to calculate the hash in connect_server()
now, while before we would not do it for Websockets, if we could not do
connection reuse, as that's all the hash was used for.

This should fix Github issue #3386

This should be backported as far as 3.2.
2026-05-20 10:29:22 +02:00
Amaury Denoyelle
e139dd90e3 MAJOR: mux_quic: support stream elasticity during connection lifetime
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
qcc_release_remote_stream() is called each time a remote stream is
closed. Flow control accounting is updated and when necessary, a
MAX_STREAMS_BIDI frame is prepared to allow the peer to initiate new
streams.

This patch extends stream elasticity features with the QUIC bidirection
stream flow control mechanism. The announced value can now be possibly
reduced depending on conn_calc_max_streams().

The first step is to decrement closed streams from the global committed
extra streams total. This must be performed conn_calc_max_streams() to
ensure the calculation will be valid.

Then, there is two cases depending on conn_calc_max_streams() result. If
the value is less than the peer still remaining stream window, nothing
more is performed. If the opposite case, flow control must be increased
and a MAX_STREAMS_BIDI frame is prepared, with the value adjusted to not
exceed the stream elasticity limit. Global extra streams total is then
finally incremented.

This calcul also ensures that when all streams are closed, global extra
streams accounting operations are decremented by 1, as a connection
always has access to one stream which is excluded from the global total.

Note that if stream elasticity is not active, flow control increases
principle is unchanged and remains statically performed.

This patch is labelled as major as it complexifies bidirectional stream
flow control mechanisme. This is a sensitive operation as there is a
risk of connection freeze if flow control updates are inadvertently
skipped.
2026-05-20 09:52:50 +02:00
Amaury Denoyelle
89f3975acc MINOR: mux_quic: define ms_bidi_rel QCC member
Add a new QCC member <ms_bidi_rel>. This represents the number of
concurrent streams advertised similarly to ms_bidi, but as a relative
value.

This patch does not introduce any functional change. For now,
<ms_bidi_rel> will be equal to <ms_bidi_init>. However, with the
implementation of stream elasticity and dynamic adjustment for
concurrent max-streams-bidi, the former will be required to keep the
last advertised value.
2026-05-20 09:52:50 +02:00
Amaury Denoyelle
d21ec4c707 MINOR: quic: use stream elasticity value for initial advertisement
When stream elasticity is active, the maximum number of concurrent bidi
streams advertised via transport parameters is now reduced depending on
the connection load. This is implemented via conn_calc_max_streams()
which returns the value to use.

This is not applied on listeners with enabled 0-RTT. Indeed, for such
connections, clients are expected to reuse the previously seen transport
parameters. The server on the other hand must not decrease several
values on the newly advertised params, in particular for the maximum
number of concurrent bidi streams. The simplest way to prevent 0-RTT
failure is to not mix stream elasticity with it.

Note that the 0-RTT limitation is only applied for the initial value :
during the connection lifetime, stream elasticity can still be used by
the MUX to dynamically reduce the stream window. This will be
implemented in a future patch.
2026-05-20 09:52:50 +02:00
Amaury Denoyelle
e4adba6e64 MINOR: mux_quic: implement basic committed_extra_streams accounting
Account QUIC frontend connections into committed_extra_streams when
stream elasticity setting is active. This is performed in QCC init and
release functions.

This patch has no impact on QUIC subsystem for now. Connections will
still allow a static number of concurrent streams based on
tune.quic.fe.stream.max-concurrent. However, this has a direct
repercussion on H2 subsystem, as a higher count of QUIC connections will
reduce the concurrent streams allowed there.
2026-05-20 09:52:50 +02:00
Amaury Denoyelle
33c8270903 OPTIM: h2: do not update committed streams if elasticity disabled
When streams-elasticity is enabled in the configuration, H2 mux is
responsible to update the global committed_extra_streams value.

Adjust these operations to ensure they are skipped if streams-elasticity
is disabled, which is the current default. This prevents unnecessary
atomic operations in this case.

No need to backport unless streams-elasticity feature is picked in older
releases.
2026-05-20 09:52:50 +02:00
Amaury Denoyelle
ad3562fea1 MINOR: h2: explain committed_extra_streams dec on h2_init() error
h2_init() is now responsible to increment committed_extra_streams for
new frontend connections, in relation to the newly implemented
stream-elasticity feature. In case of an early error, a mirroring
decrement is executed on fail_stream label.

However, for now this error label can only be selected via BE conns. In
fact, it's not yet possible for h2_init() to fail after the extra
streams increment.

However, the decrement operation is kept to prevent any omissions in
case of future evolutions of h2_init() error path. To prevent reporting
of a possible dead code, add an extra comment which summarizes the
situation.
2026-05-20 09:52:50 +02:00
Maxime Henrion
641fe4f119 MEDIUM: startup: add automatic chroot feature
It is now possible to use "chroot auto" in the configuration. This lets
haproxy create an anonymous (cleaned up after the process terminates)
and read-only directory for chroot. This directory is created in /tmp;
we might want to support creating it in a different directory in the
future, either by respecting $TMPDIR or by allowing an optional
directory after the "auto" keyword.
2026-05-20 08:34:24 +02:00
Maxime Henrion
2d2980408f MINOR: startup: support unprivileged chroot if possible
Try to use unshare(CLONE_NEWUSER) if available so we can have a chroot
as an unprivileged user. This is a Linux-only mechanism.
2026-05-20 08:34:17 +02:00
Willy Tarreau
7004bb3b8c MINOR: backend: support hash-key guid for a stabler distribution
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
When server fleets are constantly updated, using a stable distribution
across a bunch of load balancers can be convenient. The addr and port
already provide a bit of this but for situations were addresses might
differ between sites or change dynamically this does not work. The guid
is perfect for this because by definition it's supposed to designate a
single server and be unique. So when two servers anywhere have the same,
the tool that provisionned them promises that they are the same server.

So here we introduce "hash-key guid" which performs a 32-bit hash on
the GUID value. When no guid is provided, a fallback is performed on
ID, as is done for other keys.
2026-05-19 19:11:25 +02:00
Willy Tarreau
a59e6e5efd MINOR: server: support hash-key id32 for a cleaner distribution
The "id" hash-key scales the ID by a factor of 16 that tries to leave
room between the nodes on the 32-bit space to permit smooth weight
variations (e.g. during slowstart). However this does not deal well
with overlaps between server IDs. For example, assigning IDs that are
only multiples of 256 million to 16 servers yields traffic only on
one since in practice they all have the same 28 lower bits.

The new "id32" hash key bridges this gap by using the full 32-bit ID
of the server as the key. On the other hand, the user must be careful
not to switch the hash function to "none" when using incremental IDs
because in this case they might be very poorly distributed. But this
can be convenient for automated provisionning systems which assign
IDs themselves, as the full 32 bits are used now.
2026-05-19 19:11:25 +02:00
Willy Tarreau
cb5d98c495 BUG/MINOR: backend: fix balance hash calculation when using hash-type none
The "hash-type xxx none" is broken for keys that are not in type string
because the sample fetch call casts them to SMP_T_BIN, that tends to
preserve the original format (integers, IP addresses etc), but the
gen_hash() function in case of BE_LB_HFCN_NONE expects to read a string
representing a number, that it parses to retrieve the value, and just
fails on many binary types. For example, the following just always
returns key 0:

    balance hash rand()
    hash type consistent none

An ugly workaround is to make sure the expression returns a string, for
example this:

    balance hash rand(),concat()
    hash type consistent none

In order to fix most cases here, we force the conversion to type string
when using BE_LB_HFCN_NONE, but a better approach would require a larger
rework and split gen_hash() or change it to accept an integer as well,
so that the caller could cast to SMP_T_INT for BE_LB_HFCN_NONE and pass
the resulting number already parsed with the least information loss. In
this case even IPv4 addresses would be preserved.

The current approach at least addresses the initially envisioned use
cases, and the limitations have been added to the doc. This can be
backported to 3.0 though it's not really important.
2026-05-19 19:11:25 +02:00
Willy Tarreau
f2bf3483ba BUG/MINOR: server: accept server IDs above 2^31 and clarify error message
Due to the check of the stored value instead of the parsed one, it was not
permitted to use server IDs above 2^31 while they are perfectly possible.
Let's refine the parsing and also update the error message to indicate the
range. The doc was also refined to reflect the relation with hash-key.

This may be backported though it wouldn't have any effect on working
configs.
2026-05-19 19:11:25 +02:00
Amaury Denoyelle
f2b152c95e MEDIUM: ssl: allow h3/QMux negotiation without explicit proto
Implements automatic selection of QMux MUX if "h3" ALPN has been
negotiated on top of TCP/SSL.

The first part of this change is to define "alpn" member of
mux_proto_list. This is necessary so that conn_get_best_mux_entry() can
select it when "h3" has been chosen. As a side-effect, this also
automatically sets a default ALPN to "h3" for bind lines with "proto
qmux".

The most important change is to adapt the SSL layer. On handshake
completion, the eligible MUX is retrieved via conn_select_mux_fe/be()
functions. If xprt_qmux is required by it, MUX init is delayed and QMux
handshake is started first.

This last change is necessary as connection flags CO_FL_QMUX_RECV/SEND
are only set if "proto qmux" is explicitely set. In case xprt_qmux is
activated via pure ALPN negotiation, these flags are also set on
xprt_qmux_init(). This is mandatory to ensure emission/reception of QMux
transport parameters will be performed as expected.
2026-05-19 18:40:50 +02:00
Amaury Denoyelle
e30bcfe6cd MINOR: proxy/server: reject TCP ALPN h3 without experimental
Add a postparsing check on TCP ALPN bind and server setting. An error is
reported if the token "h3" is present and expose-experimental-directives
is not globally activated. This ensures that QMux protocol won't be
selected if experimental features are not explicitely requested.

The check is not performed though if "proto qmux" is explicitely
defined, as this setting already checks for experimental support.

Currently, it's not possible to activate QMux without any explicit
"proto qmux" config. However, this will be implemented in a next patch,
so this check will become necessary.
2026-05-19 18:40:50 +02:00
Amaury Denoyelle
879c78c909 MINOR: connection/mux_quic: add MUX <init_xprt> field for QMux handshake
The first part of this patch defines a new mux_proto_list field named
<xprt_init>. This allows to define an extra XPRT layer which should be
activated first prior to the MUX creation both on frontend and backend
sides.

This is immediately used for QMux mux_proto_list to require XPRT_QMUX
handshake. With this change, activation of QMux connection flags in
session_accept_fd() and connect_server() are adjusted to take into
account <init_xprt> field. This approach is much more evolutive than
relying on the previous MUX name.

Change in connect_server() will also be necessary to support QMux
activation on a TCP server with h3 ALPN without explicit "proto qmux".
This guarantees that MUX initialization is delayed after QMux handshake.
2026-05-19 18:40:50 +02:00
Amaury Denoyelle
356f1ab5d7 MINOR: connection: define conn_select_mux_be()
This patch is similar to the previous one but this time for backend
connections. The MUX selection code is directly extracted from
conn_install_mux_chk() and conn_install_mux_be().
2026-05-19 18:40:46 +02:00
Amaury Denoyelle
86ffbaa0f5 MINOR: connection: define conn_select_mux_fe()
Define a new function conn_select_mux_fe().

The objective is to have a preliminary function to determine the MUX
which will be used without initializing it. This will be useful for MUX
which relies on a specific XPRT handshake prior to its startup, which is
the case for QMux protocol.

The code of conn_select_mux_fe() is identical to the beginning of
conn_install_mux_fe() with a similar MUX selection logic. However,
connection MUX initialization is not performed in this case. In a future
patch, both functions should be merged together to reduce code
duplication.
2026-05-19 18:33:54 +02:00
Olivier Houchard
6aab6d4e98 MEDIUM: connections: Use both mux_proto and alpn to pick a mux
In conn_get_best_mux() and conn_get_best_mux_entry(), the mux name was
provided sometimes based on the "proto" directive, sometimes based on
the ALPN, but in any case, it was compared again the mux_proto_list
mux_proto field. This is not correct, as ALPN can be different from the
internal mux_proto. So enhance those functions so that they wll accept
an ALPN as well. If a mux_proto is provided, that will be used, if not,
and if an ALPN is provided, then that will be used, and compared against
the ALPN provided by the mux, if any.
2026-05-19 18:33:54 +02:00
Olivier Houchard
022681eca2 MINOR: mux: Rename the "token" from mux_proto_list to mux_proto
In struct mux_proto_list, rename the "token" field to "mux_proto". That
field should only be used to match the name provided in the "proto"
directive, and it will be soon.
This should be a no-op.
2026-05-19 18:33:54 +02:00
Amaury Denoyelle
50354f929d BUG/MINOR: httpclient-cli: fix uninit variable in error label
The following patch fixes a leak in case of httpclient_start() failure
in the httpclient_cli code by adding httpclient_destroy() call on error
path.

  c53256adbc
  BUG/MINOR: httpclient-cli: Destroy http-client context if failing to start it

However, error label may be selected prior to httpclient allocation if
CLI arguments are incorrect. This can cause a crash due to a deferencing
of an uninitialized variable. This has been detected via a compilation
error :

  src/httpclient_cli.c: In function 'hc_cli_parse':
  src/httpclient_cli.c:162:2: error: 'hc' may be used uninitialized in this function [-Werror=maybe-uninitialized]
    162 |  httpclient_destroy(hc);
        |  ^~~~~~~~~~~~~~~~~~~~~~

This must be backported along the above patch, which is scheduled up to
the 2.6 release.
2026-05-19 18:33:13 +02:00
Christopher Faulet
6f6bf3fecc CLEANUP: haterm: Remove "(too old kernel)" from warning message during init
During initialization of the haterm master pipe, If its size is limited
(lower than the configured one * 5/4), a warning is emitted. In this
warning, it is specified this happened because the kernel is too old. But it
is unrelated. So let's remove this part.
2026-05-19 17:50:50 +02:00
Christopher Faulet
1279bd80e9 MINOR: haterm: Don't init haterm master pipe if not used
There is no reason to initialize the haterm master pipe if haterm is not
used. So now, it is only performed if a non-disabled haterm frontend is
found. To do so, in addition to test the proxy's flags and capabilities, we
also check if "stream_new_from_sc" points on "hstream_new".
2026-05-19 17:50:50 +02:00
Christopher Faulet
b74b5289c8 BUG/MINOR: h1: Don't mask websocket protocol if multiple protocols used
During H1 message parsing, the Upgrade header values are checked to detect
"websocket" prototol, to properly handle websocket upgrades between H1 and
H2 and to possibly reject messages if mandatory headers are missing.

However, the flag is reset for each new Upgrade header and the information
may be lost. So never reset it.

This patch must be backported as far as 2.4.
2026-05-19 17:50:50 +02:00
Christopher Faulet
8dd49dfaba BUG/MEDIUM: h1: Skip all h2c values from Upgrade headers during parsing
During the H1 message parsing, the Upgrade header values are checked to
detect "h2c" and "h2" tokens and skip them. To do so, we rely on
H1_MF_UPG_H2C flag, set during the parsing. And during the request
post-parsing, if this flag was set, all Upgrade headers are removed.

This was fixed by the commit 7b89aa5b1 ("BUG/MINOR: h1: do not forward h2c
upgrade header token").

However, there are two issues here and the commit above must be refined.
First, the flag is reset for each new Upgrade header. So "h2c" or "h2"
tokens will be properly detected if all tokens are set on the same Upgrade
header. But if splitted on several headers, previously detected tokens will
be hidden by a next ones.

Concretly, the following will be properly caught

  Connection: upgrade
  Upgrade: foo, h2c, bar

But then following not:

  Connection: upgrade:
  Upgrade: foo, h2c
  Upgrade: bar

Then, when a "h2c" or "h2" token is finally reported, all Upgrade headers
are removed, regardless other tokens.

So, to fix the both issues, everything is now handled during the message
parsing by skipping "h2c" and "h2" tokens, rebuilding the Upgrade header
value without then offending tokens. The same was already performed for the
Connection header, to skip "keep-alive" and "close" value. So it is not a so
fancy change.

Thanks to this change, it is no longer necessary to handle H1_MF_UPG_H2C
during the request post-parsing. And in fact, this flag is no longer
necessary. So let's remove it too.

Thanks to Vincent55 for finding and reporting this.

This patch must be backported as far as 2.4.
2026-05-19 17:50:50 +02:00
Christopher Faulet
c53256adbc BUG/MINOR: httpclient-cli: Destroy http-client context if failing to start it
When the call to httpclient_start() failed, it is the caller responsibilty
to destroy the http-client context by calling httpclient_destroy(). It is
performed at several places but it was missing in the httpclient_cli
code. So let's fix it.

This patch must be backported as far as 2.6. On 3.2 and lower, it must be
applied on http_client.c.
2026-05-19 17:50:50 +02:00
Christopher Faulet
18c5cd6674 BUG/MINOR: server: Properly handle init-state value during haproxy startup
Unlike stated in the configuration manual, the server 'init-state' parameter
was not evaluated during haproxy startup/reload. After a review, it appeared
there were also issues if combined with the 'track' parameter. In addtition,
this parameter was only evaluated when health-checks were enabled for the
server, leading to unexpected behavior if the serve settings are dynamically
changed via the CLI.

To fix those issues, behavior of the 'init-state' parameter was slightly
adapted. It is always evaluated, even when there is no running health-checks
for the server. An error is reported if the 'track' parameter is also
defined. Both cannot work together.

In addition, the "none" state was introduced to be able to restore the
default behavior. It will be especially useful when the parameter is
inherited from a 'default-server' directive.

This patch should fix the issue #3298. It must be backported as far as 3.2.
2026-05-19 17:50:50 +02:00
Remi Tricot-Le Breton
b786eaf1b1 BUG/MINOR: jws: Add missing return value check (EVP_PKEY_get_bn_param)
Two calls of 'EVP_PKEY_get_bn_param' did not have their return value
checked.

This patch can be backported up to 3.2.
2026-05-19 15:21:26 +02:00
Willy Tarreau
307294b30a BUG/MINOR: jws: fix OpenSSL 3.0 version check from > to >=
Three #if directives used > 0x30000000L which excluded OpenSSL 3.0.0
exactly from the modern code path, treating it as pre-3.0. Changed all
three to >= 0x30000000L to match jwe.c and openssl-compat.h conventions.

This affects EC key thumbprint generation, RSA JWK generation, and
JWS algorithm detection for OpenSSL 3.0.0.
2026-05-19 15:21:24 +02:00
Willy Tarreau
0284be5456 BUG/MEDIUM: limits: properly account for global.maxpipes in compute_ideal_maxconn()
Starting a config with maxpipes and no maxconn always ended up in error
because the number of FDs needed for pipes was not deduced from the total
number of FDs when calculating maxconn, and was later found to exceed the
total number of allocatable FD during final checks.

When global.maxpipes is set, it must be used during compute_ideal_maxconn()
so that it's properly deduced.

Without this, just having "maxpipes 500" in a config prevents it from
starting. With the fix, it properly starts with a maxconn adjusted
depending on the number of splice-enabled proxies.

This should be backported, theoretically everywhere, but preferably
progressively. The following config should fail on affected versions
and load with fixed ones:

   global
        maxpipes 500

   frontend srv1
        bind :8001
2026-05-19 15:19:23 +02:00
Willy Tarreau
11bad01760 MINOR: proxy: remove the experimental status on dynamic backends
As initially planned, if no trouble was reported on dynamic backend
commands on the CLI, the experimental status could be dropped before the
release. The feedback was not very broad, but was conclusive in that the
operations work as expected and the current syntax can be preserved even
for future evolutions. So we can drop the experimental status.
2026-05-19 14:56:45 +02:00
Willy Tarreau
b59fe471a5 DOC: config: further clarify that resolvers "default" exists
It was explained in the general presentation of resolvers but not in
the "resolvers" keyword description itself, which might be where users
could be looking for that info, so let's quickly repeat that info there.
2026-05-19 14:48:27 +02:00
Willy Tarreau
29b9da7821 CLEANUP: jwe: fix theoretical overflow in AAD length calculation
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
The expression items[JWE_ELT_JOSE].length << 3 performs the shift on an
unsigned int (32-bit) before being cast to uint64_t instead of after.
This means that we don't cover for a possible overflow (which would
never happen as it would need a header length beyond 512MB). At least
fixing it will avoid code check reports.
2026-05-18 18:52:28 +02:00
Willy Tarreau
d4a4be6c34 BUG/MINOR: jwt: fix possible memory leak in convert_ecdsa_sig() error path
The allocated ec_R and ec_S were not released in case one of the two
would fail to be allocated/created, and would cause a memory leak. Let's
add the missing BN_free(). This may be backported to 2.4.
2026-05-18 18:50:30 +02:00
Willy Tarreau
bbc41785d9 CLEANUP: tcpcheck: mention that we're a bit far for a sync errno
The collection of errno in tcpcheck_eval_connect() and tcpcheck_main()
is quite far from the production location, and the risk of having a
zero errno is definitely not null. Tests show that this works, so
better not try to fix something not broken, but at least place a
comment there indicating that it's not necessarily super-reliable.
This would need to be revisited the day we finally store errno in
the connection.
2026-05-18 18:47:41 +02:00
Willy Tarreau
3b825d2745 BUG/MINOR: check: properly report errno in chk_report_conn_err()
When in 2.2, with commit c8dc20a825 ("BUG/MINOR: checks: refine which
errno values are really errors."), errno reporting was refined, an
extra check was added before calling retrieve_errno_from_socket(), and
by mistake the test on !errno got inverted so that we only call the
function to retrieve the error from the socket when errno is set!
The first test in the function detects it and returns without changing
anything, so this didn't have much effect, however when errno is not
set (certain call places purposely pass zero so that getsockopt() is
used), this wasn't called so the error wasn't reported. Apparently it
only happened when called from process_chk_conn() after an async
error was detected, so probably just cases where POLLERR is reported,
which remains infrequent.

Let's fix the direction of this flag. It can be backported if needed
but it's unlikely anyone really noticed.
2026-05-18 18:40:37 +02:00
Willy Tarreau
3da2b63274 BUG/MINOR: sock: store the connection error status
When an async connect() fails in sock_conn_check(), it returns an errno
that will not be retrieved later by a subsequent getsockopt(SO_ERROR).
The problem is that this errno is then definitely lost. This is visible
in the 4be_1srv_smtpchk_httpchk_layer47errors regtest that fails on
certain systems (e.g. glibc 2.31 on arm32 running Linux 6.1), where the
connect() error is systematically lost and the "Connection refused" is
never seen in the check status. It also matches a few random reports of
the past indicating that the connection error was sometimes not reported
in the stats page in front of a down server.

Ideally we should store errno in connections as soon as the error is
seen. However this would require significant changes that are not
acceptable yet for 3.4 nor stable releases. A more acceptable fix is to
make use of the extra CO_ER_* flags set by conn_set_errno() as soon as
the error is detected. This will recognize a sufficiently large number
of errors and the check status will report them (here we'll have
"ECONNREFUSED" in the check). Note that on systems where the error is
seen synchronously, we can have "ECONNREFUSED (Connection refused)",
but this is not a problem.

This fix adds the missing conn_set_errno() call to sock_conn_check(),
that is thus sufficient to catch this error. In addition, the two
affected regtests were updated to search for ECONNREFUSED here.

This might be backported to older releases if users request it, but it
is probably not necessary.
2026-05-18 18:16:25 +02:00
Willy Tarreau
fdb569c2ea REGTESTS: quic/issuers_chain_path: do not forget to enable QUIC compat mode
This test is compatible with QUIC_OPENSSL_COMPAT but the "limited-quic"
directive was not set, making it fail on older libs with no QUIC support
despite being declared as compatible.
2026-05-18 18:01:53 +02:00
Willy Tarreau
fd31df765f REGTESTS: do not run quic/tls13_ssl_crt-list_filters in quic openssl compat mode
This test uses the the backend, it fails in QUIC_OPENSSL_COMPAT so let's
disable it in this case, like other similar tests.
2026-05-18 18:01:53 +02:00
Willy Tarreau
b44d60eb42 CLEANUP: stick-table: uniformize the different action_inc_gpc*()
Some checks failed
Contrib / admin/halog/ (push) Has been cancelled
Contrib / dev/flags/ (push) Has been cancelled
Contrib / dev/haring/ (push) Has been cancelled
Contrib / dev/hpack/ (push) Has been cancelled
Contrib / dev/poll/ (push) Has been cancelled
VTest / Generate Build Matrix (push) Has been cancelled
Windows / Windows, gcc, all features (push) Has been cancelled
VTest / (push) Has been cancelled
While action_inc_gpc1() explicitly checks if s->stkctr or sess->stkctr
are set since 2.8 with commit 6c0117168 ("MEDIUM: stick-table: set the
track-sc limit at boottime via tune.stick-counters"), action_inc_gpc0()
and the generic action_inc_gpc() still stuck to the old approach of not
checking them, causing confusion when reviewing the code.

Upon closer inspection, the only case where the pointer may be NULL is
when global.tune.nb_stk_ctr is zero, which happens when the global
section contains "tune.stick-counters 0". However in this case, the
config parser "parse_inc_gpc()" will reject any reference to any stick
counter, so in theory there is no problem.

Regardless, the difference of treatment between sibling functions remains
confusing and the check is cheap, so let's generalize it, it will save a
future reader from the need to inspect stream_new() and session_new().
2026-05-17 23:10:27 +02:00
Willy Tarreau
015933794e BUG/MINOR: session/trace: use distinct flags for SESS_EV_END and _ERR
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
Session traces were brought in 3.1 by commit abb07af67 ("MINOR:
session/trace: enable very minimal session tracing") though there was
an issue, because SESS_EV_END and SESS_EV_ERR have the same value (it's
a copy-paste mistake).

This can be backported to 3.2.
2026-05-16 20:29:40 +02:00
Willy Tarreau
4519906c70 DOC: internal: add a few rules about internal core principles
The new file core-principles.txt quickly enumerates a number of rules
and invariants across the project. These can be used as quick reminders
as well as basic rules for reviews. It's still lacking a lot of info but
should be a good start.
2026-05-16 20:12:32 +02:00
Willy Tarreau
2f88b4bc4b CLEANUP: address a few typos and copy-paste errors in httpclient and dns
Some checks are pending
Contrib / admin/halog/ (push) Waiting to run
Contrib / dev/flags/ (push) Waiting to run
Contrib / dev/haring/ (push) Waiting to run
Contrib / dev/hpack/ (push) Waiting to run
Contrib / dev/poll/ (push) Waiting to run
VTest / Generate Build Matrix (push) Waiting to run
VTest / (push) Blocked by required conditions
Windows / Windows, gcc, all features (push) Waiting to run
These are either typos or copy-paste mistakes (mostly mouse-induced
spaces instead of tabs for dns.c).
2026-05-15 18:25:13 +02:00
Willy Tarreau
9ebb00e673 CLEANUP: proxy: fix duplicate declaration of cli_find_frontend in proxy.h
The function cli_find_frontend was declared twice identically at lines 98-99
of include/haproxy/proxy.h. The second declaration should have been for
cli_find_backend, which is defined in src/proxy.c and used in several places
but was missing from the header's exported symbols.

This is a simple copy-paste mistake where line 99 duplicated line 98 verbatim
instead of declaring cli_find_backend.
2026-05-15 18:24:57 +02:00
Willy Tarreau
3460626148 BUG/MINOR: resolvers: fix missing task_idle destruction in resolvers_destroy()
When destroying a stream-based DNS nameserver, task_req and task_rsp
were destroyed but task_idle was missed, causing a task object leak.
This doesn't necessarily have to be backported since it's only upon
exit that it is visible.
2026-05-15 18:19:41 +02:00
Willy Tarreau
6cbcb4f9db BUG/MINOR: resolvers: fix leaked fields on cfg_parse_resolvers() error paths
cfg_parse_resolvers() has many error paths on allocation failure when
parsing "nameserver". These paths handle their own cleanup instead of
centralizing it. The result is that some errors paths leak some fields.
The most complex ones are the strdup() failures which require to check
for stream or dgram to figure what to free. These can be detected via
ASAN on a dummy strdup() allocation failure:

  Indirect leak of 131080 byte(s) in 1 object(s) allocated from:
      #0 0x7f0b7ed1f0ab in malloc (/usr/lib64/libasan.so.8+0x11f0ab)
      #1 0x000000c73e19 in dns_ring_new src/dns_ring.c:59
      #2 0x000000af1848 in dns_dgram_init src/dns.c:480
      #3 0x000000922005 in cfg_parse_resolvers src/resolvers.c:3792
      #4 0x00000089d33e in parse_cfg src/cfgparse.c:2202
      #5 0x0000009e0a39 in read_cfg src/haproxy.c:1142
      #6 0x000000447e8c in main src/haproxy.c:3474
      #7 0x7f0b7e02ad13 in __libc_start_call_main (/lib64/libc.so.6+0x2ad13)
      #8 0x7ffd35f1531c  ([stack]+0x2031c)

  Indirect leak of 304 byte(s) in 1 object(s) allocated from:
      #0 0x7f0b7ed1ea23 in calloc (/usr/lib64/libasan.so.8+0x11ea23)
      #1 0x000000af1681 in dns_dgram_init src/dns.c:468
      #2 0x000000922005 in cfg_parse_resolvers src/resolvers.c:3792
      #3 0x00000089d33e in parse_cfg src/cfgparse.c:2202
      #4 0x0000009e0a39 in read_cfg src/haproxy.c:1142
      #5 0x000000447e8c in main src/haproxy.c:3474
      #6 0x7f0b7e02ad13 in __libc_start_call_main (/lib64/libc.so.6+0x2ad13)
      #7 0x7ffd35f1531c  ([stack]+0x2031c)

  Indirect leak of 104 byte(s) in 1 object(s) allocated from:
      #0 0x7f0b7ed1ea23 in calloc (/usr/lib64/libasan.so.8+0x11ea23)
      #1 0x000000921f83 in cfg_parse_resolvers src/resolvers.c:3772
      #2 0x00000089d33e in parse_cfg src/cfgparse.c:2202
      #3 0x0000009e0a39 in read_cfg src/haproxy.c:1142
      #4 0x000000447e8c in main src/haproxy.c:3474
      #5 0x7f0b7e02ad13 in __libc_start_call_main (/lib64/libc.so.6+0x2ad13)
      #6 0x7ffd35f1531c  ([stack]+0x2031c)

  Indirect leak of 64 byte(s) in 1 object(s) allocated from:
      #0 0x7f0b7ed1f0ab in malloc (/usr/lib64/libasan.so.8+0x11f0ab)
      #1 0x000000c73e09 in dns_ring_new src/dns_ring.c:55
      #2 0x000000af1848 in dns_dgram_init src/dns.c:480
      #3 0x000000922005 in cfg_parse_resolvers src/resolvers.c:3792
      #4 0x00000089d33e in parse_cfg src/cfgparse.c:2202
      #5 0x0000009e0a39 in read_cfg src/haproxy.c:1142
      #6 0x000000447e8c in main src/haproxy.c:3474
      #7 0x7f0b7e02ad13 in __libc_start_call_main (/lib64/libc.so.6+0x2ad13)
      #8 0x7ffd35f1531c  ([stack]+0x2031c)

  Indirect leak of 15 byte(s) in 1 object(s) allocated from:
      #0 0x7f0b7ed18e20 in strdup (/usr/lib64/libasan.so.8+0x118e20)
      #1 0x00000092203b in cfg_parse_resolvers src/resolvers.c:3798
      #2 0x00000089d33e in parse_cfg src/cfgparse.c:2202
      #3 0x0000009e0a39 in read_cfg src/haproxy.c:1142
      #4 0x000000447e8c in main src/haproxy.c:3474
      #5 0x7f0b7e02ad13 in __libc_start_call_main (/lib64/libc.so.6+0x2ad13)
      #6 0x7ffd35f1531c  ([stack]+0x2031c)

This should be completely reworked so that the cleanup is performed in
a central place, as the risk to get it wrong remains high.

This patch does the minimal changes to clean this up. It does not need
to be backported since it only triggers on boot OOM.
2026-05-15 18:07:50 +02:00
Willy Tarreau
677fdfe126 BUG/MINOR: resolvers: fix leaked dgram and dns_ring struct in parse_resolve_conf()
Some strdup() failures in parse_resolve_conf() do not release everything
due to the way the function is built, resulting in leaks on error that are
caught by ASAN:

  Direct leak of 304 byte(s) in 1 object(s) allocated from:
      #0 0x7fe74231ea23 in calloc (/usr/lib64/libasan.so.8+0x11ea23)
      #1 0x000000af1681 in dns_dgram_init src/dns.c:468
      #2 0x00000091cbbf in parse_resolve_conf src/resolvers.c:3559
      #3 0x00000092179e in cfg_parse_resolvers src/resolvers.c:3815
      #4 0x00000089d33e in parse_cfg src/cfgparse.c:2202
      #5 0x0000009e0a39 in read_cfg src/haproxy.c:1142
      #6 0x000000447e8c in main src/haproxy.c:3474
      #7 0x7fe74162ad13 in __libc_start_call_main (/lib64/libc.so.6+0x2ad13)
      #8 0x7ffc0a43e31f  ([stack]+0x2031f)

  Indirect leak of 131080 byte(s) in 1 object(s) allocated from:
      #0 0x7fe74231f0ab in malloc (/usr/lib64/libasan.so.8+0x11f0ab)
      #1 0x000000c73e19 in dns_ring_new src/dns_ring.c:59
      #2 0x000000af1848 in dns_dgram_init src/dns.c:480
      #3 0x00000091cbbf in parse_resolve_conf src/resolvers.c:3559
      #4 0x00000092179e in cfg_parse_resolvers src/resolvers.c:3815
      #5 0x00000089d33e in parse_cfg src/cfgparse.c:2202
      #6 0x0000009e0a39 in read_cfg src/haproxy.c:1142
      #7 0x000000447e8c in main src/haproxy.c:3474
      #8 0x7fe74162ad13 in __libc_start_call_main (/lib64/libc.so.6+0x2ad13)
      #9 0x7ffc0a43e31f  ([stack]+0x2031f)

  Indirect leak of 64 byte(s) in 1 object(s) allocated from:
      #0 0x7fe74231f0ab in malloc (/usr/lib64/libasan.so.8+0x11f0ab)
      #1 0x000000c73e09 in dns_ring_new src/dns_ring.c:55
      #2 0x000000af1848 in dns_dgram_init src/dns.c:480
      #3 0x00000091cbbf in parse_resolve_conf src/resolvers.c:3559
      #4 0x00000092179e in cfg_parse_resolvers src/resolvers.c:3815
      #5 0x00000089d33e in parse_cfg src/cfgparse.c:2202
      #6 0x0000009e0a39 in read_cfg src/haproxy.c:1142
      #7 0x000000447e8c in main src/haproxy.c:3474
      #8 0x7fe74162ad13 in __libc_start_call_main (/lib64/libc.so.6+0x2ad13)
      #9 0x7ffc0a43e31f  ([stack]+0x2031f)

  SUMMARY: AddressSanitizer: 131448 byte(s) leaked in 3 allocation(s).

Let's free the dgram and the dns ring. This can be backported though it's
not important as it only happens on OOM condition during boot.
2026-05-15 18:00:04 +02:00
Willy Tarreau
b15e9b1b29 BUG/MINOR: resolvers: report the expression error in the do-resolve() action parser
When an expression is used for do-resolve(), an error may be reported.
Unfortunately it was scratched and replaced by the do-resolve() error,
leaving no chance to know exactly what was wrong. Let's report the
contents of the error when available. It will indicate identifiers that
are not found or invalid ranges or types being used.

This can be backported to all versions.
2026-05-15 17:53:00 +02:00
Willy Tarreau
0c8c9b1c2a CLEANUP: resolvers: properly initialize the sample in resolv_action_do_resolve()
The sample used to pass the IP address only had its data, px, sess and
strm fields initialized before being passed to vars_set_by_name(). It
turns out that this latter one doesn't seem to touch ctx, flags nor opt
but nothing guarantees it. Let's at least initialize the fields properly
to avoid passing random garbage.

No backport is needed.
2026-05-15 17:51:58 +02:00
Willy Tarreau
bed842390f BUG/MINOR: proxy: use proxy_drop() in parse_new_proxy() error path
In parse_new_proxy(), when proxy_defproxy_cpy() fails, the error path used
ha_free(&curproxy) to release the partially constructed proxy. However, the
proxy was allocated via alloc_new_proxy() which performs significant setup:
  - setup_new_proxy() inserts it into the proxy_by_name tree (proxy_store_name)
  - It appends to the global proxies list (LIST_APPEND)
  - proxy_take() increments its refcount

Additionally, proxy_defproxy_cpy() may have allocated further resources
(strdup'd strings, compression structures, email alert fields, etc).

Using ha_free() only freed the proxy struct itself, leaving:
  - The proxy still registered in the name tree (dangling pointer)
  - The proxy still linked in the global proxies list
  - All strdup'd strings and other allocations leaked

This is visible with ASAN when causing random allocation errors:

  [NOTICE]   (27033) : haproxy version is 3.4-dev12-b15468-11
  [NOTICE]   (27033) : path to executable is ./haproxy
  [ALERT]    (27033) : config : parsing [/dev/stdin:5015] : proxy 'bk3': failed to duplicate tcpcheck preset-vars
  [ALERT]    (27033) : config : Error(s) found in configuration file : /dev/stdin

  =================================================================
  ==27033==ERROR: LeakSanitizer: detected memory leaks

  Direct leak of 4 byte(s) in 1 object(s) allocated from:
      #0 0x7f113e518e20 in strdup (/usr/lib64/libasan.so.8+0x118e20)
      #1 0x000000955410 in setup_new_proxy src/proxy.c:3178
      #2 0x000000955816 in alloc_new_proxy src/proxy.c:3221
      #3 0x000000956c33 in parse_new_proxy src/proxy.c:3554
      #4 0x000000a24d03 in cfg_parse_listen src/cfgparse-listen.c:495
      #5 0x00000089d33e in parse_cfg src/cfgparse.c:2202
      #6 0x0000009e0bb9 in read_cfg src/haproxy.c:1142
      #7 0x000000447e8c in main src/haproxy.c:3474
      #8 0x7f113d82ad13 in __libc_start_call_main (/lib64/libc.so.6+0x2ad13)
      #9 0x7fff65b4e320  ([stack]+0x20320)

  SUMMARY: AddressSanitizer: 4 byte(s) leaked in 1 allocation(s).

The fix replaces ha_free(&curproxy) with proxy_drop(curproxy), which
properly calls deinit_proxy() to release all internal resources, removes
the proxy from trees and lists, decrements the refcount, and frees the
struct.

No backport is needed since proxy_drop() is only in 3.4.
2026-05-15 17:39:25 +02:00
Willy Tarreau
569f1e2f37 BUG/MINOR: dns: fix dangling dgram pointer on dns_dgram_init() failure path
In dns_dgram_init(), the newly created dgram is assigned to the name server
before the ring is attached. In case of errors, e.g. due to too many watchers,
the dgram is released but not removed from ns->dgram. Let's only assign the
pointer on success to avoid this, as it's not needed before. The problem
was introduced in 2.4 with commit c943799c86 ("MEDIUM: resolvers/dns: split
dns.c into dns.c and resolvers.c"), and was possibly there before. The fix
may be backported to all stable versions.
2026-05-15 17:39:25 +02:00
Willy Tarreau
493dc352ad BUG/MINOR: resolvers: fix dangling list pointer in resolvers_new() error paths
The resolver 'r' is appended to the global sec_resolvers list, but upon failure
later, pointers are released but the element remains in the list, corrupting it,
and possibly causing a crash during deinit() when releasing remaining ones.
Adding a LIST_DEL_INIT() on the error unrolling path is sufficient.

Note that the issue will only happen on failure to allocate memory via
strdup() so the risk is low. The bug was introduced in 2.6 by commit
e7f5776800 ("MINOR: resolvers: resolvers_new() create a resolvers with
default values"), so the fix may be backported to several releases, but
does not necessarily have to go that far.
2026-05-15 17:39:25 +02:00
Willy Tarreau
8aa99dfc74 BUG/MEDIUM: server/cli: unlock server lock on failure in cli_parse_set_server
In cli_parse_set_server()'s 'ssl' branch, the server lock is taken,
and not released in case srv_set_ssl() fails, resulting in a dead lock
and a panic the next time an attempt to touch this server is made. The
lock must be released on all error paths.

This was introduced in 3.3 by commit f8f94ffc9 ("BUG/MEDIUM: server:
Use sni as pool connection name for SSL server only") which was marked
for backporting to 3.0, so this must likely be backported that far.
2026-05-15 17:39:25 +02:00
Willy Tarreau
5b468a0820 BUG/MINOR: servers: use proper source of pool_conn_name in srv_settings_cpy()
The condition 'if (srv->pool_conn_name)' was checking the destination
instead of the source 'src->pool_conn_name', meaning the strdup() would
never fire (since newly calloc'd servers start with NULL pool_conn_name),
and the pool_conn_name setting from default-server was silently ignored.

Introduced in 3.2 with commit f0f1816f1 ("MINOR: check: implement
check-pool-conn-name srv keyword") when pool_conn_name support was added
to srv_settings_cpy(). The bug caused any 'pool-conn-name' setting in a
'default-server' line to be lost for all servers inheriting from it.

Note that it's not the first time this function induces such a bug due
to the poor choice of "srv" vs "src" that should be renamed to avoid
keyboard mistakes and visual confusion.

This needs to be backported to 3.2.
2026-05-15 17:39:25 +02:00
Willy Tarreau
6c663a9374 BUG/MINOR: server: better handling of OOM in srv_set_fqdn()
This function may face an OOM on strdup() in the middle of the hostname
or hostname_dn replacement, leaving NULLs in either or both of the server's
fields, which is definitely not good for other call places.

Let's perform a safe replacement instead: we first allocate the new
values, and only if they are successful, then we release the previous
ones and replace them.

It is not necessary to backport this unless the issue is reported (it
was found via code review).
2026-05-15 17:39:25 +02:00
Willy Tarreau
2a43a1306b CLEANUP: dns: fix misleading error messages in dns_stream_init()
All task allocation errors report "memory allocation error initializing
the ring" when the actual failure was task_new_anywhere() returning NULL.
This clearly is a copy-paste. Let's fix the error messages to help when
debugging. Since it's only about allocation failures during init, there
is probably no point in backporting this.
2026-05-15 17:39:25 +02:00
Willy Tarreau
b6bd6f5b9a CLEANUP: proxy: fix tiny mistakes in parse error messages
One is s/keyworld/keyword in the retry-on parser. The other one is a
wrong argument "len" being printed in case of parse error for
"declare capture" instead of the length itself.

These can be backported though they are not important.
2026-05-15 15:46:46 +02:00
Willy Tarreau
ace19fd638 BUG/MEDIUM: dns: fix memory leak of sockaddr in dns_session_init() error path
In dns_session_init(), sockaddr_alloc() allocates 'addr' from the sockaddr
pool, but on failure of appctx_finalize_startup() we jump to the error label
without calling sockaddr_free(&addr), leaking the allocation. Let's add the
missing sockaddr_free() on the error branch.

This must be backported to 2.6.
2026-05-15 15:40:29 +02:00
Willy Tarreau
bb5c18ab74 BUG/MEDIUM: resolvers: fix name compression pointer validation in resolv_read_name()
The original DNS code would only use the 8 lower bits of the compression
offset. This was fixed in 2.0 with commit 2fa66c3b9 ("BUG/MEDIUM: dns:
overflowed dns name start position causing invalid dns error") but it was
not sufficient because the anti-loop check continues to use only 8 of the
14 bits, thus a crafted response where the 8 lower bits pass the check and
the 6 higher should fail it would be accepted. The impacts remains limited
thanks to the bounds check and the recursion limits, but such invalid
responses could still cost a lot to process. Let's compute the 14-bit
offset once for all and use it everywhere.
2026-05-15 15:33:14 +02:00
Willy Tarreau
fefce297ab BUG/MEDIUM: dns: fix long loops in additional records parse on name failure
In resolv_validate_dns_response(), the additional records loop calls
resolv_read_name(). When it returns zero due to a bad response, the main
loop does a "continue" without making the "reader" pointer progress, so it
evaluates the exact same field again and again. Fortunately this is limited
by arcount which is 16 bits, but it means it can still iterate 65535 times
there, allocating and releasing an answer_record at each turn. Let's just
jump to the invalid_resp label that handles the cleaning. There was the
same pattern (without the allocation) with nscount a few lines above BTW.
These can possibly explain some situations where a high CPU usage observed
processing responses.

Seems like these were introduced in 2.2 with commit 37950c8d2
("BUG/MEDIUM: dns: improper parsing of aditional records")

This must be backported to stable versions.
2026-05-15 15:26:49 +02:00
Willy Tarreau
bcb4f9cd4a BUG/MINOR: config/dns: properly fail on duplicate nameserver name detection
In cfg_parse_resolvers(), two duplicate name checks set err_code but lacked
'goto out', allowing execution to fall through and create the duplicate entry.
This would result in new resolvers and nameservers to be created after the
error was displayed, and a leak of the previous one. It's mostly harmless
since we're exiting after such errors. This can be backported if desired.
2026-05-15 15:04:00 +02:00
Willy Tarreau
da4a4976d7 BUG/MINOR: backend: correct parameter value validation in get_server_ph_post()
In the inner while loop that validates each character of a POST parameter
value, the code checks *p via HTTP_IS_TOKEN() and HTTP_IS_LWS() instead
of *end, while the loop condition only advances "end", so only the first
character of each value is validated.

This means spaces or binary data embedded in parameter values after the
first character goes undetected. Fix by replacing both references to *p
with *end to properly scan through all characters as intended.

This bug was introduced in 1.5-dev20 by commit 98634f0c7 ("MEDIUM:
backend: Enhance hash-type directive with an algorithm options") so
the fix must be backported to all versions.
2026-05-15 15:03:16 +02:00
91 changed files with 1520 additions and 411 deletions

View file

@ -1,6 +1,86 @@
ChangeLog :
===========
2026/05/20 : 3.4-dev13
- BUG/MINOR: backend: correct parameter value validation in get_server_ph_post()
- BUG/MINOR: config/dns: properly fail on duplicate nameserver name detection
- BUG/MEDIUM: dns: fix long loops in additional records parse on name failure
- BUG/MEDIUM: resolvers: fix name compression pointer validation in resolv_read_name()
- BUG/MEDIUM: dns: fix memory leak of sockaddr in dns_session_init() error path
- CLEANUP: proxy: fix tiny mistakes in parse error messages
- CLEANUP: dns: fix misleading error messages in dns_stream_init()
- BUG/MINOR: server: better handling of OOM in srv_set_fqdn()
- BUG/MINOR: servers: use proper source of pool_conn_name in srv_settings_cpy()
- BUG/MEDIUM: server/cli: unlock server lock on failure in cli_parse_set_server
- BUG/MINOR: resolvers: fix dangling list pointer in resolvers_new() error paths
- BUG/MINOR: dns: fix dangling dgram pointer on dns_dgram_init() failure path
- BUG/MINOR: proxy: use proxy_drop() in parse_new_proxy() error path
- CLEANUP: resolvers: properly initialize the sample in resolv_action_do_resolve()
- BUG/MINOR: resolvers: report the expression error in the do-resolve() action parser
- BUG/MINOR: resolvers: fix leaked dgram and dns_ring struct in parse_resolve_conf()
- BUG/MINOR: resolvers: fix leaked fields on cfg_parse_resolvers() error paths
- BUG/MINOR: resolvers: fix missing task_idle destruction in resolvers_destroy()
- CLEANUP: proxy: fix duplicate declaration of cli_find_frontend in proxy.h
- CLEANUP: address a few typos and copy-paste errors in httpclient and dns
- DOC: internal: add a few rules about internal core principles
- BUG/MINOR: session/trace: use distinct flags for SESS_EV_END and _ERR
- CLEANUP: stick-table: uniformize the different action_inc_gpc*()
- REGTESTS: do not run quic/tls13_ssl_crt-list_filters in quic openssl compat mode
- REGTESTS: quic/issuers_chain_path: do not forget to enable QUIC compat mode
- BUG/MINOR: sock: store the connection error status
- BUG/MINOR: check: properly report errno in chk_report_conn_err()
- CLEANUP: tcpcheck: mention that we're a bit far for a sync errno
- BUG/MINOR: jwt: fix possible memory leak in convert_ecdsa_sig() error path
- CLEANUP: jwe: fix theoretical overflow in AAD length calculation
- DOC: config: further clarify that resolvers "default" exists
- MINOR: proxy: remove the experimental status on dynamic backends
- BUG/MEDIUM: limits: properly account for global.maxpipes in compute_ideal_maxconn()
- BUG/MINOR: jws: fix OpenSSL 3.0 version check from > to >=
- BUG/MINOR: jws: Add missing return value check (EVP_PKEY_get_bn_param)
- BUG/MINOR: server: Properly handle init-state value during haproxy startup
- BUG/MINOR: httpclient-cli: Destroy http-client context if failing to start it
- BUG/MEDIUM: h1: Skip all h2c values from Upgrade headers during parsing
- BUG/MINOR: h1: Don't mask websocket protocol if multiple protocols used
- MINOR: haterm: Don't init haterm master pipe if not used
- CLEANUP: haterm: Remove "(too old kernel)" from warning message during init
- BUG/MINOR: httpclient-cli: fix uninit variable in error label
- MINOR: mux: Rename the "token" from mux_proto_list to mux_proto
- MEDIUM: connections: Use both mux_proto and alpn to pick a mux
- MINOR: connection: define conn_select_mux_fe()
- MINOR: connection: define conn_select_mux_be()
- MINOR: connection/mux_quic: add MUX <init_xprt> field for QMux handshake
- MINOR: proxy/server: reject TCP ALPN h3 without experimental
- MEDIUM: ssl: allow h3/QMux negotiation without explicit proto
- BUG/MINOR: server: accept server IDs above 2^31 and clarify error message
- BUG/MINOR: backend: fix balance hash calculation when using hash-type none
- MINOR: server: support hash-key id32 for a cleaner distribution
- MINOR: backend: support hash-key guid for a stabler distribution
- MINOR: startup: support unprivileged chroot if possible
- MEDIUM: startup: add automatic chroot feature
- MINOR: h2: explain committed_extra_streams dec on h2_init() error
- OPTIM: h2: do not update committed streams if elasticity disabled
- MINOR: mux_quic: implement basic committed_extra_streams accounting
- MINOR: quic: use stream elasticity value for initial advertisement
- MINOR: mux_quic: define ms_bidi_rel QCC member
- MAJOR: mux_quic: support stream elasticity during connection lifetime
- BUG/MEDIUM: servers: Store the connection hash with the parameter cache
- BUG/MINOR: prevent conn leak in case of xprt_qmux init failure
- BUILD: traces: set a few __maybe_unused on vars used only for traces
- BUILD: traces: add USE_TRACE allowing to disable traces
- MINOR: startup: do not execute chroot() when "/"
- MEDIUM: startup: warn when chroot is not set for root
- BUG/MEDIUM: servers: Don't forget to set srv_hash when needed
- DOC: fix typo on QUIC stream.max-concurrent reference
- BUG/MINOR: mux_quic: do not exceed stream.max-concurrent on backend side
- BUG/MINOR: htx: Fix value of HTX_XFER_HDRS_ONLY flag
- MEDIUM: htx: Improve htx_xfer API to not count HTX meta-data
- BUG/MEDIUM: applet: Fix transfer of HTX data to the applet
- BUG/MEDIUM: htx: Alloc a chunk of right size in htx_replace_blk_value()
- MEDIUM: stick-tables: Avoid freeing elements while holding a lock
- MINOR: intops: add a multiply overflow detection for ulong and size_t
- CLEANUP: tree-wide: use array_size_or_fail() in array size for allocations
- DOC: update supported gcc and openssl versions in INSTALL
2026/05/13 : 3.4-dev12
- SCRIPTS: announce-release: add a link to the OpenTelemetry filter
- BUG/MEDIUM: servers: Only requeue servers if they are up

View file

@ -111,12 +111,12 @@ HAProxy requires a working GCC or Clang toolchain and GNU make :
may want to retry with "gmake" which is the name commonly used for GNU make
on BSD systems.
- GCC >= 4.7 (up to 15 tested). Older versions are no longer supported due to
- GCC >= 4.7 (up to 16 tested). Older versions are no longer supported due to
the latest mt_list update which only uses c11-like atomics. Newer versions
may sometimes break due to compiler regressions or behaviour changes. The
version shipped with your operating system is very likely to work with no
trouble. Clang >= 3.0 is also known to work as an alternative solution, and
versions up to 19 were successfully tested. Recent versions may emit a bit
versions up to 21 were successfully tested. Recent versions may emit a bit
more warnings that are worth reporting as they may reveal real bugs. TCC
(https://repo.or.cz/tinycc.git) is also usable for developers but will not
support threading and was found at least once to produce bad code in some
@ -237,7 +237,7 @@ to forcefully enable it using "USE_LIBCRYPT=1".
-----------------
For SSL/TLS, it is necessary to use a cryptography library. HAProxy currently
supports the OpenSSL library, and is known to build and work with branches
1.0.0, 1.0.1, 1.0.2, 1.1.0, 1.1.1, and 3.0 to 3.6. It is recommended to use
1.0.0, 1.0.1, 1.0.2, 1.1.0, 1.1.1, and 3.0 to 4.0. It is recommended to use
at least OpenSSL 1.1.1 to have support for all SSL keywords and configuration
in HAProxy. OpenSSL follows a long-term support cycle similar to HAProxy's,
and each of the branches above receives its own fixes, without forcing you to

View file

@ -44,6 +44,7 @@
# USE_CLOSEFROM : enable use of closefrom() on *bsd, solaris. Automatic.
# USE_PRCTL : enable use of prctl(). Automatic.
# USE_PROCCTL : enable use of procctl(). Automatic.
# USE_TRACE : enable trace subsystem. Always on.
# USE_ZLIB : enable zlib library support and disable SLZ
# USE_SLZ : enable slz library instead of zlib (default=enabled)
# USE_CPU_AFFINITY : enable pinning processes to CPU on Linux. Automatic.
@ -343,7 +344,7 @@ use_opts = USE_EPOLL USE_KQUEUE USE_NETFILTER USE_POLL \
USE_TPROXY USE_LINUX_TPROXY USE_LINUX_CAP \
USE_LINUX_SPLICE USE_LIBCRYPT USE_CRYPT_H USE_ENGINE \
USE_GETADDRINFO USE_OPENSSL USE_OPENSSL_WOLFSSL USE_OPENSSL_AWSLC \
USE_ECH \
USE_ECH USE_TRACE \
USE_SSL USE_LUA USE_ACCEPT4 USE_CLOSEFROM USE_ZLIB USE_SLZ \
USE_CPU_AFFINITY USE_TFO USE_NS USE_DL USE_RT USE_LIBATOMIC \
USE_MATH USE_DEVICEATLAS USE_51DEGREES \
@ -366,6 +367,9 @@ $(warn_unknown_options)
# on the make command line.
USE_POLL = default
# traces are always enabled
USE_TRACE = default
# SLZ is always supported unless explicitly disabled by passing USE_SLZ=""
# or disabled by enabling ZLIB using USE_ZLIB=1
ifeq ($(USE_ZLIB:0=),)

View file

@ -1,2 +1,2 @@
$Format:%ci$
2026/05/13
2026/05/20

View file

@ -1 +1 @@
3.4-dev12
3.4-dev13

View file

@ -3,7 +3,7 @@
Configuration Manual
----------------------
version 3.4
2026/05/13
2026/05/20
This document covers the configuration language as implemented in the version
@ -2126,13 +2126,28 @@ ca-base <dir>
directives. Absolute locations specified in "ca-file", "ca-verify-file" and
"crl-file" prevail and ignore "ca-base".
chroot <jail dir>
chroot { <jail dir> | auto }
Changes current directory to <jail dir> and performs a chroot() there before
dropping privileges. This increases the security level in case an unknown
vulnerability would be exploited, since it would make it very hard for the
attacker to exploit the system. This only works when the process is started
with superuser privileges. It is important to ensure that <jail_dir> is both
empty and non-writable to anyone.
attacker to exploit the system. It is important to ensure that <jail dir>
is both empty and non-writable to anyone. When the process is started with
superuser privileges, the chroot() is performed directly. On Linux, when
started unprivileged, haproxy attempts to perform it from inside a new
user namespace created with unshare(CLONE_NEWUSER); if that mechanism is
unavailable the chroot() will fail with the usual error.
As a special case, <jail dir> may be set to "auto", in which case haproxy
creates an anonymous temporary directory, unlinks it, and chroots into it.
The resulting jail has no name in the filesystem and is empty and read-only,
removing the need to prepare a dedicated jail directory.
When starting with superuser privileges, a warning will be displayed if no
chroot is used, in order to encourage users to always use the mechanism. If
for any reason there is a compelling reason not to use chroot (e.g. access to
a server via a UNIX socket with an unconvenient path), it remains possible to
silence the warning by adding an explicit "chroot /", which has the benefit
of being visible in a configuration.
close-spread-time <time>
Define a time window during which idle connections and active connections
@ -3314,7 +3329,7 @@ setenv <name> <value>
the configuration file sees the new value. See also "presetenv", "resetenv",
and "unsetenv".
shm-stats-file <name> [ EXPERIMENTAL ]
shm-stats-file <name>
When this directive is set, it enables the use of shared memory for storing
stats counters. <name> is used as argument to shm_open() to open the shared
memory at a unique location. It also means that the directive is only
@ -3330,7 +3345,7 @@ shm-stats-file <name> [ EXPERIMENTAL ]
See also "guid", "guid-prefix" and "shm-stats-file-max-objects"
shm-stats-file-max-objects <number> [ EXPERIMENTAL ]
shm-stats-file-max-objects <number>
This setting defines the maximum number of objects the shared memory used
for shared counters will be able to store per thread group. It is directly
related to the maximum memory size of the shm and is used to "premap" the
@ -5306,17 +5321,26 @@ tune.quic.frontend.stream-data-ratio <0..100, in percent> (deprecated)
tune.quic.be.stream.max-concurrent <number>
tune.quic.fe.stream.max-concurrent <number>
Sets the QUIC initial_max_streams_bidi transport parameter either on frontend
or backend side. This is the maximum number of bidirectional streams that the
remote peer will be authorized to open concurrently during the connection
lifetime. On frontend side, this limits the number of concurrent HTTP/3
client requests.
On frontend side, this is used as the value for the advertised
initial_max_streams_bidi transport parameter. This is enforced as the maximum
number of bidirectional streams that the remote peer will be authorized to
open concurrently during the connection lifetime. This effectively limits the
number of concurrent HTTP/3 client requests.
The default value is 100. Note that if you reduces it, it can restrict the
buffering capabilities of streams on receive, which would result in poor
upload throughput. It can be corrected by increasing the QUIC stream rxbuf
connection setting.
On backend side, this is enforced locally by haproxy to limit the number of
concurrent requests multiplexed over a single connection. This may be further
restricted by the peer flow control. It may be necessary to reduce the
default value of 100 to improve a site's responsiveness at the expense of a
higher number of opened backend connections. Similarly to the frontend side,
this setting also directly impacts the Rx buffering capability, this time
though limiting the HTTP download capacity. QUIC stream rxbuf setting can be
increased when dealing mostly with HTTP responses larger than "tune.bufsize".
See also: "tune.quic.be.stream.rxbuf", "tune.quic.fe.stream.rxbuf",
"tune.quic.be.stream.data-ratio", "tune.quic.fe.stream.data-ratio"
@ -5719,6 +5743,16 @@ tune.streams-elasticity <number>
use lower values (120 to 200) to support 1.2 to 2 streams per connection on
average at full load.
Contrary to HTTP/2, QUIC is capable to dynamically adjust the number of
concurrent streams during the connection lifetime. However, QUIC flow control
is stricter than HTTP/2, thus it is preferable when using it to specify
values big enough to prevent extra latency on the connection. There is also a
limitation for QUIC listeners with enabled 0-RTT. In this case, the initial
value advertised to the peer will ignore stream elasticity and instead rely
solely on the "tune.quic.fe.stream.max-concurrent" setting. However, the
stream elasticity principle will still be effective past this initial
annoucement during the connection lifetime.
Monitoring the total number of active streams on backends, including queues,
provides a practical indicator of a sustainable target load and helps avoid
over-provisioning.
@ -8260,7 +8294,10 @@ hash-type <method> <function> <modifier>
none don't hash the key, the key will be used as a hash, this can be
useful to manually hash the key using a converter for that purpose
and let haproxy use the result directly.
and let haproxy use the result directly. The operation will
convert the key to a string if it is not already, and parse it as
an integer whose value will be used as the key. Some input key
types might not be relevant here (e.g. IP addresses).
<modifier> indicates an optional method applied after hashing the key :
@ -18847,6 +18884,21 @@ hash-key <key>
better only use values comprised between 1 and this value to
avoid overlap.
id32 The node keys will be derived from the server's numeric
identifier as set from "id" or which defaults to its position
in the server list, but the full 32 bits of the ID will be
used so that there is no collision. This one is not scaled
like "id" is, so it is recommended to either always use it
with a hash function (see "hash-key") or with explicitly
assigned ID values that are evenly distributed over the 32-bit
space.
guid The node keys will be derived from the server's guid, when
available, otherwise they will fall back on "id". The benefit
is that it does not depend on ordering at all, only on an
internal stable identifier that can be replicated across many
load balancers.
addr The node keys will be derived from the server's address, when
available, or else fall back on "id".
@ -18876,9 +18928,13 @@ healthcheck <name>
id <value>
May be used in the following contexts: tcp, http, log
Set a persistent ID for the server. This ID must be positive and unique for
the proxy. An unused ID will automatically be assigned if unset. The first
assigned value will be 1. This ID is currently only returned in statistics.
Set a persistent ID for the server. This ID must be a 32-bit positive number
and unique for the proxy. An unused ID will automatically be assigned if
unset. The first assigned value will be 1. This ID is currently only returned
in statistics, and is used to place LB nodes when using consistent hash
algorithms when "hash-key" is set to "id" (the default). In this case, only
the 28 lowest bits of the value are used (i.e. (id % 268435356)), so better
only use values comprised between 1 and this value to avoid overlap.
idle-ping <delay>
May be used in the following contexts: tcp, http, log
@ -18972,7 +19028,7 @@ downinter <delay>
"inter" setting will have a very limited effect as it will not be able to
reduce the time spent in the queue.
init-state { fully-up | up | down | fully-down }
init-state { fully-up | up | down | fully-down | none }
May be used in the following contexts: tcp, http
May be used in sections : defaults | frontend | listen | backend
@ -18980,20 +19036,25 @@ init-state { fully-up | up | down | fully-down }
The "init-state" option sets the initial state of the server:
- when set to 'fully-up', the server is considered immediately available
and can turn to the DOWN state when ALL health checks fail.
- when set to 'up' (the default), the server is considered immediately
available and will initiate a health check that can turn it to the DOWN
state immediately if it fails.
- when set to 'down', the server initially is considered unavailable and
will initiate a health check that can turn it to the UP state immediately
if it succeeds.
and, if health checks are enabled for this server, it will be turned to
the DOWN state when ALL health checks fail.
- when set to 'up', the server is considered immediately available and, if
health checks are enabled for this server, it will be turned to the DOWN
state immediately if the next health check fails.
- when set to 'down', the server initially is considered unavailable and,
if health checks are enabled for this server, it can be turned to the UP
state if the next health check succeeds.
- when set to 'fully-down', the server is initially considered unavailable
and can turn to the UP state when ALL health checks succeed.
and, if health checks are enabled for this server, it will turned to the
UP state when ALL health checks succeed.
- when set to 'none' (the default value), init-state management is
disabled. It can be used to restore the default behavior when this
parameter was inherited from a 'default-server' directive.
The server's init-state is considered when the HAProxy instance is
(re)started, a new server is detected (for example via service discovery /
DNS resolution), a dynamic server is inlived, a server exits maintenance,
etc.
etc. This directive cannot be used when the server is tracking another one.
Examples:
# pass client traffic ONLY to Redis "master" node
@ -20189,7 +20250,11 @@ a cache of previous answers, an answer will be considered obsolete after
resolvers <resolvers id>
Creates a new name server list labeled <resolvers id>
Creates a new name server list labeled <resolvers id>. As mentioned above,
the special name "default" always exists and will be automatically created if
not explicitly declared; this will be the one internal services such as
httpclient rely on. Declaring a "default" entry will affect how such services
perform their name resolution.
A resolvers section accept the following parameters:

View file

@ -0,0 +1,229 @@
HAPROXY CORE PRINCIPLES
0. RULE ZERO: EXCEPTIONS AND JUSTIFICATION
- These rules are mandatory; violations are bugs unless explicitly justified.
- A violation is acceptable if accompanied by a comment explaining WHY the
standard approach was insufficient (e.g., "Performance-critical bypass").
- Reviews should flag unjustified violations but accept commented ones.
1. PROJECT ORGANIZATION
- header files all under "include/", and split between haproxy/<file>-t.h for
type definitions (types, enums, structures), and haproxy/<file>.h for static
definitions and exported symbols. A few imported libs under include/import.
- C source files in src/.
- some API doc in doc/internals/api/ (not always up to date, check date or
version at the top).
2. ENVIRONMENT AND DATA TYPES
- The project targets 32/64-bit POSIX systems (little or big endian).
- Char is signed or unsigned 8-bit, short signed 16-bit, int signed 32-bit.
- Long and pointers always match the native word size. Long long is 64-bit.
- Aliases: uchar (unsigned char), uint (unsigned int), ulong (unsigned long),
ushort (unsigned short), ullong (unsigned long long), llong (long long),
schar (signed char).
- size_t always same size as long but often declared as uint on 32-bit and
ulong on 64-bit. Do not use in printf() without a cast (ulong with "%lu").
- Main platforms are x86_64 and aarch64 with high thread counts (>=64).
- Unaligned accesses are permitted for archs that support them; portable
wrappers in net_helper.h (read_u32(), write_u32() etc).
- signed integer wrapping well-defined via -fwrapv.
- arch-specific asm() statements OK as long as equivalent C-code exists for
generic archs.
- Pointer arithmetics used a lot via container_of(), offset_of(), and void*
casts.
- Floating point not used.
3. MEMORY MANAGEMENT AND POOLS
- Pools are used for runtime allocation; malloc/free are for boot code only.
- pool_alloc() semantics match malloc(); the return must always be tested.
- pool_alloc() and malloc() are not interchangeable / compatible.
- pool_free() semantics match free(); it is a no-op on NULL.
- pool_free() makes the pointer invalid immediately; it must not be touched
or passed to pool_free() again.
- Memory allocated from one pool must be released to the same pool.
- ha_free() calls free() and sets the pointer to NULL before returning.
- my_realloc2() frees the original pointer if the allocation fails.
- never leave dangling pointers in structs after free().
4. BUFFER INVARIANTS (struct buffer)
- Buffers are 4-word inline structs used for data in transit (wrapping,
sliding window).
- Members: area (storage), size (capacity), head (offset), data (count).
- The area pointer is allowed to be NULL when size is zero.
- always true: 0<=data<=size; always true when size>0: 0<=head<size.
- contents start at <head>, for <data> bytes, and may wrap at the end of the
storage area (area+size).
- API (b_*, in buf.h and dynbuf.h) supports empty or unallocated buffers.
- idempotent functions b_alloc() and b_free() use pools to manage the
storage area and check <size> to know if alloc/free still needed.
- a non-contiguous version exists (ncbuf, ncbmbuf), allowing holes anywhere
in data. The former mandates holes of at least 8 bytes. The second relies
on a bitmap of populated places.
- another string API exists, "ist", representing a pointer and a length in a
struct that is returned by inline functions and macros. It is described in
doc/internals/api/ist.txt
- buffers can switch to and from HTX, which is an internal representation of
HTTP elements, with an API supporting header addition/modification/removal,
start-line manipulation, data appending/consumption etc. HTX functions are
all prefixed with "htx_". Between htx_from_buf() and htx_to_buf(), only the
HTX API may be used, not the b_* API.
5. DATA MANIPULATION (CHUNKS, TRASH, LISTS, TREES)
- Chunks use the buffer API but are NOT allowed to wrap.
- Chunks are used for linear operations like chunk_printf().
- Trash is a thread-local temporary buffer; scope stays within the caller.
- trash always the same size as a buffer (global.tune.bufsize).
- get_trash_chunk() provides up to 3 rotating thread-local trash chunks (with
a scope spanning from the call to the next function call).
- For longer lived trash chunks, alloc_trash_chunk() is available but must be
released using free_trash_chunk() on leaving.
- standard doubly-linked lists (struct list) are provided via macros LIST_*.
- LIST_INIT() must be used on new heads and elements. LIST_DELETE() only
removes the element and does not reinitialize it, so the idempotent
LIST_DEL_INIT() is generally preferred. Iterators like list_for_each_* are
available, some safe against item removal. See doc/internals/api/list.txt
for details (grep -i "^list_" to list available macros).
- thread-safe doubly-linked lists (struct mt_list) are provided via macros
mt_list_*. They work like lists and use compatible storage, though they may
not be mixed. See doc/internals/api/mt_list.txt (grep -i "^mt_list_" to
list available operations).
- elastic binary trees (ebtree) are used for fast access (O(logN) operations,
O(1) deletion). Idempotent deletion. Main functions are lookup, insert,
delete, first, next, with type-based prefix eb{32,64,st,mb,pt}_*().
- compact elastic binary trees (cebtree) are used for read-mostly focusing on
space savings (O(logN) operations, but higher cost than ebtree). Same ops
as ebtree, with type-based prefix ceb{32,u32,64,u64,s,is}_*.
6. THREAD SYNCHRONIZATION
- Threads are started at boot (one per CPU) and persist for the process life,
arranged in thread groups (tg) by cache locality.
- Each thread has its own polling loop and scheduler. Total parallelism.
- thread_isolate()/thread_release() for total thread isolation (very heavy).
- "tid" always current thread number, "th_ctx" always current thread's context,
"ti" current thread info.
- "tgid" always current tg number, "tg_ctx" current tg context.
- HA_ATOMIC_* for atomic operations on integers and pointers (includes load
and store). DWCAS available on some platforms but requires an equivalent
for other ones.
- The _HA_ATOMIC_* version (leading underscore) do not use barriers so these
must be explicit (__ha_barrier_*).
- Atomic loops must use CPU relaxation or exponential back-off.
- For multiple changes at once, threads may use spinlocks (HA_SPIN_LOCK()/
HA_SPIN_UNLOCK/HA_SPIN_TRYLOCK), and upgradable RW locks (HA_RWLOCK_*) if
read accesses dominate.
- No sleeping locks (mutex etc), only spinning/rwlocks/atomic loops.
7. SCHEDULING AND LATENCY
- Latency is critical.
- No runtime filesystem access, no blocking calls, no long loops.
- Complex processing must be split into small steps; the task must yield.
- CPUs are not dedicated to haproxy, high risk of a thread being interrupted
by another process if it works too long, catastrophic if it happens with a
lock held.
- A watchdog kills the process if a task hogs a CPU for > few milliseconds.
- Tasks vs Tasklets: Tasks have tree storage (rq) and timers (wq); tasklets
use list elements instead of rq and are smaller (no wq). Only task.c/h may
distinguish rq vs list access.
- Tasks are aliased to tasklet while they are running (hence why some
functions cast task to tasklets and conversely to access certain fields).
- inter-thread task/tasklet wakeups always safe using the task_* API.
- task/tasklet->state field must always be accessed atomically.
8. ARCHITECTURAL LAYERS (MUX AND STREAMS)
- Naming: Lower layer (multiplexed), attached to the connection uses suffix
'c' (h1c, h2c, qcc, muxc); Upper layer (demultiplexed/application, often a
stream) uses suffix 's' (h1s, h2s, qcs, muxs).
- Application layer stream (struct stream) has two stream connectors (stconn):
front (scf) and back (scb). Responsible for processing requests/responses,
deciding which server to route it, finding a backend connection or creating
one, and exchanging data between the two sides.
- Stream connectors link to a muxs or applet via a stream endpoint descriptor
(sedesc/sd), and exchange data via buffers, which for an HTTP muxs are HTX
buffers containing HTX blocks.
- The sd carries the shared context between layers.
- When a stream detaches from a mux, a new sd is allocated for the stream and
the mux keeps its previous sd: stconn and muxs both always have a valid sd.
- Front connections/streams are tied to the creator thread forever.
- Idle back connections can be stolen via mux->takeover(), but become
thread-bound once a stream attaches. => all streams of a mux are on the
same thread.
- session vs connection vs stream: connection is transport; session lasts for
the client connection's life; stream are request/response pairs.
- applets carry a context specific to the service being executed or the CLI
command in appctx->svcctx, and this one is always zeroed before the handler
is first called.
9. FUNCTION RETURN CONVENTIONS
- Boolean style: Functions named as actions/sentences return 0 (failure) or
non-zero (success).
- Integer style: some syscall-like functions return <0 (error) or >=0 (success).
- Tri-state style, e.g. counts: <0 (error), 0 (no progress), >0 (success).
10. DIAGNOSTICS AND SAFETY
- When DEBUG_STRICT is set, ABORT_NOW() crashes the program immediately, and
BUG_ON(cond[,msg]) crashes the program if the condition is true.
- COUNT_IF() / CHECK_IF() only track if a condition occurs (non-fatal).
- Glitches are counters for uncommon events used to detect hostile behavior.
- strcpy(), strcat() and sprintf() are totally forbidden (the program will
not build).
11. BASIC CODING STYLE
- Linux Kernel-like, but uses tabs for indent, spaces for alignment. Function
definitions have their opening brace on a new line, never on the same line.
- All local variables must be declared at the beginning of the function
block, before any executable statements (gnu89-like).
- Avoid variable shadowing in code blocks.
- Beware of local static and global variables.
- Use const arguments whenever possible.
- Avoid static storage when persistence is not needed.
- Macros in uppercase unless they're used to wrap functions which then get a
leading underscore.
- Explicitly compare functions returning non-zero with 0 (e.g. strcmp) unless
they explicitly return a boolean (e.g. isalnum) or a pointer (e.g. strchr).
- Unsigned int comparisons to zero never use >0 but !=0 to avoid signedness
mistakes.
- turn non-zero integer to boolean using "!" or "!!".
12. BUILD AND TEST
- Preferred build command:
$ make -j$(nproc) TARGET=linux-glibc OPT_CFLAGS='-std=gnu89 -Os' \
USE_OPENSSL=1 USE_QUIC_OPENSSL_COMPAT=1 USE_QUIC=1 USE_LUA=1
- Individual files can be tested by passing src/file.o as a make argument.
- Compiler warnings are not permitted for new code.
13. COMMIT MESSAGES AND DOCUMENTATION
- Commit messages must follow the project's strict format below. Do not try
to learn better from previous commits, which might be wrong during reviews.
- Structure: <TAG>: <location>: <subject> (max ~70 chars), then blank line,
then description.
- Tags:
- CLEANUP: spelling fixes, refactoring, no new code nor functional change.
- MINOR: new feature or low-impact change, may be backported if needed.
- MEDIUM: new feature or change with moderate severity/impact/risk.
- MAJOR: new feature or change with important severity/impact/risk.
- OPTIM: Performance improvements, may always be reverted if it breaks.
- DOC: Documentation updates or fixes.
- BUG/<severity>: Fixes a bug. Specify if regression or long-standing.
Valid severities are MINOR (low impact), MEDIUM (perf/stability risk
in uncommon configs, MAJOR (most configs), CRITICAL (stability risk
without workaround).
- Regressions: Find original commit via `git blame`; designate using
`git log -1 --format='%h ("%s")'` and version via `git describe --tags`.
- Location: subsystem (stream, tasks, mux-h2, qpack etc).
- Description: Explain technical "WHY", "HOW", and technical impact. Explain
how to trigger the bug for developer testing.
- Backports: only for fixes, mention versions ("Must be backported to 3.0").
- Style: No generic messages like "fix(xxx): blah". Be technically precise.
- Do not mix spelling fixes in comments (not important) with other changes.
However it's preferred to have a single commit for many typo fixes at once.
- Spelling mistakes in user-visible parts (doc, logs, traces, error messages)
must be in their own commit (may need backport).
- One commit per bug.
- Example:
BUG/MEDIUM: sample: fix null pointer dereference in h1_parse_line
When parsing malformed headers, the line buffer was not initialized.
This caused a crash on certain edge cases. Let's fix this by always
initializing the line buffer when first calling the parser. This was
brought by commit 04c9e8f5 ("MINOR: add h1_parse_line") in latest -dev
so no backport is needed.

View file

@ -179,6 +179,8 @@ enum {
/* below we have all handshake flags grouped into one */
CO_FL_HANDSHAKE = CO_FL_SEND_PROXY | CO_FL_ACCEPT_PROXY | CO_FL_ACCEPT_CIP | CO_FL_SOCKS4_SEND | CO_FL_SOCKS4_RECV,
CO_FL_WAIT_XPRT = CO_FL_WAIT_L4_CONN | CO_FL_HANDSHAKE | CO_FL_WAIT_L6_CONN,
/* handshake running on top of a layer6 */
CO_FL_WAIT_XPRT_L6 = CO_FL_QMUX_SEND | CO_FL_QMUX_RECV,
CO_FL_SSL_WAIT_HS = 0x08000000, /* wait for an SSL handshake to complete */
@ -673,11 +675,12 @@ struct connection {
};
struct mux_proto_list {
const struct ist token; /* token name and length. Empty is catch-all */
const struct ist mux_proto; /* Mux protocol, to be used with the "proto" directive */
enum proto_proxy_mode mode;
enum proto_proxy_side side;
const struct mux_ops *mux;
const char *alpn; /* Default alpn to set by default when the mux protocol is forced (optional, in binary form) */
int init_xprt;
struct list list;
};

View file

@ -86,7 +86,10 @@ int conn_create_mux(struct connection *conn, int *closed_connection);
int conn_notify_mux(struct connection *conn, int old_flags, int forced_wake);
int conn_upgrade_mux_fe(struct connection *conn, void *ctx, struct buffer *buf,
struct ist mux_proto, int mode);
const struct mux_proto_list *conn_select_mux_fe(const struct connection *conn);
int conn_install_mux_fe(struct connection *conn, void *ctx);
const struct mux_proto_list *conn_select_mux_be(const struct connection *conn);
int conn_install_mux_be(struct connection *conn, void *ctx, struct session *sess,
const struct mux_ops *force_mux_ops);
int conn_install_mux_chk(struct connection *conn, void *ctx, struct session *sess);
@ -111,6 +114,7 @@ int conn_reverse(struct connection *conn);
const char *conn_err_code_name(struct connection *c);
const char *conn_err_code_str(struct connection *c);
int xprt_add_hs(struct connection *conn);
int xprt_add_l6hs(struct connection *conn, int xprt);
void register_mux_proto(struct mux_proto_list *list);
static inline void conn_report_term_evt(struct connection *conn, enum term_event_loc loc, unsigned char type);
@ -649,7 +653,7 @@ static inline struct mux_proto_list *get_mux_proto(const struct ist proto)
struct mux_proto_list *item;
list_for_each_entry(item, &mux_proto_list.list, list) {
if (isteq(proto, item->token))
if (isteq(proto, item->mux_proto))
return item;
}
return NULL;
@ -668,6 +672,7 @@ void list_mux_proto(FILE *out);
*/
static inline const struct mux_proto_list *conn_get_best_mux_entry(
const struct ist mux_proto,
const struct ist alpn,
int proto_side, int proto_is_quic, int proto_mode)
{
struct mux_proto_list *item;
@ -676,10 +681,14 @@ static inline const struct mux_proto_list *conn_get_best_mux_entry(
list_for_each_entry(item, &mux_proto_list.list, list) {
if (!(item->side & proto_side) || !(item->mode & proto_mode) || ((proto_is_quic != 0) != ((item->mux->flags & MX_FL_FRAMED) != 0)))
continue;
if (istlen(mux_proto) && isteq(mux_proto, item->token)) {
if (istlen(mux_proto) && isteq(mux_proto, item->mux_proto)) {
return item;
}
else if (!istlen(item->token)) {
else if (istlen(alpn) && item->alpn &&
strlen(item->alpn) == istlen(alpn) + 1 &&
!memcmp(alpn.ptr, item->alpn + 1, istlen(alpn)))
return item;
else if (!istlen(item->mux_proto)) {
if (!fallback || (item->mode == proto_mode && fallback->mode != proto_mode))
fallback = item;
}
@ -696,11 +705,12 @@ static inline const struct mux_proto_list *conn_get_best_mux_entry(
*/
static inline const struct mux_ops *conn_get_best_mux(struct connection *conn,
const struct ist mux_proto,
const struct ist alpn,
int proto_side, int proto_mode)
{
const struct mux_proto_list *item;
item = conn_get_best_mux_entry(mux_proto, proto_side, proto_is_quic(conn->ctrl), proto_mode);
item = conn_get_best_mux_entry(mux_proto, alpn, proto_side, proto_is_quic(conn->ctrl), proto_mode);
return item ? item->mux : NULL;
}

View file

@ -98,7 +98,7 @@ enum h1m_state {
#define H1_MF_UPG_WEBSOCKET 0x00008000 // Set for a Websocket upgrade handshake
#define H1_MF_TE_CHUNKED 0x00010000 // T-E "chunked"
#define H1_MF_TE_OTHER 0x00020000 // T-E other than supported ones found (only "chunked" is supported for now)
#define H1_MF_UPG_H2C 0x00040000 // "h2c" or "h2" used as upgrade token
/* unused: 0x00040000 */
#define H1_MF_NOT_HTTP 0x00080000 // Not an HTTP message (e.g "RTSP", only possible if invalid message are accepted)
/* Mask to use to reset H1M flags when we restart headers parsing.
*
@ -160,7 +160,7 @@ int h1_headers_to_hdr_list(char *start, const char *stop,
int h1_parse_xfer_enc_header(struct h1m *h1m, struct ist value);
void h1_parse_connection_header(struct h1m *h1m, struct ist *value);
void h1_parse_upgrade_header(struct h1m *h1m, struct ist value);
void h1_parse_upgrade_header(struct h1m *h1m, struct ist *value);
void h1_generate_random_ws_input_key(char key_out[25]);
void h1_calculate_ws_output_key(const char *key, char *result);

View file

@ -17,7 +17,7 @@ struct httpclient {
struct buffer buf; /* input buffer, raw HTTP */
} res;
struct {
/* callbacks used to send the request, */
/* callbacks used to send the request, */
void (*req_payload)(struct httpclient *hc); /* send a payload */
/* callbacks used to receive the response, if not set, the IO
@ -28,7 +28,7 @@ struct httpclient {
void (*res_end)(struct httpclient *hc); /* end of the response */
} ops;
struct sockaddr_storage *dst; /* destination address */
struct appctx *appctx; /* HTTPclient appctx */
struct appctx *appctx; /* HTTP client appctx */
int timeout_server; /* server timeout in ms */
void *caller; /* ptr of the caller */
unsigned int flags; /* other flags */
@ -50,7 +50,7 @@ struct httpclient {
#define HTTPCLIENT_FS_ENDED 0x00020000 /* the httpclient is stopped */
/* options */
#define HTTPCLIENT_O_HTTPPROXY 0x00000001 /* the request must be use an absolute URI */
#define HTTPCLIENT_O_HTTPPROXY 0x00000001 /* the request must use an absolute URI */
#define HTTPCLIENT_O_RES_HTX 0x00000002 /* response is stored in HTX */
/* States of the HTTP Client Appctx */
@ -65,4 +65,4 @@ enum {
#define HTTPCLIENT_USERAGENT "HAProxy"
#endif /* ! _HAPROXY_HTTCLIENT__T_H */
#endif /* !_HAPROXY_HTTPCLIENT_T_H */

View file

@ -38,4 +38,4 @@ static inline int httpclient_started(struct httpclient *hc)
return !!(hc->flags & HTTPCLIENT_FS_STARTED);
}
#endif /* ! _HAPROXY_HTTCLIENT_H */
#endif /* !_HAPROXY_HTTPCLIENT_H */

View file

@ -65,7 +65,9 @@ struct buffer *htx_copy_to_large_buffer(struct buffer *dst, struct buffer *src);
#define HTX_XFER_DEFAULT 0x00000000 /* Default XFER: no partial xfer / remove blocks from source */
#define HTX_XFER_KEEP_SRC_BLKS 0x00000001 /* Don't remove xfer blocks from source messages during xfer */
#define HTX_XFER_PARTIAL_HDRS_COPY 0x00000002 /* Allow partial copy of headers and trailers part */
#define HTX_XFER_HDRS_ONLY 0x00000003 /* Only Transfer header blocks (start-line, header and EOH) */
#define HTX_XFER_HDRS_ONLY 0x00000004 /* Only Transfer header blocks (start-line, header and EOH) */
#define HTX_XFER_NO_METADATA 0x00000008 /* <count> don't include meta-data, only payload */
size_t htx_xfer(struct htx *dst, struct htx *src, size_t count, unsigned int flags);
/* Functions and macros to get parts of the start-line or length of these

View file

@ -76,6 +76,56 @@ static inline unsigned int div64_32(unsigned long long o1, unsigned int o2)
return result;
}
/* returns non-zero if a*b would overflow an unsigned long, otherwise sets the
* result into res and returns 0.
*/
static inline int mulul_overflow(unsigned long a, unsigned long b, unsigned long *res)
{
/* __builtin_mul_overflow() is gcc >= 5 or clang >= 3.4 */
#if (defined(__GNUC__) && __GNUC__ >= 5) || \
(defined(__clang__) && ((__clang_major__ > 3) || (__clang_major__ == 3 && __clang_minor__ >= 4)))
return __builtin_mul_overflow(a, b, res);
#else
/* portable method involving a division */
if (a && b && a > (~(ulong)0) / b)
return 1;
*res = a * b;
return 0;
#endif
}
/* returns non-zero if a*b would overflow a size_t, otherwise sets the
* result into res and returns 0.
*/
static inline int mulsz_overflow(size_t a, size_t b, size_t *res)
{
/* __builtin_mul_overflow() is gcc >= 5 or clang >= 3.4 */
#if (defined(__GNUC__) && __GNUC__ >= 5) || \
(defined(__clang__) && ((__clang_major__ > 3) || (__clang_major__ == 3 && __clang_minor__ >= 4)))
return __builtin_mul_overflow(a, b, res);
#else
/* portable method involving a division */
if (a && b && a > (~(size_t)0) / b)
return 1;
*res = a * b;
return 0;
#endif
}
/* Computes the size of an array of m*n bytes, taking overflows into account.
* If the multiply would overflow, returns the largest possible size_t so that
* any call to malloc() or equivalent would fail. Otherwise returns the size.
* Note that this implies that even 1*max would not be permitted either.
*/
static inline size_t array_size_or_fail(size_t m, size_t n)
{
size_t size;
if (unlikely(mulsz_overflow(m, n, &size)))
return DISGUISE(~(size_t)0);
return size;
}
/* rotate left a 64-bit integer by <bits:[0-5]> bits */
static inline uint64_t rotl64(uint64_t v, uint8_t bits)
{

View file

@ -53,6 +53,7 @@ struct qcc {
struct list frms; /* prepared frames related to flow-control */
uint64_t ms_bidi_init; /* max initial sub-ID of bidi stream allowed for the peer */
uint64_t ms_bidi_rel; /* max relative sub-ID of bidi stream allowed for the peer */
uint64_t ms_bidi; /* max sub-ID of bidi stream allowed for the peer */
uint64_t cl_bidi_r; /* total count of closed remote bidi stream since last MAX_STREAMS emission */

View file

@ -10,6 +10,7 @@
#include <haproxy/connection.h>
#include <haproxy/list.h>
#include <haproxy/mux_quic-t.h>
#include <haproxy/quic_tune.h>
#include <haproxy/stconn.h>
#include <haproxy/h3.h>
@ -128,6 +129,9 @@ static inline void qcs_wait_http_req(struct qcs *qcs)
BUG_ON_HOT(qcs->flags & QC_SF_HREQ_RECV);
qcs->flags |= QC_SF_HREQ_RECV;
++qcc->nb_hreq;
/* On BE side avail_streams cb should prevent opening of too many concurrent streams. */
BUG_ON(conn_is_back(qcc->conn) && qcc->nb_hreq > quic_tune.be.stream_max_concurrent);
}
void qcc_show_quic(struct qcc *qcc);

View file

@ -96,7 +96,7 @@ void proxy_capture_error(struct proxy *proxy, int is_back,
void (*show)(struct buffer *, const struct error_snapshot *));
void proxy_adjust_all_maxconn(void);
struct proxy *cli_find_frontend(struct appctx *appctx, const char *arg);
struct proxy *cli_find_frontend(struct appctx *appctx, const char *arg);
struct proxy *cli_find_backend(struct appctx *appctx, const char *arg);
int resolve_stick_rule(struct proxy *curproxy, struct sticking_rule *mrule);
void free_stick_rules(struct list *rules);
void free_server_rules(struct list *srules);

View file

@ -1,5 +1,5 @@
/*
* include/haproxy/dns-t.h
* include/haproxy/resolvers-t.h
* This file provides structures and types for DNS.
*
* Copyright (C) 2014 Baptiste Assmann <bedis9@gmail.com>
@ -114,7 +114,7 @@ struct resolv_answer_item {
char name[DNS_MAX_NAME_SIZE+1]; /* answer name */
int16_t type; /* question type */
int16_t class; /* query class */
int32_t ttl; /* response TTL */
uint32_t ttl; /* response TTL */
int16_t priority; /* SRV type priority */
uint16_t weight; /* SRV type weight */
uint16_t port; /* SRV type port */
@ -281,7 +281,7 @@ enum {
* matching preference was found.
*/
RSLV_UPD_SRVIP_NOT_FOUND, /* provided IP not found
* OR provided IP found and preference is not match and an IP
* OR provided IP found and preference is not matched and an IP
* matching preference was found.
*/
RSLV_UPD_NO_IP_FOUND, /* no IP could be found in the response */

View file

@ -1,5 +1,5 @@
/*
* include/haproxy/dns.h
* include/haproxy/resolvers.h
* This file provides functions related to DNS protocol
*
* Copyright (C) 2014 Baptiste Assmann <bedis9@gmail.com>

View file

@ -111,7 +111,8 @@ enum srv_initaddr {
* at start up time.
*/
enum srv_init_state {
SRV_INIT_STATE_FULLY_DOWN = 0, /* the server should initially be considered DOWN until it passes all health checks. Please keep set to zero. */
SRV_INIT_STATE_NONE = 0,
SRV_INIT_STATE_FULLY_DOWN, /* the server should initially be considered DOWN until it passes all health checks. Please keep set to zero. */
SRV_INIT_STATE_DOWN, /* the server should initially be considered DOWN until it passes one health check. */
SRV_INIT_STATE_UP, /* the server should initially be considered UP, but will go DOWN if it fails one health check. */
SRV_INIT_STATE_FULLY_UP, /* the server should initially be considered UP, but will go DOWN if it fails all health checks. */
@ -248,7 +249,9 @@ struct pid_list {
/* srv methods of computing chash keys */
enum srv_hash_key {
SRV_HASH_KEY_ID = 0, /* derived from server puid */
SRV_HASH_KEY_ID = 0, /* derived from server puid, 28 LSB used */
SRV_HASH_KEY_ID32, /* derived from server puid, 32 bits used */
SRV_HASH_KEY_GUID, /* derived from server guid */
SRV_HASH_KEY_ADDR, /* derived from server address */
SRV_HASH_KEY_ADDR_PORT /* derived from server address and port */
};
@ -326,6 +329,7 @@ enum renegotiate_mode {
struct path_parameters {
__decl_thread(HA_RWLOCK_T param_lock);
char nego_alpn[MAX_ALPN_SIZE];
int64_t srv_hash;
#ifdef USE_QUIC
struct quic_early_transport_params tps;
#endif

View file

@ -278,6 +278,35 @@ static inline void srv_adm_set_ready(struct server *s)
srv_clr_admin_flag(s, SRV_ADMF_FMAINT);
}
static inline void srv_set_init_state(struct server *srv)
{
/* no init-state configured or the server is already disabled: don't eval init-state */
if (srv->init_state == SRV_INIT_STATE_NONE ||
srv->next_admin & (SRV_ADMF_CMAINT | SRV_ADMF_FMAINT))
return;
if (srv->init_state == SRV_INIT_STATE_FULLY_UP) {
/* initially UP, when all checks fail to bring server DOWN */
srv->next_state = SRV_ST_RUNNING;
srv->check.health = srv->check.rise + srv->check.fall - 1;
}
else if (srv->init_state == SRV_INIT_STATE_UP) {
/* initially UP, when one check fails check brings server DOWN */
srv->next_state = SRV_ST_RUNNING;
srv->check.health = srv->check.rise;
}
else if (srv->init_state == SRV_INIT_STATE_DOWN) {
/* initially DOWN, when one check is successful bring server UP */
srv->next_state = SRV_ST_STOPPED;
srv->check.health = srv->check.rise - 1;
}
else if (srv->init_state == SRV_INIT_STATE_FULLY_DOWN) {
/* initially DOWN, when all checks are successful bring server UP */
srv->next_state = SRV_ST_STOPPED;
srv->check.health = 0;
}
}
/* appends an initaddr method to the existing list. Returns 0 on failure. */
static inline int srv_append_initaddr(unsigned int *list, enum srv_initaddr addr)
{

View file

@ -50,7 +50,7 @@ struct certificate_ocsp {
int refcount_store; /* Number of ckch_store that reference this certificate_ocsp */
int refcount; /* Number of actual references to this certificate_ocsp (SSL_CTXs mostly) */
struct buffer response;
long expire;
unsigned long expire;
X509 *issuer;
STACK_OF(X509) *chain;
struct eb64_node next_update; /* Key of items inserted in ocsp_update_tree (sorted by absolute date) */

View file

@ -34,6 +34,8 @@
#define _TRC_LOC(f,l) __TRC_LOC(f, ":", l)
#define __TRC_LOC(f,c,l) f c #l
#if defined(USE_TRACE)
/* truncate a macro arg list to exactly 5 args and replace missing ones with NULL.
* The first one (a0) is always ignored.
*/
@ -139,8 +141,23 @@
&trace_no_cb, ist2(_msg, _msg_len)); \
} \
} while (0)
#else
# define TRACE_ENABLED(level, mask, args...) 0
# define TRACE(msg, mask, args...) do { /* do nothing */ } while(0)
# define TRACE_ERROR(msg, mask, args...) do { /* do nothing */ } while(0)
# define TRACE_USER(msg, mask, args...) do { /* do nothing */ } while(0)
# define TRACE_DATA(msg, mask, args...) do { /* do nothing */ } while(0)
# define TRACE_PROTO(msg, mask, args...) do { /* do nothing */ } while(0)
# define TRACE_STATE(msg, mask, args...) do { /* do nothing */ } while(0)
# define TRACE_DEVEL(msg, mask, args...) do { /* do nothing */ } while(0)
# define TRACE_ENTER(mask, args...) do { /* do nothing */ } while(0)
# define TRACE_LEAVE(mask, args...) do { /* do nothing */ } while(0)
# define TRACE_POINT(mask, args...) do { /* do nothing */ } while(0)
# define TRACE_PRINTF(level, args...) do { /* do nothing */ } while(0)
# define TRACE_PRINTF_LOC(level, args...) do { /* do nothing */ } while(0)
#endif
#if defined(DEBUG_DEV) || defined(DEBUG_FULL)
#if defined (USE_TRACE) && (defined(DEBUG_DEV) || defined(DEBUG_FULL))
# define DBG_TRACE(msg, mask, args...) TRACE(msg, mask, ##args)
# define DBG_TRACE_ERROR(msg, mask, args...) TRACE_ERROR(msg, mask, ##args)
# define DBG_TRACE_USER(msg, mask, args...) TRACE_USER(msg, mask, ##args)

View file

@ -29,7 +29,7 @@ syslog S3 -level notice {
syslog S4 -level notice {
recv
expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be4/srv4 failed.+reason: Layer4 connection problem.+info: \"Connection refused\".+check duration: [[:digit:]]+ms.+status: 0/1 DOWN."
expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be4/srv4 failed.+reason: Layer4 connection problem.+info: \"ECONNREFUSED returned by OS.*\".+check duration: [[:digit:]]+ms.+status: 0/1 DOWN."
} -start
server s1 {

View file

@ -7,12 +7,12 @@ feature ignore_unknown_macro
syslog S1 -level notice {
recv
expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be1/srv1 failed.*Connection refused at step 2 of tcp-check.*connect port 1"
expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be1/srv1 failed.*ECONNREFUSED returned by OS.* at step 2 of tcp-check.*connect port 1"
} -start
syslog S2 -level notice {
recv
expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be2/srv1 failed.*Connection refused at step 1 of tcp-check.*connect port 1"
expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be2/srv1 failed.*ECONNREFUSED returned by OS.* at step 1 of tcp-check.*connect port 1"
} -start
server s1 {

View file

@ -146,7 +146,7 @@ client c1 -connect ${hap_fe1_sock} {
} -run
# missing websocket key
client c2 -connect ${hap_fe1_sock} {
client c2_1 -connect ${hap_fe1_sock} {
txreq \
-req "GET" \
-url "/" \
@ -158,6 +158,19 @@ client c2 -connect ${hap_fe1_sock} {
expect resp.status == 400
} -run
client c2_2 -connect ${hap_fe1_sock} {
txreq \
-req "GET" \
-url "/" \
-hdr "host: 127.0.0.1" \
-hdr "connection: upgrade" \
-hdr "upgrade: proto1, websocket, proto2" \
-hdr "upgrade: proto3"
rxresp
expect resp.status == 400
} -run
# missing key on server side
client c3 -connect ${hap_fe2_sock} {
txreq \

View file

@ -50,10 +50,10 @@ client c1 -connect ${h1_feS_sock} {
haproxy h1 -cli {
# non existent backend
send "experimental-mode on; add backend be from def"
send "add backend be from def"
expect ~ "Mode is required"
send "experimental-mode on; add backend be from def_http"
send "add backend be from def_http"
expect ~ "New backend registered."
send "add server be/srv ${hsrv_fe_addr}:${hsrv_fe_port}"

View file

@ -40,29 +40,29 @@ haproxy h1 -conf {
} -start
haproxy h1 -cli {
send "experimental-mode on; del backend other"
send "del backend other"
expect ~ "No such backend."
send "experimental-mode on; del backend li"
send "del backend li"
expect ~ "Cannot delete a listen section."
send "experimental-mode on; del backend be_ref"
send "del backend be_ref"
expect ~ "This proxy cannot be removed at runtime due to other configuration elements pointing to it."
send "show stat be 2 -1"
expect ~ "be,BACKEND,"
send "experimental-mode on; del backend be"
send "del backend be"
expect ~ "Backend must be unpublished prior to its deletion."
send "unpublish backend be;"
expect ~ ".*"
send "experimental-mode on; del backend be"
send "del backend be"
expect ~ "Only a backend without server can be deleted."
send "del server be/s1"
expect ~ ".*"
send "experimental-mode on; del backend be"
send "del backend be"
expect ~ "Backend deleted."
send "show stat be 2 -1"
@ -75,7 +75,7 @@ haproxy h1 -cli {
send "unpublish backend be_unnamed_def_ref;"
expect ~ ".*"
send "experimental-mode on; del backend be_unnamed_def_ref"
send "del backend be_unnamed_def_ref"
expect ~ "Backend deleted."
send "show stat be_unnamed_def_ref 2 -1"
@ -83,6 +83,6 @@ haproxy h1 -cli {
send "unpublish backend be_unnamed_def_ref2;"
expect ~ ".*"
send "experimental-mode on; del backend be_unnamed_def_ref2"
send "del backend be_unnamed_def_ref2"
expect ~ "Backend deleted."
}

View file

@ -1,10 +1,12 @@
#REGTEST_TYPE=bug
varnishtest "Test for ECDSA/RSA selection and crt-list filters"
feature cmd "$HAPROXY_PROGRAM -cc 'version_atleast(2.8)'"
feature cmd "$HAPROXY_PROGRAM -cc 'feature(QUIC)'"
# QUIC backend are not supported with USE_QUIC_OPENSSL_COMPAT
feature cmd "$HAPROXY_PROGRAM -cc 'feature(QUIC) && !feature(QUIC_OPENSSL_COMPAT) && !feature(OPENSSL_WOLFSSL)'"
# Note that USE_OPENSSL is always set if USE_QUIC is set
# Same conditions as for ssl/tls13_ssl_crt-list_filters.vtc about TLS library versions
feature cmd "$HAPROXY_PROGRAM -cc 'ssllib_name_startswith(OpenSSL) && openssl_version_atleast(1.1.1) || feature(OPENSSL_AWSLC)'"
# This test checks if the multiple certificate types works correctly with the
# SNI, and that the negative filters are correctly excluded
#

View file

@ -6,6 +6,10 @@ haproxy h1 -conf {
thread-groups 1
.endif
.if feature(QUIC_OPENSSL_COMPAT)
limited-quic
.endif
stats socket "${tmpdir}/h1/stats" level admin
issuers-chain-path "${testdir}/certs/issuers-chain-path/ca/"
crt-base "${testdir}/certs/issuers-chain-path"

View file

@ -1562,6 +1562,16 @@ int acme_res_certificate(struct task *task, struct acme_ctx *ctx, char **errmsg)
key = ctx->store->data->key;
ctx->store->data->key = NULL;
/* OpenSSL's BIO_new_mem_buf() expects a NUL-terminated string when
* passed -1. The httpclient buffer lacks this, so manually terminate
* it here to prevent an out-of-bounds heap read during PEM parsing.
*/
if (b_room(&hc->res.buf) < 1) {
memprintf(errmsg, "ACME certificate response has no room for NUL terminator");
goto error;
}
hc->res.buf.area[hc->res.buf.data] = '\0';
/* XXX: might need a function dedicated to this, which does not read a private key */
if (ssl_sock_load_pem_into_ckch(ctx->store->path, hc->res.buf.area, ctx->store->data , errmsg) != 0)
goto error;

View file

@ -539,9 +539,6 @@ size_t appctx_rcv_buf(struct stconn *sc, struct buffer *buf, size_t count, unsig
if (applet_fl_test(appctx, APPCTX_FL_OUTBLK_ALLOC))
goto end;
if (!count)
goto end;
if (!appctx_get_buf(appctx, &appctx->outbuf)) {
TRACE_STATE("waiting for appctx outbuf allocation", APPLET_EV_RECV|APPLET_EV_BLK, appctx);
goto end;
@ -550,7 +547,8 @@ size_t appctx_rcv_buf(struct stconn *sc, struct buffer *buf, size_t count, unsig
if (flags & CO_RFL_BUF_FLUSH)
applet_fl_set(appctx, APPCTX_FL_FASTFWD);
ret = CALL_APPLET_WITH_RET(appctx->applet, rcv_buf(appctx, buf, count, flags));
if (count)
ret = CALL_APPLET_WITH_RET(appctx->applet, rcv_buf(appctx, buf, count, flags));
if (ret)
applet_fl_clr(appctx, APPCTX_FL_OUTBLK_FULL);
@ -608,7 +606,7 @@ size_t appctx_htx_snd_buf(struct appctx *appctx, struct buffer *buf, size_t coun
goto end;
}
htx_xfer(appctx_htx, buf_htx, count, HTX_XFER_DEFAULT);
htx_xfer(appctx_htx, buf_htx, count, HTX_XFER_NO_METADATA);
if (htx_is_empty(buf_htx)) {
appctx_htx->flags |= (buf_htx->flags & HTX_FL_EOM);
}

View file

@ -87,7 +87,7 @@ unsigned int gen_hash(const struct proxy* px, const char* key, unsigned long len
hash = hash_crc32(key, len);
break;
case BE_LB_HFCN_NONE:
/* use key as a hash */
/* use key as a hash. It MUST be in string format */
{
const char *_key = key;
@ -370,11 +370,11 @@ struct server *get_server_ph_post(struct stream *s, const struct server *avoid)
len -= plen + 1;
while (len && *end != '&') {
if (unlikely(!HTTP_IS_TOKEN(*p))) {
if (unlikely(!HTTP_IS_TOKEN(*end))) {
/* if in a POST, body must be URI encoded or it's not a URI.
* Do not interpret any possible binary data as a parameter.
*/
if (likely(HTTP_IS_LWS(*p))) /* eol, uncertain uri len */
if (likely(HTTP_IS_LWS(*end))) /* eol, uncertain uri len */
break;
return NULL; /* oh, no; this is not uri-encoded.
* This body does not contain parameters.
@ -545,7 +545,14 @@ struct server *get_server_expr(struct stream *s, const struct server *avoid)
if (px->lbprm.tot_used == 1)
goto hash_done;
smp = sample_fetch_as_type(px, s->sess, s, SMP_OPT_DIR_REQ | SMP_OPT_FINAL, px->lbprm.expr, SMP_T_BIN);
/* Note that if the hash-type doesn't hash the key, we must provide it
* as a string representing a number as it will be parsed by read_int64().
* Otherwise it's binary. The difference happens on samples returing
* ints (e.g. rand()) as well as IP addresses, which, when turned to
* binary, are just binary-encoded and cannot be parsed.
*/
smp = sample_fetch_as_type(px, s->sess, s, SMP_OPT_DIR_REQ | SMP_OPT_FINAL, px->lbprm.expr,
((px->lbprm.algo & BE_LB_HASH_FUNC) == BE_LB_HFCN_NONE) ? SMP_T_STR : SMP_T_BIN);
if (!smp)
return NULL;
@ -1811,7 +1818,10 @@ int connect_server(struct stream *s)
{
struct connection *cli_conn = objt_conn(strm_orig(s));
struct connection *srv_conn = NULL;
const struct mux_proto_list *mux_proto = NULL;
struct server *srv;
struct ist name = IST_NULL;
struct sample *name_smp;
int reuse_mode;
int reuse __maybe_unused = 0;
int may_use_early_data __maybe_unused = 1; // are we allowed to use early data ?
@ -1833,6 +1843,17 @@ int connect_server(struct stream *s)
if (err != SRV_STATUS_OK)
return SF_ERR_INTERNAL;
if (srv && srv->pool_conn_name_expr) {
name_smp = sample_fetch_as_type(s->be, s->sess, s,
SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
srv->pool_conn_name_expr, SMP_T_STR);
if (name_smp) {
name = ist2(name_smp->data.u.str.area,
name_smp->data.u.str.data);
}
}
hash = be_calculate_conn_hash(srv, s, s->sess, bind_addr, s->scb->dst, name);
if (!be_supports_conn_reuse(s->be))
goto skip_reuse;
@ -1844,20 +1865,7 @@ int connect_server(struct stream *s)
}
else {
const int not_first_req = s->txn.http && s->txn.http->flags & TX_NOT_FIRST;
struct ist name = IST_NULL;
struct sample *name_smp;
if (srv && srv->pool_conn_name_expr) {
name_smp = sample_fetch_as_type(s->be, s->sess, s,
SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
srv->pool_conn_name_expr, SMP_T_STR);
if (name_smp) {
name = ist2(name_smp->data.u.str.area,
name_smp->data.u.str.data);
}
}
hash = be_calculate_conn_hash(srv, s, s->sess, bind_addr, s->scb->dst, name);
err = be_reuse_connection(hash, s->sess, s->be, srv, s->scb,
s->target, not_first_req);
if (err == SF_ERR_INTERNAL)
@ -2079,7 +2087,7 @@ int connect_server(struct stream *s)
if (IS_HTX_STRM(s) && srv->use_ssl &&
(srv->ssl_ctx.alpn_str || srv->ssl_ctx.npn_str)) {
HA_RWLOCK_RDLOCK(SERVER_LOCK, &srv->path_params.param_lock);
if (srv->path_params.nego_alpn[0] == 0)
if (srv->path_params.srv_hash != hash || srv->path_params.nego_alpn[0] == 0)
may_start_mux_now = 0;
HA_RWLOCK_RDUNLOCK(SERVER_LOCK, &srv->path_params.param_lock);
}
@ -2130,9 +2138,11 @@ int connect_server(struct stream *s)
srv_conn->flags |= CO_FL_SOCKS4;
}
if (srv && srv->mux_proto && isteq(srv->mux_proto->token, ist("qmux"))) {
srv_conn->flags |= (CO_FL_QMUX_RECV|CO_FL_QMUX_SEND);
may_start_mux_now = 0;
if (may_start_mux_now) {
/* Delay MUX init if an XPRT handshake is required prior. */
mux_proto = conn_select_mux_be(srv_conn);
if (mux_proto && mux_proto->init_xprt)
may_start_mux_now = 0;
}
#if defined(USE_OPENSSL) && defined(TLSEXT_TYPE_application_layer_protocol_negotiation)
@ -2242,6 +2252,13 @@ int connect_server(struct stream *s)
}
}
}
else if (mux_proto && mux_proto->init_xprt) {
/* Add handshake layer prior to MUX init if required. Does nothing if SSL layer is active though. */
if (xprt_add_l6hs(srv_conn, mux_proto->init_xprt)) {
conn_full_close(srv_conn);
return SF_ERR_INTERNAL;
}
}
/*
* Now that the mux may have been created, we can start the xprt.

View file

@ -2180,7 +2180,17 @@ enum act_return http_action_req_cache_use(struct act_rule *rule, struct proxy *p
sec_entry = get_secondary_entry(cache_tree, res,
s->txn.http->cache_secondary_hash,
0);
if (sec_entry && sec_entry != res) {
if (!sec_entry) {
/* Secondary key miss: release the retained primary entry
* and reattach the detached row before returning.
*/
release_entry(cache_tree, res, 0);
shctx_wrlock(shctx);
if (detached)
shctx_row_reattach(shctx, entry_block);
shctx_wrunlock(shctx);
}
else if (sec_entry != res) {
/* The wrong row was added to the hot list. */
release_entry(cache_tree, res, 0);
retain_entry(sec_entry);

View file

@ -1629,11 +1629,6 @@ static int cfg_parse_global_shm_stats_file(char **args, int section_type,
struct proxy *curpx, const struct proxy *defpx,
const char *file, int line, char **err)
{
if (!experimental_directives_allowed) {
memprintf(err, "'%s' directive is experimental, must be allowed via a global 'expose-experimental-directives'", args[0]);
return -1;
}
if (global.shm_stats_file != NULL) {
memprintf(err, "'%s' already specified.\n", args[0]);
return -1;
@ -1644,7 +1639,6 @@ static int cfg_parse_global_shm_stats_file(char **args, int section_type,
return -1;
}
mark_tainted(TAINTED_CONFIG_EXP_KW_DECLARED);
global.shm_stats_file = strdup(args[1]);
return 0;
}
@ -1653,11 +1647,6 @@ static int cfg_parse_global_shm_stats_file_max_objects(char **args, int section_
struct proxy *curpx, const struct proxy *defpx,
const char *file, int line, char **err)
{
if (!experimental_directives_allowed) {
memprintf(err, "'%s' directive is experimental, must be allowed via a global 'expose-experimental-directives'", args[0]);
return -1;
}
if (shm_stats_file_max_objects != -1) {
memprintf(err, "'%s' already specified.\n", args[0]);
return -1;
@ -1668,7 +1657,6 @@ static int cfg_parse_global_shm_stats_file_max_objects(char **args, int section_
return -1;
}
mark_tainted(TAINTED_CONFIG_EXP_KW_DECLARED);
shm_stats_file_max_objects = atoi(args[1]);
return 0;
}

View file

@ -1495,7 +1495,7 @@ static int bind_parse_tls_ticket_keys(char **args, int cur_arg, struct proxy *px
goto fail;
}
keys_ref->tlskeys = malloc(TLS_TICKETS_NO * sizeof(union tls_sess_key));
keys_ref->tlskeys = malloc(array_size_or_fail(TLS_TICKETS_NO, sizeof(union tls_sess_key)));
if (!keys_ref->tlskeys) {
memprintf(err, "'%s' : allocation error", args[cur_arg+1]);
goto fail;

View file

@ -1394,7 +1394,7 @@ int parse_cfg(const struct cfgfile *cfg)
global.cfg_curr_line = 0;
global.cfg_curr_file = file;
if ((thisline = malloc(sizeof(*thisline) * linesize)) == NULL) {
if ((thisline = malloc(array_size_or_fail(sizeof(*thisline), linesize))) == NULL) {
ha_alert("Out of memory trying to allocate a buffer for a configuration line.\n");
err_code = -1;
goto err;
@ -1442,7 +1442,7 @@ next_line:
char *newline;
int newlinesize = linesize * 2;
newline = realloc(thisline, sizeof(*thisline) * newlinesize);
newline = realloc(thisline, array_size_or_fail(sizeof(*thisline), newlinesize));
if (newline == NULL) {
ha_alert("parsing [%s:%d]: line too long, cannot allocate memory.\n",
file, linenum);

View file

@ -812,7 +812,7 @@ void chk_report_conn_err(struct check *check, int errno_bck, int expired)
}
errno = unclean_errno(errno_bck);
if (conn && errno)
if (conn && !errno)
retrieve_errno_from_socket(conn);
TRACE_ENTER(CHK_EV_HCHK_END|CHK_EV_HCHK_ERR, check, 0, 0, (size_t[]){expired});

View file

@ -1151,8 +1151,13 @@ int cli_parse_cmdline(struct appctx *appctx)
*/
if (len-1 == strlen(appctx->cli_ctx.payload_pat)) {
if (strncmp(str, appctx->cli_ctx.payload_pat, len-1) == 0) {
/* end of payload was reached, rewind before the previous \n and replace it by a \0 */
b_sub(buf, strlen(appctx->cli_ctx.payload_pat) + 2);
/* end of payload was reached, rewind before the previous \n, if any, and replace it by a \0
* Otherwise, the payload is empty, just
*/
if (b_data(buf) > len)
b_sub(buf, len+1);
else
b_sub(buf, len);
*b_tail(buf) = '\0';
appctx->st1 &= ~APPCTX_CLI_ST1_PAYLOAD;
}
@ -2543,7 +2548,7 @@ static int _getsocks(char **args, char *payload, struct appctx *appctx, void *pr
/* We will send sockets MAX_SEND_FD per MAX_SEND_FD, allocate a
* buffer big enough to store the socket information.
*/
tmpbuf = malloc(MAX_SEND_FD * (1 + MAXPATHLEN + 1 + IFNAMSIZ + sizeof(int)));
tmpbuf = malloc(array_size_or_fail(MAX_SEND_FD, (1 + MAXPATHLEN + 1 + IFNAMSIZ + sizeof(int))));
if (tmpbuf == NULL) {
ha_warning("Failed to allocate memory to transfer socket information\n");
goto out;

View file

@ -196,7 +196,7 @@ int conn_notify_mux(struct connection *conn, int old_flags, int forced_wake)
* information to create one, typically from the ALPN. If we're
* done with the handshake, attempt to create one.
*/
if (unlikely(!conn->mux) && !(conn->flags & (CO_FL_WAIT_XPRT|CO_FL_QMUX_RECV|CO_FL_QMUX_SEND))) {
if (unlikely(!conn->mux) && !(conn->flags & (CO_FL_WAIT_XPRT|CO_FL_WAIT_XPRT_L6))) {
ret = conn_create_mux(conn, NULL);
if (ret < 0)
goto done;
@ -282,6 +282,7 @@ int conn_upgrade_mux_fe(struct connection *conn, void *ctx, struct buffer *buf,
struct ist mux_proto, int mode)
{
struct bind_conf *bind_conf = __objt_listener(conn->target)->bind_conf;
struct ist alpn = IST_NULL;
const struct mux_ops *old_mux, *new_mux;
void *old_mux_ctx;
const char *alpn_str = NULL;
@ -289,9 +290,9 @@ int conn_upgrade_mux_fe(struct connection *conn, void *ctx, struct buffer *buf,
if (!mux_proto.len) {
conn_get_alpn(conn, &alpn_str, &alpn_len);
mux_proto = ist2(alpn_str, alpn_len);
alpn = ist2(alpn_str, alpn_len);
}
new_mux = conn_get_best_mux(conn, mux_proto, PROTO_SIDE_FE, mode);
new_mux = conn_get_best_mux(conn, mux_proto, alpn, PROTO_SIDE_FE, mode);
old_mux = conn->mux;
/* No mux found */
@ -318,6 +319,29 @@ int conn_upgrade_mux_fe(struct connection *conn, void *ctx, struct buffer *buf,
return 0;
}
/* Returns the mux_proto_list entry compatible with <conn> frontend connection
* or NULL if nothing eligible.
* TODO duplicate code to merge with conn_install_mux_fe().
*/
const struct mux_proto_list *conn_select_mux_fe(const struct connection *conn)
{
struct bind_conf *bind_conf;
const char *alpn_str = NULL;
struct ist alpn;
int alpn_len = 0, mode;
bind_conf = __objt_listener(conn->target)->bind_conf;
if (bind_conf->mux_proto)
return bind_conf->mux_proto;
mode = conn_pr_mode_to_proto_mode(bind_conf->frontend->mode);
conn_get_alpn(conn, &alpn_str, &alpn_len);
alpn = ist2(alpn_str, alpn_len);
return conn_get_best_mux_entry(IST_NULL, alpn, PROTO_SIDE_FE,
proto_is_quic(conn->ctrl), mode);
}
/* installs the best mux for incoming connection <conn> using the upper context
* <ctx>. If the mux protocol is forced, we use it to find the best
* mux. Otherwise we use the ALPN name, if any. Returns < 0 on error.
@ -330,14 +354,14 @@ int conn_install_mux_fe(struct connection *conn, void *ctx)
if (bind_conf->mux_proto)
mux_ops = bind_conf->mux_proto->mux;
else {
struct ist mux_proto;
struct ist alpn;
const char *alpn_str = NULL;
int alpn_len = 0;
int mode = conn_pr_mode_to_proto_mode(bind_conf->frontend->mode);
conn_get_alpn(conn, &alpn_str, &alpn_len);
mux_proto = ist2(alpn_str, alpn_len);
mux_ops = conn_get_best_mux(conn, mux_proto, PROTO_SIDE_FE, mode);
alpn = ist2(alpn_str, alpn_len);
mux_ops = conn_get_best_mux(conn, IST_NULL, alpn, PROTO_SIDE_FE, mode);
if (!mux_ops)
return -1;
}
@ -353,6 +377,66 @@ int conn_install_mux_fe(struct connection *conn, void *ctx)
return conn_install_mux(conn, mux_ops, ctx, bind_conf->frontend, conn->owner);
}
/* Returns the mux_proto_list entry compatible with <conn> backend connection
* or NULL if nothing eligible.
* TODO duplicate code to merge with conn_install_mux_be/chk().
*/
const struct mux_proto_list *conn_select_mux_be(const struct connection *conn)
{
struct session *sess;
struct server *srv;
struct proxy *prx;
struct check *check;
struct ist alpn;
const char *alpn_str = NULL;
int alpn_len = 0, mode;
sess = conn->owner;
if (sess && obj_type(sess->origin) == OBJ_TYPE_CHECK) {
check = __objt_check(sess->origin);
if (check->mux_proto)
return check->mux_proto;
mode = tcpchk_rules_type_to_proto_mode(check->tcpcheck->rs->flags);
conn_get_alpn(conn, &alpn_str, &alpn_len);
alpn = ist2(alpn_str, alpn_len);
return conn_get_best_mux_entry(IST_NULL, alpn, PROTO_SIDE_BE,
proto_is_quic(conn->ctrl), mode);
}
else {
srv = objt_server(conn->target);
prx = objt_proxy(conn->target);
if (srv)
prx = srv->proxy;
if (!prx) {
/* Target should either be a server or a proxy.
* USE a full a BUG_ON() once considered definitive.
*/
BUG_ON_HOT(1);
return NULL;
}
mode = conn_pr_mode_to_proto_mode(prx->mode);
if (srv && srv->mux_proto)
return srv->mux_proto;
if (!conn_get_alpn(conn, &alpn_str, &alpn_len)) {
if (srv && srv->path_params.nego_alpn[0]) {
alpn_str = srv->path_params.nego_alpn;
alpn_len = strlen(alpn_str);
}
}
alpn = ist2(alpn_str, alpn_len);
return conn_get_best_mux_entry(IST_NULL, alpn, PROTO_SIDE_BE,
proto_is_quic(conn->ctrl), mode);
}
}
/* installs the best mux for outgoing connection <conn> using the upper context
* <ctx>. If the server mux protocol is forced, we use it to find the best mux.
* It's also possible to specify an alternative mux protocol <force_mux_ops>,
@ -380,20 +464,20 @@ int conn_install_mux_be(struct connection *conn, void *ctx, struct session *sess
mux_ops = force_mux_ops;
}
else {
struct ist mux_proto;
struct ist alpn;
const char *alpn_str = NULL;
int alpn_len = 0;
int mode = conn_pr_mode_to_proto_mode(prx->mode);
if (!conn_get_alpn(conn, &alpn_str, &alpn_len)) {
if (srv && srv->path_params.nego_alpn[0]) {
if (srv && srv->path_params.srv_hash == conn->hash_node.key && srv->path_params.nego_alpn[0]) {
alpn_str = srv->path_params.nego_alpn;
alpn_len = strlen(alpn_str);
}
}
mux_proto = ist2(alpn_str, alpn_len);
alpn = ist2(alpn_str, alpn_len);
mux_ops = conn_get_best_mux(conn, mux_proto, PROTO_SIDE_BE, mode);
mux_ops = conn_get_best_mux(conn, IST_NULL, alpn, PROTO_SIDE_BE, mode);
if (!mux_ops)
return -1;
}
@ -434,15 +518,15 @@ int conn_install_mux_chk(struct connection *conn, void *ctx, struct session *ses
if (check->mux_proto)
mux_ops = check->mux_proto->mux;
else {
struct ist mux_proto;
struct ist alpn;
const char *alpn_str = NULL;
int alpn_len = 0;
int mode = tcpchk_rules_type_to_proto_mode(check->tcpcheck->rs->flags);
conn_get_alpn(conn, &alpn_str, &alpn_len);
mux_proto = ist2(alpn_str, alpn_len);
alpn = ist2(alpn_str, alpn_len);
mux_ops = conn_get_best_mux(conn, mux_proto, PROTO_SIDE_BE, mode);
mux_ops = conn_get_best_mux(conn, IST_NULL, alpn, PROTO_SIDE_BE, mode);
if (!mux_ops)
return -1;
}
@ -763,6 +847,43 @@ int xprt_add_hs(struct connection *conn)
return 0;
}
/* Activates an <xprt> layer on top of <conn> connection. This handshake layer
* should be designed to work on top of the layer 6. If SSL is active and its
* handshake still in progress, this function does nothing.
*
* Returns 0 on success else a negative error code.
*/
int xprt_add_l6hs(struct connection *conn, int xprt)
{
const struct xprt_ops *ops = xprt_get(xprt);
void *ops_ctx = NULL;
/* Only QMux is supported as handshake on top of layer6 for now. */
BUG_ON(xprt != XPRT_QMUX);
if (conn->flags & CO_FL_ERROR)
return -1;
/* Do nothing if SSL is in used but handshake still in progress. In
* this case, xprt layer will be added on handshake completion.
*/
if (conn->xprt == xprt_get(XPRT_SSL) &&
(conn->flags & CO_FL_WAIT_L6_CONN)) {
return 0;
}
if (ops->init(conn, &ops_ctx))
return -1;
ops->add_xprt(conn, ops_ctx, conn->xprt_ctx, conn->xprt, NULL, NULL);
conn->xprt = ops;
conn->xprt_ctx = ops_ctx;
/* Reset XPRT READY flag before the next conn_xprt_start(). */
conn->flags &= ~CO_FL_XPRT_READY;
return 0;
}
/* returns a short name for an error, typically the same as the enum name
* without the "CO_ER_" prefix, or an empty string for no error (better eye
* catching in logs). This is more compact for some debug cases.
@ -1991,7 +2112,7 @@ void list_mux_proto(FILE *out)
fprintf(out, "Available multiplexer protocols :\n"
"(protocols marked as <default> cannot be specified using 'proto' keyword)\n");
list_for_each_entry(item, &mux_proto_list.list, list) {
proto = item->token;
proto = item->mux_proto;
if (item->mode == PROTO_MODE_ANY)
mode = "TCP|HTTP";

View file

@ -79,7 +79,7 @@ static struct dict_entry *__dict_lookup(struct dict *d, const char *s)
*/
struct dict_entry *dict_insert(struct dict *d, char *s)
{
struct dict_entry *de;
struct dict_entry *de, *tree_de;
struct ebpt_node *n;
HA_RWLOCK_RDLOCK(DICT_LOCK, &d->rwlock);
@ -97,13 +97,18 @@ struct dict_entry *dict_insert(struct dict *d, char *s)
HA_RWLOCK_WRLOCK(DICT_LOCK, &d->rwlock);
n = ebis_insert(&d->values, &de->value);
HA_RWLOCK_WRUNLOCK(DICT_LOCK, &d->rwlock);
if (n != &de->value) {
tree_de = container_of(n, struct dict_entry, value);
if (tree_de == de)
HA_RWLOCK_WRUNLOCK(DICT_LOCK, &d->rwlock);
else {
/* another entry was already there, we'll return it, kill
* ours and bump the other's refcount before returning it.
*/
HA_ATOMIC_INC(&tree_de->refcount);
HA_RWLOCK_WRUNLOCK(DICT_LOCK, &d->rwlock);
free_dict_entry(de);
de = container_of(n, struct dict_entry, value);
}
return de;
return tree_de;
}
@ -117,10 +122,11 @@ void dict_entry_unref(struct dict *d, struct dict_entry *de)
if (!de)
return;
if (HA_ATOMIC_SUB_FETCH(&de->refcount, 1) != 0)
return;
HA_RWLOCK_WRLOCK(DICT_LOCK, &d->rwlock);
if (HA_ATOMIC_SUB_FETCH(&de->refcount, 1) != 0) {
HA_RWLOCK_WRUNLOCK(DICT_LOCK, &d->rwlock);
return;
}
ebpt_delete(&de->value);
HA_RWLOCK_WRUNLOCK(DICT_LOCK, &d->rwlock);

View file

@ -194,7 +194,7 @@ int dns_send_nameserver(struct dns_nameserver *ns, void *buf, size_t len)
struct ist myist;
myist = ist2(buf, len);
ret = dns_ring_write(ns->stream->ring_req, DNS_TCP_MSG_MAX_SIZE, NULL, 0, &myist, 1);
ret = dns_ring_write(ns->stream->ring_req, DNS_TCP_MSG_MAX_SIZE, NULL, 0, &myist, 1);
if (!ret) {
ns->counters->snd_error++;
return -1;
@ -215,7 +215,7 @@ void dns_session_free(struct dns_session *);
*/
ssize_t dns_recv_nameserver(struct dns_nameserver *ns, void *data, size_t size)
{
ssize_t ret = -1;
ssize_t ret = -1;
if (ns->dgram) {
struct dgram_conn *dgram = &ns->dgram->conn;
@ -475,7 +475,6 @@ int dns_dgram_init(struct dns_nameserver *ns, struct sockaddr_storage *sk)
dgram->conn.t.sock.fd = -1;
dgram->conn.addr.to = *sk;
HA_SPIN_INIT(&dgram->conn.lock);
ns->dgram = dgram;
dgram->ofs_req = ~0; /* init ring offset */
dgram->ring_req = dns_ring_new(2*DNS_TCP_MSG_RING_MAX_SIZE);
@ -490,6 +489,7 @@ int dns_dgram_init(struct dns_nameserver *ns, struct sockaddr_storage *sk)
ha_alert("nameserver sets too many watchers > 255 on ring. This is a bug and should not happen.\n");
goto out;
}
ns->dgram = dgram;
return 0;
out:
dns_ring_free(dgram->ring_req);
@ -913,6 +913,7 @@ static int dns_session_init(struct appctx *appctx)
return 0;
error:
sockaddr_free(&addr);
return -1;
}
@ -1345,8 +1346,8 @@ int dns_stream_init(struct dns_nameserver *ns, struct server *srv)
{
struct dns_stream_server *dss = NULL;
dss = calloc(1, sizeof(*dss));
if (!dss) {
dss = calloc(1, sizeof(*dss));
if (!dss) {
ha_alert("memory allocation error initializing dns tcp server '%s'.\n", srv->id);
goto out;
}
@ -1362,7 +1363,7 @@ int dns_stream_init(struct dns_nameserver *ns, struct server *srv)
}
/* Create the task associated to the resolver target handling conns */
if ((dss->task_req = task_new_anywhere()) == NULL) {
ha_alert("memory allocation error initializing the ring for dns tcp server '%s'.\n", srv->id);
ha_alert("memory allocation error initializing req task for dns tcp server '%s'.\n", srv->id);
goto out;
}
@ -1379,7 +1380,7 @@ int dns_stream_init(struct dns_nameserver *ns, struct server *srv)
/* Create the task associated to the resolver target handling conns */
if ((dss->task_rsp = task_new_anywhere()) == NULL) {
ha_alert("memory allocation error initializing the ring for dns tcp server '%s'.\n", srv->id);
ha_alert("memory allocation error initializing rsp task for dns tcp server '%s'.\n", srv->id);
goto out;
}
@ -1389,7 +1390,7 @@ int dns_stream_init(struct dns_nameserver *ns, struct server *srv)
/* Create the task associated to the resolver target handling conns */
if ((dss->task_idle = task_new_anywhere()) == NULL) {
ha_alert("memory allocation error initializing the ring for dns tcp server '%s'.\n", srv->id);
ha_alert("memory allocation error initializing idle task for dns tcp server '%s'.\n", srv->id);
goto out;
}
@ -1425,7 +1426,7 @@ int init_dns_buffers()
if (!dns_msg_trash)
return 0;
return 1;
return 1;
}
void deinit_dns_buffers()

View file

@ -110,7 +110,7 @@ static void usermsgs_put(const struct ist *msg)
{
/* Allocate the buffer if not already done. */
if (unlikely(b_is_null(&usermsgs_buf))) {
usermsgs_buf.area = malloc(USER_MESSAGES_BUFSIZE * sizeof(char));
usermsgs_buf.area = malloc(array_size_or_fail(USER_MESSAGES_BUFSIZE, sizeof(char)));
if (usermsgs_buf.area)
usermsgs_buf.size = USER_MESSAGES_BUFSIZE;
}

View file

@ -349,7 +349,7 @@ int prepare_external_check(struct check *check)
case PR_MODE_CLI: svmode = "cli"; break;
case PR_MODE_SYSLOG: svmode = "syslog"; break;
case PR_MODE_PEERS: svmode = "peers"; break;
case PR_MODE_HTTP: svmode = (s->mux_proto) ? s->mux_proto->token.ptr : "h1"; break;
case PR_MODE_HTTP: svmode = (s->mux_proto) ? s->mux_proto->mux_proto.ptr : "h1"; break;
case PR_MODE_TCP: svmode = "tcp"; break;
case PR_MODE_SPOP: svmode = "spop"; break;
/* all valid cases must be enumerated above, below is to avoid a warning */

View file

@ -644,7 +644,7 @@ static int cfg_fcgi_apps_postparser()
px->options2 |= PR_O2_RSTRICT_REQ_HDR_NAMES_DEL;
for (srv = px->srv; srv; srv = srv->next) {
if (srv->mux_proto && isteq(srv->mux_proto->token, ist("fcgi"))) {
if (srv->mux_proto && isteq(srv->mux_proto->mux_proto, ist("fcgi"))) {
nb_fcgi_srv++;
if (fcgi_conf)
continue;

View file

@ -1166,7 +1166,7 @@ int init_pollers()
struct poller *bp;
/* always provide an aligned fdtab */
if ((fdtab = ha_aligned_zalloc(64, global.maxsock * sizeof(*fdtab))) == NULL) {
if ((fdtab = ha_aligned_zalloc(64, array_size_or_fail(global.maxsock, sizeof(*fdtab)))) == NULL) {
ha_alert("Not enough memory to allocate %d entries for fdtab!\n", global.maxsock);
goto fail_tab;
}

View file

@ -274,17 +274,17 @@ void h1_parse_connection_header(struct h1m *h1m, struct ist *value)
/* Parse the Upgrade: header of an HTTP/1 request.
* If "websocket" is found, set H1_MF_UPG_WEBSOCKET flag
* If "h2c" or "h2" found, set H1_MF_UPG_H2C flag.
* If "h2c" or "h2" found, the value is skipped.
*/
void h1_parse_upgrade_header(struct h1m *h1m, struct ist value)
void h1_parse_upgrade_header(struct h1m *h1m, struct ist *value)
{
char *e, *n;
char *e, *n, *p;
struct ist word;
h1m->flags &= ~(H1_MF_UPG_WEBSOCKET|H1_MF_UPG_H2C);
word.ptr = value.ptr - 1; // -1 for next loop's pre-increment
e = istend(value);
word.ptr = value->ptr - 1; // -1 for next loop's pre-increment
p = value->ptr;
e = value->ptr + value->len;
value->len = 0;
while (++word.ptr < e) {
/* skip leading delimiter and blanks */
@ -301,9 +301,20 @@ void h1_parse_upgrade_header(struct h1m *h1m, struct ist value)
if (isteqi(word, ist("websocket")))
h1m->flags |= H1_MF_UPG_WEBSOCKET;
else if (isteqi(word, ist("h2c")) || isteqi(word, ist("h2")))
h1m->flags |= H1_MF_UPG_H2C;
goto skip_val;
word.ptr = n;
if (value->ptr + value->len == p) {
/* no rewrite done till now */
value->len = n - value->ptr;
}
else {
if (value->len)
value->ptr[value->len++] = ',';
istcat(value, word, e - value->ptr);
}
skip_val:
word.ptr = p = n;
}
}
@ -983,7 +994,11 @@ int h1_headers_to_hdr_list(char *start, const char *stop,
}
}
else if (isteqi(n, ist("upgrade"))) {
h1_parse_upgrade_header(h1m, v);
h1_parse_upgrade_header(h1m, &v);
if (!v.len) {
/* skip it */
break;
}
}
else if (!(h1m->flags & H1_MF_RESP) && isteqi(n, ist("host"))) {
if (host_idx == -1) {

View file

@ -215,20 +215,6 @@ static int h1_postparse_req_hdrs(struct h1m *h1m, union h1_sl *h1sl, struct htx
flags |= h1m_htx_sl_flags(h1m);
/* Remove Upgrade header in problematic cases :
* - "h2c" or "h2" token specified as token
*/
if ((h1m->flags & (H1_MF_CONN_UPG|H1_MF_UPG_H2C)) == (H1_MF_CONN_UPG|H1_MF_UPG_H2C)) {
int i;
for (i = 0; hdrs[i].n.len; i++) {
if (isteqi(hdrs[i].n, ist("upgrade")))
hdrs[i].v = IST_NULL;
}
h1m->flags &=~ H1_MF_CONN_UPG;
flags &= ~HTX_SL_F_CONN_UPG;
}
sl = htx_add_stline(htx, HTX_BLK_REQ_SL, flags, meth, uri, vsn);
if (!sl || !htx_add_all_headers(htx, hdrs))
goto error;

View file

@ -1713,7 +1713,7 @@ void haproxy_init_args(int argc, char **argv)
oldpids_sig = SIGTERM; /* terminate immediately */
while (argc > 1 && argv[1][0] != '-') {
char * endptr = NULL;
oldpids = realloc(oldpids, (nb_oldpids + 1) * sizeof(int));
oldpids = realloc(oldpids, array_size_or_fail(nb_oldpids + 1, sizeof(int)));
if (!oldpids) {
ha_alert("Cannot allocate old pid : out of memory.\n");
exit(1);
@ -1926,20 +1926,37 @@ static void dump_registered_keywords(void)
/* Generate a random cluster-secret in case the setting is not provided in the
* configuration. This allows to use features which rely on it albeit with some
* limitations.
* limitations. The function doesn't (solely) use ha_random64() because this
* secret is permanent, and ha_random64() can easily be leaked at various
* places.
*/
static void generate_random_cluster_secret()
{
/* used as a default random cluster-secret if none defined. */
uint64_t rand;
union {
uint64_t by64[2];
uint32_t by32[4];
uchar by8[16];
} rand;
/* The caller must not overwrite an already defined secret. */
BUG_ON(cluster_secret_isset);
BUG_ON(sizeof(global.cluster_secret) != sizeof(rand));
#ifdef USE_OPENSSL
if (RAND_bytes(rand.by8, sizeof(rand.by8)) != 1)
#endif
{
/* no SSL or not working, fall back to other sources */
rand.by64[0] = ha_random64();
rand.by64[1] = ha_random64();
rand.by32[0] ^= ((random() & 0x00ffff00) << 8) | ((random() & 0x00ffff00) >> 8);
rand.by32[1] ^= ((random() & 0x00ffff00) << 8) | ((random() & 0x00ffff00) >> 8);
rand.by32[2] ^= ((random() & 0x00ffff00) << 8) | ((random() & 0x00ffff00) >> 8);
rand.by32[3] ^= ((random() & 0x00ffff00) << 8) | ((random() & 0x00ffff00) >> 8);
}
rand = ha_random64();
memcpy(global.cluster_secret, &rand, sizeof(rand));
rand = ha_random64();
memcpy(global.cluster_secret + sizeof(rand), &rand, sizeof(rand));
cluster_secret_isset = 1;
}
@ -3266,6 +3283,105 @@ static void set_identity(const char *program_name)
#endif
}
#if defined(CLONE_NEWUSER)
/* Setup the user namespace after a successful unshare(CLONE_NEWUSER). We do not
* return a value because this is best-effort; it is only useful in very rare
* situations (see below), and if it fails, we let subsequent setuid() and/or
* setgid() calls fail later.
*/
static void setup_user_ns(uid_t euid, gid_t egid)
{
char buf[64];
int n, ret, fd;
/* Creating uid_map and gid_map files is required for some specific
* situations where we attempt to setuid()/setgid() to the user/group
* we are already running as after a successful unshare(CLONE_NEWUSER).
* While these directives would effectively be no-ops, we still support
* them because it is possible that such setups exist in the wild. For
* instance, if haproxy is run through a systemd file containing
* "User=someuser" while the configuration file has "user someuser", we
* would be in this situation, and a user enabling "chroot auto" in this
* case would end up with seemingly unrelated setuid() failures.
*
* See user_namespaces(7) for more information.
*/
if (global.uid > 0) {
n = snprintf(buf, sizeof(buf), "%u %u 1\n", euid, euid);
fd = open("/proc/self/uid_map", O_WRONLY);
if (fd == -1)
return;
ret = write(fd, buf, n);
close(fd);
if (ret != n)
return;
}
if (global.gid > 0) {
/* In order to write to the gid_map file, we first need to write
* "deny" to the setgroups file. We allow for failure because
* older kernels do not support the setgroups file.
*/
fd = open("/proc/self/setgroups", O_WRONLY);
if (fd != -1) {
ret = write(fd, "deny", 4);
close(fd);
if (ret != 4)
return;
}
n = snprintf(buf, sizeof(buf), "%u %u 1\n", egid, egid);
fd = open("/proc/self/gid_map", O_WRONLY);
if (fd == -1)
return;
ret = write(fd, buf, n);
close(fd);
if (ret != n)
return;
}
}
#endif
static int do_chroot(const char *prog, const char *path)
{
const char *chroot_dir = path;
int error = 0;
if (strcmp(path, "auto") == 0) {
/* When "chroot auto" is used, we attempt to chroot to an
* anonymous and read-only directory.
*/
char tmpdir[] = "/tmp/haproxy.XXXXXX";
chroot_dir = mkdtemp(tmpdir);
if (chroot_dir == NULL) {
ha_alert("[%s.main()] Cannot create(%s) for chroot auto.\n",
prog, tmpdir);
return -1;
}
error = chdir(tmpdir);
/* We can call rmdir() here; we hold a reference to the
* directory since it is our CWD (and if chdir() failed we still
* want to remove the directory).
*/
DISGUISE(rmdir(tmpdir));
if (!error)
error = chroot(".");
} else if (strcmp(path, "/") != 0) {
error = chroot(path);
}
if (!error)
error = chdir("/");
if (error) {
ha_alert("[%s.main()] Cannot chroot(%s).\n", prog, chroot_dir);
return -1;
}
return 0;
}
int main(int argc, char **argv)
{
struct rlimit limit;
@ -3582,16 +3698,48 @@ int main(int argc, char **argv)
}
}
/* privileged users should use chroot whenever possible; use chroot /
* if really not wanted.
*/
if (!global.chroot) {
int chroot_permitted = geteuid() == 0;
#if defined(USE_PRCTL) && defined(PR_CAPBSET_READ) && defined(CAP_SYS_CHROOT)
chroot_permitted &= (prctl(PR_CAPBSET_READ, CAP_SYS_CHROOT, 0, 0, 0) == 1);
#endif
if (chroot_permitted) {
ha_warning("[%s.main()] HAProxy was started as root without any 'chroot' "
"directive. A chroot limits filesystem access of an intruder "
"to a single, preferably empty, directory. It is strongly recommended "
"to enable this feature whenever possible (it's always possible when "
"starting as root), via 'chroot auto' in the global section. If you "
"think you have good reasons for running outside a chroot, explicitly "
"configure 'chroot /' to silence this warning.\n", argv[0]);
}
}
#ifdef CLONE_NEWUSER
/* When we aren't root and intend to chroot, we try the Linux-only
* unshare(CLONE_NEWUSER) mechanism if available to allow chroot as an
* unprivileged user. If that doesn't work, we just let the subsequent
* chroot() fail as it would have previously.
*/
if (geteuid() != 0 && global.chroot != NULL) {
uid_t euid = geteuid();
gid_t egid = getegid();
if (unshare(CLONE_NEWUSER) == 0)
setup_user_ns(euid, egid);
}
#endif
/* Must chroot and setgid/setuid in the children */
/* chroot if needed */
if (global.chroot != NULL) {
if (chroot(global.chroot) == -1 || chdir("/") == -1) {
ha_alert("[%s.main()] Cannot chroot(%s).\n", argv[0], global.chroot);
if (nb_oldpids)
tell_old_pids(SIGTTIN);
protocol_unbind_all();
exit(1);
}
if (global.chroot != NULL && do_chroot(argv[0], global.chroot) != 0) {
if (nb_oldpids)
tell_old_pids(SIGTTIN);
protocol_unbind_all();
exit(1);
}
ha_free(&global.chroot);

View file

@ -12,7 +12,7 @@
#include <haproxy/istbuf.h>
#include <haproxy/pipe.h>
#include <haproxy/pool.h>
#include <haproxy/proxy-t.h>
#include <haproxy/proxy.h>
#include <haproxy/sc_strm.h>
#include <haproxy/stconn-t.h>
#include <haproxy/stream.h>
@ -788,7 +788,7 @@ static void hstream_parse_uri(struct ist uri, struct hstream *hs)
} while (*next);
if (use_rand)
result = ((long long)ha_random64() * result) / ((long long)RAND_MAX + 1);
result = ((long long)statistical_prng() * result) / 0xFFFFFFFFU;
switch (*arg) {
case 's':
@ -1223,11 +1223,22 @@ static int hstream_build_responses(void)
#if defined(USE_LINUX_SPLICE)
static void hstream_init_splicing(void)
{
struct proxy *px;
unsigned int pipesize = 65536;
int haterm_used = 0;
if (!(global.tune.options & GTUNE_USE_SPLICE) || !global.maxpipes)
return;
for (px = proxies_list; px; px = px->next) {
if ((px->cap & PR_CAP_FE) && !(px->flags & PR_FL_DISABLED) && px->stream_new_from_sc == hstream_new) {
haterm_used = 1;
break;
}
}
if (!haterm_used)
return;
if (global.tune.pipesize)
pipesize = global.tune.pipesize;
@ -1257,7 +1268,7 @@ static void hstream_init_splicing(void)
master_pipesize = 0;
}
else
ha_warning("Splicing in haterm is limited to %lu bytes (too old kernel)\n", (ulong)master_pipesize);
ha_warning("Splicing in haterm is limited to %lu bytes\n", (ulong)master_pipesize);
}
}
else

View file

@ -2949,20 +2949,20 @@ __LJMP static int hlua_socket_receive_yield(struct lua_State *L, int status, lua
/* remove final \r\n. */
if (nblk == 1) {
if (blk1[len1-1] == '\n') {
if (len1 && blk1[len1-1] == '\n') {
len1--;
skip_at_end++;
if (blk1[len1-1] == '\r') {
if (len1 && blk1[len1-1] == '\r') {
len1--;
skip_at_end++;
}
}
}
else {
if (blk2[len2-1] == '\n') {
if (len2 && blk2[len2-1] == '\n') {
len2--;
skip_at_end++;
if (blk2[len2-1] == '\r') {
if (len2 && blk2[len2-1] == '\r') {
len2--;
skip_at_end++;
}

View file

@ -376,7 +376,7 @@ out:
* This function tries to destroy the httpclient if it wasn't running.
* If it was running, stop the client and ask it to autodestroy itself.
*
* Once this function is used, all pointer sto the client must be removed
* Once this function is used, all pointers to the client must be removed
*
*/
void httpclient_stop_and_destroy(struct httpclient *hc)

View file

@ -91,7 +91,7 @@ void hc_cli_res_end_cb(struct httpclient *hc)
static int hc_cli_parse(char **args, char *payload, struct appctx *appctx, void *private)
{
struct hcli_svc_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
struct httpclient *hc;
struct httpclient *hc = NULL;
char *err = NULL;
enum http_meth_t meth;
char *meth_str;
@ -159,6 +159,7 @@ static int hc_cli_parse(char **args, char *payload, struct appctx *appctx, void
err:
memprintf(&err, "Can't start the HTTP client%s.\n", err ? err : "");
httpclient_destroy(hc);
return cli_err(appctx, err);
}

View file

@ -681,7 +681,7 @@ struct htx_blk *htx_replace_blk_value(struct htx *htx, struct htx_blk *blk,
}
else { /* Do a defrag first (it is always an expansion) */
struct htx_blk tmpblk;
struct buffer *chunk = alloc_trash_chunk();
struct buffer *chunk = alloc_trash_chunk_sz(n.len + v.len + delta);
void *ptr;
if (!chunk)
@ -730,17 +730,20 @@ struct htx_blk *htx_replace_blk_value(struct htx *htx, struct htx_blk *blk,
* - HTX_XFER_KEEP_SRC_BLKS: source blocks are not removed
* - HTX_XFER_PARTIAL_HDRS_COPY: partial headers and trailers part can be xferred
* - HTX_XFER_HDRS_ONLY: Only the headers part is xferred
* - HTX_XFER_NO_METADATA: <count> don't include meta-data, only payload
*/
size_t htx_xfer(struct htx *dst, struct htx *src, size_t count, unsigned int flags)
{
struct htx_blk *blk, *last_dstblk;
size_t ret = 0;
size_t meta_sz = (flags & HTX_XFER_NO_METADATA) ? 0 : sizeof(*blk);
uint32_t max, last_dstblk_sz;
int dst_full = 0;
last_dstblk = NULL;
last_dstblk_sz = 0;
for (blk = htx_get_head_blk(src); blk && count; blk = htx_get_next_blk(src, blk)) {
for (blk = htx_get_head_blk(src); blk && count > meta_sz; blk = htx_get_next_blk(src, blk)) {
struct ist v;
enum htx_blk_type type;
uint32_t sz;
@ -755,7 +758,10 @@ size_t htx_xfer(struct htx *dst, struct htx *src, size_t count, unsigned int fla
type != HTX_BLK_HDR && type != HTX_BLK_EOH)
break;
max = htx_get_max_blksz(dst, count);
max = htx_free_data_space(dst);
if (max > count - meta_sz)
max = count - meta_sz;
if (!max)
break;
@ -771,8 +777,8 @@ size_t htx_xfer(struct htx *dst, struct htx *src, size_t count, unsigned int fla
}
last_dstblk = htx_get_tail_blk(dst);
last_dstblk_sz = v.len;
count -= sizeof(*blk) + v.len;
ret += sizeof(*blk) + v.len;
count -= meta_sz + v.len;
ret += meta_sz + v.len;
if (v.len != sz) {
dst_full = 1;
goto stop;
@ -793,8 +799,8 @@ size_t htx_xfer(struct htx *dst, struct htx *src, size_t count, unsigned int fla
last_dstblk->info = blk->info;
htx_memcpy(htx_get_blk_ptr(dst, last_dstblk), htx_get_blk_ptr(src, blk), sz);
last_dstblk_sz = sz;
count -= sizeof(*blk) + sz;
ret += sizeof(*blk) + sz;
count -= meta_sz + sz;
ret += meta_sz + sz;
break;
}
@ -826,7 +832,7 @@ size_t htx_xfer(struct htx *dst, struct htx *src, size_t count, unsigned int fla
/* Remove partial headers/trailers from <dst> and rollback on <src> to not remove them later */
while (type == HTX_BLK_REQ_SL || type == HTX_BLK_RES_SL || type == HTX_BLK_HDR || type == HTX_BLK_TLR) {
BUG_ON(type != htx_get_blk_type(blk));
ret -= sizeof(*blk) + htx_get_blksz(blk);
ret -= meta_sz + htx_get_blksz(blk);
htx_remove_blk(dst, dstblk);
dstblk = htx_get_tail_blk(dst);
blk = htx_get_prev_blk(src, blk);

View file

@ -448,7 +448,7 @@ static int build_and_check_tag(jwe_enc enc, struct jwt_item items[JWE_ELT_MAX],
int retval = 1;
const EVP_MD *hash = NULL;
int mac_key_len = 0;
uint64_t aad_len = my_htonll(items[JWE_ELT_JOSE].length << 3);
uint64_t aad_len = my_htonll((uint64_t)items[JWE_ELT_JOSE].length << 3);
struct buffer *tag_data = alloc_trash_chunk();
struct buffer *hmac = alloc_trash_chunk();

View file

@ -54,13 +54,15 @@ static size_t EVP_PKEY_EC_to_pub_jwk(EVP_PKEY *pkey, char *dst, size_t dsize)
int ret = 0;
const char *crv = NULL;
#if HA_OPENSSL_VERSION_NUMBER > 0x30000000L
#if HA_OPENSSL_VERSION_NUMBER >= 0x30000000L
char curve[32] = {};
size_t curvelen;
int nid;
EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_X, &x);
EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_Y, &y);
if (EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_X, &x) == 0)
goto out;
if (EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_Y, &y) == 0)
goto out;
if (EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, curve, sizeof(curve), &curvelen) == 0)
goto out;
@ -144,7 +146,7 @@ static size_t EVP_PKEY_RSA_to_pub_jwk(EVP_PKEY *pkey, char *dst, size_t dsize)
struct buffer *str_n = NULL, *str_e = NULL;
int ret = 0;
#if HA_OPENSSL_VERSION_NUMBER > 0x30000000L
#if HA_OPENSSL_VERSION_NUMBER >= 0x30000000L
if ((EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &n)) == 0)
goto out;
@ -292,7 +294,7 @@ enum jwt_alg EVP_PKEY_to_jws_alg(EVP_PKEY *pkey)
enum jwt_alg alg = JWS_ALG_NONE;
if (EVP_PKEY_base_id(pkey) == EVP_PKEY_EC) {
#if HA_OPENSSL_VERSION_NUMBER > 0x30000000L
#if HA_OPENSSL_VERSION_NUMBER >= 0x30000000L
char curve[32] = {};
size_t curvelen;
int nid;

View file

@ -324,6 +324,8 @@ static int convert_ecdsa_sig(const struct jwt_ctx *ctx, struct buffer *signature
ec_S = BN_bin2bn((unsigned char *)(b_orig(signature) + bignum_len), bignum_len, NULL);
if (!ec_R || !ec_S) {
BN_free(ec_R);
BN_free(ec_S);
retval = JWT_VRFY_INVALID_TOKEN;
goto end;
}

View file

@ -20,6 +20,7 @@
#include <haproxy/api.h>
#include <haproxy/backend.h>
#include <haproxy/errors.h>
#include <haproxy/guid.h>
#include <haproxy/queue.h>
#include <haproxy/server.h>
#include <haproxy/tools.h>
@ -82,6 +83,7 @@ static inline u32 chash_compute_server_key(struct server *s)
{
enum srv_hash_key hash_key = s->hash_key;
struct server_inetaddr srv_addr;
const char *guid_key = NULL;
u32 key;
/* If hash-key is addr or addr-port then we need the address, but if we
@ -96,6 +98,11 @@ static inline u32 chash_compute_server_key(struct server *s)
}
break;
case SRV_HASH_KEY_GUID:
guid_key = guid_get(&s->guid);
if (!guid_key)
hash_key = SRV_HASH_KEY_ID;
break;
default:
break;
}
@ -121,6 +128,14 @@ static inline u32 chash_compute_server_key(struct server *s)
}
break;
case SRV_HASH_KEY_GUID:
key = XXH32(guid_key, strlen(guid_key), 0);
break;
case SRV_HASH_KEY_ID32:
key = full_hash(htonl(s->puid));
break;
case SRV_HASH_KEY_ID:
default:
key = s->puid * SRV_EWGHT_RANGE;

View file

@ -116,7 +116,7 @@ static int compute_ideal_maxconn()
{
int ssl_sides = !!global.ssl_used_frontend + !!global.ssl_used_backend;
int engine_fds = global.ssl_used_async_engines * ssl_sides;
int pipes = compute_ideal_maxpipes();
int pipes = global.maxpipes ? global.maxpipes : compute_ideal_maxpipes();
int remain = MAX(rlim_fd_cur_at_boot, rlim_fd_max_at_boot);
int maxconn;

View file

@ -1608,7 +1608,7 @@ struct logger *dup_logger(struct logger *def)
goto error;
}
if (def->lb.smp_rgs) {
cpy->lb.smp_rgs = malloc(sizeof(*cpy->lb.smp_rgs) * def->lb.smp_rgs_sz);
cpy->lb.smp_rgs = malloc(array_size_or_fail(sizeof(*cpy->lb.smp_rgs), def->lb.smp_rgs_sz));
if (!cpy->lb.smp_rgs)
goto error;
memcpy(cpy->lb.smp_rgs, def->lb.smp_rgs,
@ -3319,7 +3319,7 @@ struct ist *build_log_header(struct log_header hdr, size_t *nbelem)
break;
}
else if (metadata && metadata[LOG_META_TIME].len >= LOG_ISOTIME_MINLEN) {
int month;
uint month;
char *timestamp = metadata[LOG_META_TIME].ptr;
/* iso time always begins like this: '1970-01-01T00:00:00' */
@ -5499,7 +5499,7 @@ void parse_log_message(char *buf, size_t buflen, int *level, int *facility,
return;
fac_level = 10*fac_level + (*p - '0');
p++;
if ((p - buf) > buflen)
if ((p - buf) >= buflen)
return;
}
@ -6743,6 +6743,7 @@ int cfg_parse_log_profile(const char *file, int linenum, char **args, int kwm)
SMP_VAL_FE_LOG_END, &errmsg)) {
ha_alert("Parsing [%s:%d]: failed to parse logformat: %s.\n",
file, linenum, errmsg);
lf_expr_deinit(target_lf);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}

View file

@ -981,7 +981,7 @@ static inline void fcgi_strm_propagate_term_flags(struct fcgi_conn *fconn, struc
*/
static void fcgi_strm_destroy(struct fcgi_strm *fstrm)
{
struct connection *conn = fstrm->fconn->conn;
struct connection __maybe_unused *conn = fstrm->fconn->conn;
TRACE_ENTER(FCGI_EV_FSTRM_END, conn, fstrm);
@ -3540,7 +3540,7 @@ static size_t fcgi_strm_parse_trailers(struct fcgi_strm *fstrm, struct h1m *h1m,
static size_t fcgi_strm_parse_response(struct fcgi_strm *fstrm, struct buffer *buf, size_t count)
{
struct fcgi_conn *fconn = fstrm->fconn;
struct fcgi_conn __maybe_unused *fconn = fstrm->fconn;
struct htx *htx;
struct h1m *h1m = &fstrm->h1m;
size_t ret, data, total = 0;
@ -4031,7 +4031,7 @@ static int fcgi_subscribe(struct stconn *sc, int event_type, struct wait_event *
static int fcgi_unsubscribe(struct stconn *sc, int event_type, struct wait_event *es)
{
struct fcgi_strm *fstrm = __sc_mux_strm(sc);
struct fcgi_conn *fconn = fstrm->fconn;
struct fcgi_conn __maybe_unused *fconn = fstrm->fconn;
BUG_ON(event_type & ~(SUB_RETRY_SEND|SUB_RETRY_RECV));
BUG_ON(fstrm->subs && fstrm->subs != es);
@ -4551,7 +4551,7 @@ static const struct mux_ops mux_fcgi_ops = {
/* this mux registers FCGI proto */
static struct mux_proto_list mux_proto_fcgi =
{ .token = IST("fcgi"), .mode = PROTO_MODE_HTTP, .side = PROTO_SIDE_BE, .mux = &mux_fcgi_ops };
{ .mux_proto = IST("fcgi"), .mode = PROTO_MODE_HTTP, .side = PROTO_SIDE_BE, .mux = &mux_fcgi_ops };
INITCALL1(STG_REGISTER, register_mux_proto, &mux_proto_fcgi);

View file

@ -1897,7 +1897,7 @@ static void h1_append_chunk_crlf(struct buffer *buf)
*/
static void h1_set_tunnel_mode(struct h1s *h1s)
{
struct h1c *h1c = h1s->h1c;
struct h1c __maybe_unused *h1c = h1s->h1c;
h1s->req.state = H1_MSG_TUNNEL;
h1s->req.flags &= ~(H1_MF_XFER_LEN|H1_MF_CLEN|H1_MF_CHNK);
@ -2713,7 +2713,9 @@ static size_t h1_make_headers(struct h1s *h1s, struct h1m *h1m, struct htx *htx,
goto nextblk;
}
else if (isteq(n, ist("upgrade"))) {
h1_parse_upgrade_header(h1m, v);
h1_parse_upgrade_header(h1m, &v);
if (!v.len)
goto nextblk;
}
else if ((isteq(n, ist("sec-websocket-accept")) && h1m->flags & H1_MF_RESP) ||
(isteq(n, ist("sec-websocket-key")) && !(h1m->flags & H1_MF_RESP))) {
@ -6123,9 +6125,9 @@ static const struct mux_ops mux_h1_ops = {
/* this mux registers default HTX proto but also h1 proto (to be referenced in the conf */
static struct mux_proto_list mux_proto_h1 =
{ .token = IST("h1"), .mode = PROTO_MODE_HTTP, .side = PROTO_SIDE_BOTH, .mux = &mux_h1_ops };
{ .mux_proto = IST("h1"), .mode = PROTO_MODE_HTTP, .side = PROTO_SIDE_BOTH, .mux = &mux_h1_ops };
static struct mux_proto_list mux_proto_http =
{ .token = IST(""), .mode = PROTO_MODE_HTTP, .side = PROTO_SIDE_BOTH, .mux = &mux_http_ops, .alpn = "\010http/1.1" };
{ .mux_proto = IST(""), .mode = PROTO_MODE_HTTP, .side = PROTO_SIDE_BOTH, .mux = &mux_http_ops, .alpn = "\010http/1.1" };
INITCALL1(STG_REGISTER, register_mux_proto, &mux_proto_h1);
INITCALL1(STG_REGISTER, register_mux_proto, &mux_proto_http);

View file

@ -1281,7 +1281,7 @@ void _h2_trace_header(const struct ist hn, const struct ist hv,
const struct h2c *h2c, const struct h2s *h2s)
{
struct ist n_ist, v_ist;
const char *c_str, *s_str;
const char __maybe_unused *c_str, *s_str;
chunk_reset(&trash);
c_str = chunk_newstr(&trash);
@ -1456,7 +1456,7 @@ static int h2_init(struct connection *conn, struct proxy *prx, struct session *s
if (max_strm && h2c->streams_hard_limit > max_strm)
h2c->streams_limit = h2c->streams_hard_limit = max_strm;
if (h2c->streams_hard_limit > 1)
if (global.tune.streams_elasticity && h2c->streams_hard_limit > 1)
_HA_ATOMIC_ADD(&tg_ctx->committed_extra_streams, h2c->streams_hard_limit - 1);
}
@ -1527,7 +1527,10 @@ static int h2_init(struct connection *conn, struct proxy *prx, struct session *s
TRACE_LEAVE(H2_EV_H2C_NEW, conn);
return 0;
fail_stream:
if (!(h2c->flags & H2_CF_IS_BACK) && h2c->streams_hard_limit > 1)
/* Unnecessary code for now as fail_stream only occurs with BE conns.
* Still better though to keep it to prevent future mistakes.
*/
if (!(h2c->flags & H2_CF_IS_BACK) && global.tune.streams_elasticity && h2c->streams_hard_limit > 1)
_HA_ATOMIC_SUB(&tg_ctx->committed_extra_streams, h2c->streams_hard_limit - 1);
hpack_dht_free(h2c->ddht);
fail:
@ -1607,7 +1610,7 @@ static void h2_release(struct h2c *h2c)
if (!conn || !conn_is_reverse(conn))
HA_ATOMIC_DEC(&h2c->px_counters->open_conns);
if (!(h2c->flags & H2_CF_IS_BACK) && h2c->streams_hard_limit > 1)
if (!(h2c->flags & H2_CF_IS_BACK) && global.tune.streams_elasticity && h2c->streams_hard_limit > 1)
_HA_ATOMIC_SUB(&tg_ctx->committed_extra_streams, h2c->streams_hard_limit - 1);
pool_free(pool_head_h2_rx_bufs, h2c->shared_rx_bufs);
@ -2021,7 +2024,7 @@ static inline void h2s_propagate_term_flags(struct h2c *h2c, struct h2s *h2s)
*/
static void h2s_destroy(struct h2s *h2s)
{
struct connection *conn = h2s->h2c->conn;
struct connection __maybe_unused *conn = h2s->h2c->conn;
int freed = 0;
TRACE_ENTER(H2_EV_H2S_END, conn, h2s);
@ -4197,7 +4200,7 @@ static int h2_conn_reverse(struct h2c *h2c)
/* the connection was accounted as frontend streams before
* reversal, we must undo that accounting now.
*/
if (h2c->streams_hard_limit > 1)
if (h2c->streams_hard_limit > 1 && global.tune.streams_elasticity)
_HA_ATOMIC_SUB(&tg_ctx->committed_extra_streams, h2c->streams_hard_limit - 1);
h2c->flags |= H2_CF_IS_BACK;
@ -4219,7 +4222,7 @@ static int h2_conn_reverse(struct h2c *h2c)
struct proxy *prx = l->bind_conf->frontend;
/* backend connections becoming frontend need accounting. */
if (h2c->streams_hard_limit > 1)
if (h2c->streams_hard_limit > 1 && global.tune.streams_elasticity)
_HA_ATOMIC_ADD(&tg_ctx->committed_extra_streams, h2c->streams_hard_limit - 1);
h2c->flags &= ~H2_CF_IS_BACK;
@ -6233,6 +6236,13 @@ next_frame:
/* Skip StreamDep and weight for now (we don't support PRIORITY) */
if (h2c->dff & H2_F_HEADERS_PRIORITY) {
if (flen < 5) {
h2c_report_glitch(h2c, 1, "too short PRIORITY frame");
TRACE_STATE("too short PRIORITY frame", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR);
goto fail;
}
if (read_n32(hdrs) == h2c->dsi) {
/* RFC7540#5.3.1 : stream dep may not depend on itself */
h2c_report_glitch(h2c, 1, "PRIORITY frame referencing itself");
@ -6242,13 +6252,6 @@ next_frame:
goto fail;
}
if (flen < 5) {
h2c_report_glitch(h2c, 1, "too short PRIORITY frame");
TRACE_STATE("too short PRIORITY frame", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR);
goto fail;
}
hdrs += 5; // stream dep = 4, weight = 1
flen -= 5;
}
@ -7610,7 +7613,7 @@ static size_t h2s_make_data(struct h2s *h2s, struct buffer *buf, size_t count)
*/
static size_t h2s_skip_data(struct h2s *h2s, struct buffer *buf, size_t count)
{
struct h2c *h2c = h2s->h2c;
struct h2c __maybe_unused *h2c = h2s->h2c;
struct htx *htx;
int bsize; /* htx block size */
int fsize; /* h2 frame size */
@ -9010,7 +9013,7 @@ static const struct mux_ops h2_ops = {
};
static struct mux_proto_list mux_proto_h2 =
{ .token = IST("h2"), .mode = PROTO_MODE_HTTP, .side = PROTO_SIDE_BOTH, .mux = &h2_ops, .alpn = "\002h2" };
{ .mux_proto = IST("h2"), .mode = PROTO_MODE_HTTP, .side = PROTO_SIDE_BOTH, .mux = &h2_ops, .alpn = "\002h2" };
INITCALL1(STG_REGISTER, register_mux_proto, &mux_proto_h2);

View file

@ -936,9 +936,9 @@ const struct mux_ops mux_pt_ops = {
/* PROT selection : default mux has empty name */
static struct mux_proto_list mux_proto_none =
{ .token = IST("none"), .mode = PROTO_MODE_TCP, .side = PROTO_SIDE_BOTH, .mux = &mux_pt_ops };
{ .mux_proto = IST("none"), .mode = PROTO_MODE_TCP, .side = PROTO_SIDE_BOTH, .mux = &mux_pt_ops };
static struct mux_proto_list mux_proto_tcp =
{ .token = IST(""), .mode = PROTO_MODE_TCP, .side = PROTO_SIDE_BOTH, .mux = &mux_tcp_ops };
{ .mux_proto = IST(""), .mode = PROTO_MODE_TCP, .side = PROTO_SIDE_BOTH, .mux = &mux_tcp_ops };
INITCALL1(STG_REGISTER, register_mux_proto, &mux_proto_none);
INITCALL1(STG_REGISTER, register_mux_proto, &mux_proto_tcp);

View file

@ -2467,6 +2467,7 @@ int qcc_recv_stop_sending(struct qcc *qcc, uint64_t id, uint64_t err)
static int qcc_release_remote_stream(struct qcc *qcc, uint64_t id)
{
struct quic_frame *frm;
uint64_t conn_max, rem, non_extra, inc;
TRACE_ENTER(QMUX_EV_QCS_END, qcc->conn);
@ -2483,8 +2484,42 @@ static int qcc_release_remote_stream(struct qcc *qcc, uint64_t id)
/* MAX_STREAMS needed if closed streams value more than twice
* the initial window or reaching the stream ID limit.
*/
if (qcc->lfctl.cl_bidi_r > qcc->lfctl.ms_bidi_init / 2 ||
if (qcc->lfctl.cl_bidi_r > qcc->lfctl.ms_bidi_rel / 2 ||
qcc->lfctl.cl_bidi_r + qcc->lfctl.ms_bidi == max) {
BUG_ON(qcc->lfctl.ms_bidi_rel < qcc->lfctl.cl_bidi_r);
rem = qcc->lfctl.ms_bidi_rel - qcc->lfctl.cl_bidi_r;
/* if every streams are closed, decrement extra stream accounting by 1 */
non_extra = !rem ? 1 : 0;
if (!(qcc->flags & QC_CF_IS_BACK) && global.tune.streams_elasticity) {
/* If stream elasticity is active, first decrement closed from extra streams. */
if (qcc->lfctl.ms_bidi_rel > 1) {
_HA_ATOMIC_SUB(&tg_ctx->committed_extra_streams,
qcc->lfctl.cl_bidi_r - non_extra);
}
/* Now calculate the available streams. */
conn_max = conn_calc_max_streams(qcc->lfctl.ms_bidi_init);
if (conn_max <= rem) {
/* More streams already consumed than currently allowed,
* keep the current flow control limit.
*/
qcc->lfctl.ms_bidi_rel = rem;
qcc->lfctl.cl_bidi_r = 0;
goto out;
}
/* Update flow control limit up to the allowed elasticity limit. */
inc = conn_max - rem;
_HA_ATOMIC_ADD(&tg_ctx->committed_extra_streams, inc - non_extra);
qcc->lfctl.ms_bidi_rel = rem + inc;
}
else {
/* Stream elasticity not active, flow control increase remains static. */
inc = qcc->lfctl.cl_bidi_r;
}
TRACE_DATA("increase max stream limit with MAX_STREAMS_BIDI", QMUX_EV_QCC_SEND, qcc->conn);
frm = qc_frm_alloc(QUIC_FT_MAX_STREAMS_BIDI);
if (!frm) {
@ -2493,12 +2528,11 @@ static int qcc_release_remote_stream(struct qcc *qcc, uint64_t id)
goto err;
}
frm->max_streams_bidi.max_streams = qcc->lfctl.ms_bidi +
qcc->lfctl.cl_bidi_r;
frm->max_streams_bidi.max_streams = qcc->lfctl.ms_bidi + inc;
LIST_APPEND(&qcc->lfctl.frms, &frm->list);
tasklet_wakeup(qcc->wait_event.tasklet);
qcc->lfctl.ms_bidi += qcc->lfctl.cl_bidi_r;
qcc->lfctl.ms_bidi += inc;
qcc->lfctl.cl_bidi_r = 0;
}
}
@ -3307,6 +3341,10 @@ static int qcm_avail_streams(struct connection *conn)
ret = qcc_fctl_avail_streams(qcc, 1);
/* Enforce stream_max_concurrent limit even if peer allows more streams. */
if (ret > quic_tune.be.stream_max_concurrent - qcc->nb_hreq)
ret = quic_tune.be.stream_max_concurrent - qcc->nb_hreq;
/* Now cap return value if reaching max-reuse server or maximum stream
* ID. qcc_be_is_reusable() already detected if one of these has been
* exceeded.
@ -3586,6 +3624,12 @@ static void qcc_release(struct qcc *qcc)
}
TRACE_PROTO("application layer released", QMUX_EV_QCC_END, conn);
if (!(qcc->flags & QC_CF_IS_BACK) && global.tune.streams_elasticity &&
qcc->lfctl.ms_bidi_rel > 1) {
_HA_ATOMIC_SUB(&tg_ctx->committed_extra_streams,
qcc->lfctl.ms_bidi_rel - 1);
}
if (conn && !conn_is_quic(conn)) {
b_free(&qcc->rx.qmux_buf);
b_free(&qcc->tx.qmux_buf);
@ -3849,7 +3893,7 @@ static int qcm_init(struct connection *conn, struct proxy *prx,
/* Server parameters, params used for RX flow control. */
lparams = &conn->handle.qc->rx.params;
qcc->lfctl.ms_bidi = qcc->lfctl.ms_bidi_init = lparams->initial_max_streams_bidi;
qcc->lfctl.ms_bidi = qcc->lfctl.ms_bidi_init = qcc->lfctl.ms_bidi_rel = lparams->initial_max_streams_bidi;
qcc->lfctl.ms_uni = lparams->initial_max_streams_uni;
qcc->lfctl.msd_bidi_l = lparams->initial_max_stream_data_bidi_local;
qcc->lfctl.msd_bidi_r = lparams->initial_max_stream_data_bidi_remote;
@ -3878,7 +3922,7 @@ static int qcm_init(struct connection *conn, struct proxy *prx,
qcc->rfctl.msd_uni_l = rparams->initial_max_stream_data_uni;
lparams = xprt_qmux_lparams(conn->xprt_ctx);
qcc->lfctl.ms_bidi = qcc->lfctl.ms_bidi_init = lparams->initial_max_streams_bidi;
qcc->lfctl.ms_bidi = qcc->lfctl.ms_bidi_init = qcc->lfctl.ms_bidi_rel= lparams->initial_max_streams_bidi;
qcc->lfctl.ms_uni = lparams->initial_max_streams_uni;
qcc->lfctl.msd_bidi_l = lparams->initial_max_stream_data_bidi_local;
qcc->lfctl.msd_bidi_r = lparams->initial_max_stream_data_bidi_remote;
@ -3928,6 +3972,11 @@ static int qcm_init(struct connection *conn, struct proxy *prx,
qcc->next_bidi_l = 0x01;
qcc->largest_uni_r = 0x02;
qcc->next_uni_l = 0x03;
if (global.tune.streams_elasticity && qcc->lfctl.ms_bidi_init > 1) {
_HA_ATOMIC_ADD(&tg_ctx->committed_extra_streams,
qcc->lfctl.ms_bidi_init - 1);
}
}
qcc->wait_event.tasklet = tasklet_new();
@ -4154,6 +4203,10 @@ static void qcm_strm_detach(struct sedesc *sd)
qcs->flags |= QC_SF_DETACH;
qcc_refresh_timeout(qcc);
/* TODO on backend side if a QCS is detached, the connection may
* not be reinserted in the correct server pool (idle or avail).
*/
TRACE_LEAVE(QMUX_EV_STRM_END, qcc->conn, qcs);
return;
}
@ -4637,7 +4690,7 @@ static int qcm_ctl(struct connection *conn, enum mux_ctl_type mux_ctl, void *out
return qcc->nb_hreq;
case MUX_CTL_GET_MAXSTRM:
return qcc->lfctl.ms_bidi_init;
return qcc->lfctl.ms_bidi_rel;
case MUX_CTL_TEVTS:
return qcc->term_evts_log;
@ -4784,7 +4837,7 @@ void qcc_show_quic(struct qcc *qcc)
}
static struct mux_proto_list mux_proto_quic =
{ .token = IST("quic"), .mode = PROTO_MODE_HTTP, .side = PROTO_SIDE_BOTH, .mux = &quic_ops };
{ .mux_proto = IST("quic"), .mode = PROTO_MODE_HTTP, .side = PROTO_SIDE_BOTH, .mux = &quic_ops };
INITCALL1(STG_REGISTER, register_mux_proto, &mux_proto_quic);
@ -4813,6 +4866,7 @@ static const struct mux_ops qmux_ops = {
};
static struct mux_proto_list mux_proto_qmux =
{ .token = IST("qmux"), .mode = PROTO_MODE_HTTP, .side = PROTO_SIDE_BOTH, .mux = &qmux_ops };
{ .mux_proto = IST("qmux"), .mode = PROTO_MODE_HTTP, .side = PROTO_SIDE_BOTH, .mux = &qmux_ops,
.alpn = "\002h3", .init_xprt = XPRT_QMUX };
INITCALL1(STG_REGISTER, register_mux_proto, &mux_proto_qmux);

View file

@ -1154,7 +1154,7 @@ static inline void spop_strm_propagate_term_flags(struct spop_conn *spop_conn, s
*/
static void spop_strm_destroy(struct spop_strm *spop_strm)
{
struct connection *conn = spop_strm->spop_conn->conn;
struct connection __maybe_unused *conn = spop_strm->spop_conn->conn;
TRACE_ENTER(SPOP_EV_SPOP_STRM_END, conn, spop_strm);
@ -3199,7 +3199,7 @@ static int spop_subscribe(struct stconn *sc, int event_type, struct wait_event *
static int spop_unsubscribe(struct stconn *sc, int event_type, struct wait_event *es)
{
struct spop_strm *spop_strm = __sc_mux_strm(sc);
struct spop_conn *spop_conn = spop_strm->spop_conn;
struct spop_conn __maybe_unused *spop_conn = spop_strm->spop_conn;
BUG_ON(event_type & ~(SUB_RETRY_SEND|SUB_RETRY_RECV));
BUG_ON(spop_strm->subs && spop_strm->subs != es);
@ -3738,10 +3738,10 @@ static const struct mux_ops mux_spop_ops = {
};
static struct mux_proto_list mux_proto_spop =
{ .token = IST("spop"), .mode = PROTO_MODE_SPOP, .side = PROTO_SIDE_BE, .mux = &mux_spop_ops };
{ .mux_proto = IST("spop"), .mode = PROTO_MODE_SPOP, .side = PROTO_SIDE_BE, .mux = &mux_spop_ops };
static struct mux_proto_list mux_proto_default_spop =
{ .token = IST(""), .mode = PROTO_MODE_SPOP, .side = PROTO_SIDE_BE, .mux = &mux_spop_ops };
{ .mux_proto = IST(""), .mode = PROTO_MODE_SPOP, .side = PROTO_SIDE_BE, .mux = &mux_spop_ops };
INITCALL1(STG_REGISTER, register_mux_proto, &mux_proto_spop);
INITCALL1(STG_REGISTER, register_mux_proto, &mux_proto_default_spop);

View file

@ -447,8 +447,8 @@ static size_t tcp_fullhdr_find_opt(const struct sample *smp, uint8_t opt)
/* kind1 = NOP and is a single byte, others have a length field */
if (smp->data.u.str.area[next] == 1)
next++;
else if (next + 1 < len)
next += smp->data.u.str.area[next + 1];
else if (next + 1 < len && smp->data.u.str.area[next + 1] > 1)
next += (uchar)smp->data.u.str.area[next + 1];
else
break;
if (smp->data.u.str.area[curr] == opt && next <= len)
@ -605,7 +605,7 @@ static int sample_conv_tcp_options_list(const struct arg *arg_p, struct sample *
/* kind1 = NOP and is a single byte, others have a length field */
if (smp->data.u.str.area[ofs] == 1)
ofs++;
else if (ofs + 1 < len && smp->data.u.str.area[ofs + 1])
else if (ofs + 1 < len && smp->data.u.str.area[ofs + 1] > 1)
ofs += (uchar)smp->data.u.str.area[ofs + 1];
else
break;
@ -780,7 +780,7 @@ static int sample_conv_ip_fp(const struct arg *arg_p, struct sample *smp, void *
/* kind1 = NOP and is a single byte, others have a length field */
if (smp->data.u.str.area[ofs] == 1)
next = ofs + 1;
else if ((ofs + 1 < tcplen) && smp->data.u.str.area[ofs + 1]) /* optlen 0 will cause an infinite loop */
else if ((ofs + 1 < tcplen) && smp->data.u.str.area[ofs + 1] > 1)
next = ofs + (uchar)smp->data.u.str.area[ofs + 1];
else
break;

View file

@ -371,7 +371,7 @@ int rhttp_bind_listener(struct listener *listener, char *errmsg, int errlen)
}
/* Check that server uses HTTP/2 either with proto or ALPN. */
if ((!srv->mux_proto || !isteqi(srv->mux_proto->token, ist("h2"))) &&
if ((!srv->mux_proto || !isteqi(srv->mux_proto->mux_proto, ist("h2"))) &&
(!srv->use_ssl || !isteqi(ist(srv->ssl_ctx.alpn_str), ist("\x02h2")))) {
snprintf(errmsg, errlen, "Cannot reverse connect with server '%s/%s' unless HTTP/2 is activated on it with either proto or alpn keyword.", name, ist0(sv_name));
goto err;

View file

@ -69,7 +69,7 @@
#include <haproxy/uri_auth.h>
/* Lock to ensure multiple backends deletion concurrently is safe */
static __decl_spinlock(proxies_del_lock);
__decl_spinlock(proxies_del_lock);
int listeners; /* # of proxy listeners, set by cfgparse */
struct proxy *proxies_list = NULL; /* list of main proxies */
@ -903,7 +903,7 @@ static int proxy_parse_declare(char **args, int section, struct proxy *curpx,
len = strtol(args[4], &error, 10);
if (*error != '\0') {
memprintf(err, "'%s %s': cannot parse the length '%s'.",
args[0], args[1], args[3]);
args[0], args[1], args[4]);
return -1;
}
@ -1006,7 +1006,7 @@ proxy_parse_retry_on(char **args, int section, struct proxy *curpx,
PR_RE_JUNK_REQUEST;
else if (strcmp(args[i], "none") == 0) {
if (i != 1 || *args[i + 1]) {
memprintf(err, "'%s' 'none' keyworld only usable alone", args[0]);
memprintf(err, "'%s' 'none' keyword only usable alone", args[0]);
return -1;
}
} else {
@ -1689,13 +1689,13 @@ int proxy_finalize(struct proxy *px, int *err_code)
* due to the proxy's mode not being taken into account
* on first pass. Let's adjust it now.
*/
mux_ent = conn_get_best_mux_entry(bind_conf->mux_proto->token, PROTO_SIDE_FE, is_quic, mode);
mux_ent = conn_get_best_mux_entry(bind_conf->mux_proto->mux_proto, IST_NULL, PROTO_SIDE_FE, is_quic, mode);
if (!mux_ent || !isteq(mux_ent->token, bind_conf->mux_proto->token)) {
if (!mux_ent || !isteq(mux_ent->mux_proto, bind_conf->mux_proto->mux_proto)) {
ha_alert("%s '%s' : MUX protocol '%.*s' is not usable for 'bind %s' at [%s:%d].\n",
proxy_type_str(px), px->id,
(int)bind_conf->mux_proto->token.len,
bind_conf->mux_proto->token.ptr,
(int)bind_conf->mux_proto->mux_proto.len,
bind_conf->mux_proto->mux_proto.ptr,
bind_conf->arg, bind_conf->file, bind_conf->line);
cfgerr++;
}
@ -1703,16 +1703,16 @@ int proxy_finalize(struct proxy *px, int *err_code)
if ((mux_ent->mux->flags & MX_FL_FRAMED) && !(bind_conf->options & BC_O_USE_SOCK_DGRAM)) {
ha_alert("%s '%s' : frame-based MUX protocol '%.*s' is incompatible with stream transport of 'bind %s' at [%s:%d].\n",
proxy_type_str(px), px->id,
(int)bind_conf->mux_proto->token.len,
bind_conf->mux_proto->token.ptr,
(int)bind_conf->mux_proto->mux_proto.len,
bind_conf->mux_proto->mux_proto.ptr,
bind_conf->arg, bind_conf->file, bind_conf->line);
cfgerr++;
}
else if (!(mux_ent->mux->flags & MX_FL_FRAMED) && !(bind_conf->options & BC_O_USE_SOCK_STREAM)) {
ha_alert("%s '%s' : stream-based MUX protocol '%.*s' is incompatible with framed transport of 'bind %s' at [%s:%d].\n",
proxy_type_str(px), px->id,
(int)bind_conf->mux_proto->token.len,
bind_conf->mux_proto->token.ptr,
(int)bind_conf->mux_proto->mux_proto.len,
bind_conf->mux_proto->mux_proto.ptr,
bind_conf->arg, bind_conf->file, bind_conf->line);
cfgerr++;
}
@ -1778,6 +1778,22 @@ int proxy_finalize(struct proxy *px, int *err_code)
}
#endif /* TLSEXT_TYPE_application_layer_protocol_negotiation */
} /* HTTP && bufsize < 16384 */
#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
if (px->mode == PR_MODE_HTTP && !bind_conf->mux_proto &&
bind_conf->ssl_conf.alpn_str &&
strstr(bind_conf->ssl_conf.alpn_str, "\002h3")) {
if (!experimental_directives_allowed) {
ha_alert("HTTP/3 on TCP listed via ALPN on frontend '%s' at [%s:%d] relies on the experimental QMux protocol, "
"must be allowed via a global 'expose-experimental-directives'.\n",
px->id, bind_conf->file, bind_conf->line);
cfgerr++;
}
mark_tainted(TAINTED_CONFIG_EXP_KW_DECLARED);
}
#endif /* TLSEXT_TYPE_application_layer_protocol_negotiation */
#endif /* USE_OPENSSL */
#ifdef USE_QUIC
@ -1792,6 +1808,13 @@ int proxy_finalize(struct proxy *px, int *err_code)
proxy_type_str(px), px->id);
*err_code |= ERR_WARN;
}
if (bind_conf->ssl_conf.early_data && conn_calc_max_streams(1)) {
ha_notice("Binding [%s:%d] for %s %s: "
"stream elasticity is ignored for initial connection settings as this is incompatible with 0-RTT.",
bind_conf->file, bind_conf->line,
proxy_type_str(px), px->id);
}
}
#endif /* USE_QUIC */
@ -2849,13 +2872,13 @@ int proxy_finalize(struct proxy *px, int *err_code)
* due to the proxy's mode not being taken into account
* on first pass. Let's adjust it now.
*/
mux_ent = conn_get_best_mux_entry(newsrv->mux_proto->token, PROTO_SIDE_BE, srv_is_quic(newsrv), mode);
mux_ent = conn_get_best_mux_entry(newsrv->mux_proto->mux_proto, IST_NULL, PROTO_SIDE_BE, srv_is_quic(newsrv), mode);
if (!mux_ent || !isteq(mux_ent->token, newsrv->mux_proto->token)) {
if (!mux_ent || !isteq(mux_ent->mux_proto, newsrv->mux_proto->mux_proto)) {
ha_alert("%s '%s' : MUX protocol '%.*s' is not usable for server '%s' at [%s:%d].\n",
proxy_type_str(px), px->id,
(int)newsrv->mux_proto->token.len,
newsrv->mux_proto->token.ptr,
(int)newsrv->mux_proto->mux_proto.len,
newsrv->mux_proto->mux_proto.ptr,
newsrv->id, newsrv->conf.file, newsrv->conf.line);
cfgerr++;
}
@ -2863,16 +2886,16 @@ int proxy_finalize(struct proxy *px, int *err_code)
if ((mux_ent->mux->flags & MX_FL_FRAMED) && !srv_is_quic(newsrv)) {
ha_alert("%s '%s' : MUX protocol '%.*s' is incompatible with stream transport used by server '%s' at [%s:%d].\n",
proxy_type_str(px), px->id,
(int)newsrv->mux_proto->token.len,
newsrv->mux_proto->token.ptr,
(int)newsrv->mux_proto->mux_proto.len,
newsrv->mux_proto->mux_proto.ptr,
newsrv->id, newsrv->conf.file, newsrv->conf.line);
cfgerr++;
}
else if (!(mux_ent->mux->flags & MX_FL_FRAMED) && srv_is_quic(newsrv)) {
ha_alert("%s '%s' : MUX protocol '%.*s' is incompatible with framed transport used by server '%s' at [%s:%d].\n",
proxy_type_str(px), px->id,
(int)newsrv->mux_proto->token.len,
newsrv->mux_proto->token.ptr,
(int)newsrv->mux_proto->mux_proto.len,
newsrv->mux_proto->mux_proto.ptr,
newsrv->id, newsrv->conf.file, newsrv->conf.line);
cfgerr++;
}
@ -3562,7 +3585,7 @@ struct proxy *parse_new_proxy(const char *name, unsigned int cap,
ha_alert("parsing [%s:%d] : %s\n", file, linenum, errmsg);
free(errmsg);
ha_free(&curproxy);
proxy_drop(curproxy);
return NULL;
}
}
@ -5443,8 +5466,8 @@ static int cli_io_handler_show_errors(struct appctx *appctx)
/* register cli keywords */
static struct cli_kw_list cli_kws = {{ },{
{ { "add", "backend", NULL }, "add backend <backend> : add a new backend", cli_parse_add_backend, NULL, NULL, NULL, ACCESS_EXPERIMENTAL },
{ { "del", "backend", NULL }, "del backend <backend> : delete a backend", cli_parse_delete_backend, NULL, NULL, NULL, ACCESS_EXPERIMENTAL },
{ { "add", "backend", NULL }, "add backend <backend> : add a new backend", cli_parse_add_backend, NULL, NULL, NULL, 0 },
{ { "del", "backend", NULL }, "del backend <backend> : delete a backend", cli_parse_delete_backend, NULL, NULL, NULL, 0 },
{ { "disable", "frontend", NULL }, "disable frontend <frontend> : temporarily disable specific frontend", cli_parse_disable_frontend, NULL, NULL },
{ { "enable", "frontend", NULL }, "enable frontend <frontend> : re-enable specific frontend", cli_parse_enable_frontend, NULL, NULL },
{ { "publish", "backend", NULL }, "publish backend <backend> : mark backend as ready for traffic", cli_parse_publish_backend, NULL, NULL },

View file

@ -70,6 +70,10 @@ static int qmux_parse_frm(struct qcc *qcc, struct buffer *buf)
struct qf_reset_stream *rst_frm = &frm.reset_stream;
qcc_recv_reset_stream(qcc, rst_frm->id, rst_frm->app_error_code, rst_frm->final_size);
}
else if (frm.type == QUIC_FT_STOP_SENDING) {
struct qf_stop_sending *ss_frm = &frm.stop_sending;
qcc_recv_stop_sending(qcc, ss_frm->id, ss_frm->app_error_code);
}
else if (frm.type == QUIC_FT_MAX_DATA) {
struct qf_max_data *md_frm = &frm.max_data;
qcc_recv_max_data(qcc, md_frm->max_data);
@ -82,13 +86,26 @@ static int qmux_parse_frm(struct qcc *qcc, struct buffer *buf)
struct qf_max_streams *ms_frm = &frm.max_streams_bidi;
qcc_recv_max_streams(qcc, ms_frm->max_streams, 1);
}
else if (frm.type == QUIC_FT_MAX_STREAMS_UNI) {
struct qf_max_streams *ms_frm = &frm.max_streams_uni;
qcc_recv_max_streams(qcc, ms_frm->max_streams, 0);
}
else if (frm.type == QUIC_FT_DATA_BLOCKED ||
frm.type == QUIC_FT_STREAM_DATA_BLOCKED ||
frm.type == QUIC_FT_STREAMS_BLOCKED_BIDI ||
frm.type == QUIC_FT_STREAMS_BLOCKED_UNI) {
/* TODO */
CHECK_IF("received flow control blocked frame not yet handled in QMux");
}
else if (frm.type == QUIC_FT_PADDING) {
CHECK_IF("received padding frame not yet handled in QMux");
}
else if (frm.type == QUIC_FT_CONNECTION_CLOSE ||
frm.type == QUIC_FT_CONNECTION_CLOSE_APP) {
CHECK_IF("received connection_close frame not yet handled in QMux");
}
else {
/* qmux_is_frm_valid() must prevent this */
ABORT_NOW();
}

View file

@ -378,7 +378,7 @@ int quic_get_cid_tid(const unsigned char *cid, size_t cid_len,
tree = &quic_fe_cid_trees[quic_cid_tree_idx(&derive_cid)];
HA_RWLOCK_RDLOCK(QC_CID_LOCK, &tree->lock);
node = ebmb_lookup(&tree->root, cid, cid_len);
node = ebmb_lookup(&tree->root, derive_cid.data, derive_cid.len);
if (node) {
conn_id = ebmb_entry(node, struct quic_connection_id, node);
cid_tid = HA_ATOMIC_LOAD(&conn_id->tid);

View file

@ -710,7 +710,7 @@ static inline int ha_quic_set_write_secret(SSL *ssl, enum ssl_encryption_level_t
static int ha_quic_flush_flight(SSL *ssl)
{
struct quic_conn *qc = SSL_get_ex_data(ssl, ssl_qc_app_data_index);
struct quic_conn __maybe_unused *qc = SSL_get_ex_data(ssl, ssl_qc_app_data_index);
TRACE_ENTER(QUIC_EV_CONN_FFLIGHT, qc);
TRACE_LEAVE(QUIC_EV_CONN_FFLIGHT, qc);

View file

@ -1060,7 +1060,7 @@ int quic_tls_key_update(struct quic_conn *qc)
struct quic_tls_secrets *rx = &tls_ctx->rx;
struct quic_tls_secrets *tx = &tls_ctx->tx;
/* Used only for the traces */
struct quic_kp_trace kp_trace = {
struct quic_kp_trace __maybe_unused kp_trace = {
.rx_sec = rx->secret,
.rx_seclen = rx->secretlen,
.tx_sec = tx->secret,

View file

@ -1,6 +1,7 @@
#include <arpa/inet.h>
#include <string.h>
#include <haproxy/connection.h>
#include <haproxy/global.h>
#include <haproxy/ncbuf-t.h>
#include <haproxy/net_helper.h>
@ -70,7 +71,7 @@ void quic_transport_params_init(struct quic_transport_params *p, int server)
quic_tune.be.max_idle_timeout;
/* Set limit on number of concurrently opened streams. */
p->initial_max_streams_bidi = max_streams_bidi;
p->initial_max_streams_bidi = server ? max_streams_bidi : 0;
p->initial_max_streams_uni = max_streams_uni;
/* Set connection flow-control data limit, either from configuration,
@ -862,6 +863,15 @@ int qc_lstnr_params_init(struct quic_conn *qc,
rx_params->initial_source_connection_id.len = scidlen;
TRACE_PROTO("\nRX(local) transp. params.", QUIC_EV_TRANSP_PARAMS, qc, rx_params);
/* Reduce max-streams-bidi if stream elasticity is active. This is
* ignored however if 0-RTT is configured as clients could reuse
* different transport parameters than the one advertised.
*/
if (global.tune.streams_elasticity && !qc->li->bind_conf->ssl_conf.early_data) {
rx_params->initial_max_streams_bidi =
conn_calc_max_streams(rx_params->initial_max_streams_bidi);
}
return 1;
}

View file

@ -444,7 +444,7 @@ INITCALL0(STG_REGISTER, regex_register_build_options);
#ifdef USE_PCRE2
static int init_pcre2_per_thread(void)
{
local_pcre2_match = pcre2_match_data_create(MAX_MATCH - 1, NULL);
local_pcre2_match = pcre2_match_data_create(MAX_MATCH, NULL);
if (!local_pcre2_match) {
ha_alert("Failed to allocate PCRE2 match data context for thread %u.\n", tid);
return 0;

View file

@ -226,7 +226,7 @@ struct show_resolvers_ctx {
};
/* returns the currently accepted address families as a combination of
* RSLV_ACCEPT_IPV4 and RSLV_ACCEPT_IPV6 only. It will dynamically adapt adapt
* RSLV_ACCEPT_IPV4 and RSLV_ACCEPT_IPV6 only. It will dynamically adapt
* the IPv6 status to sock_inet6_seems_reachable if RSLV_AUTO_FAMILY is set,
* otherwise returns the relevant bits of resolv_accept_families.
*/
@ -509,7 +509,7 @@ resolv_run_resolution(struct resolv_resolution *resolution)
return 0;
/* Check if a resolution has already been started for this server return
* directly to avoid resolution pill up. */
* directly to avoid resolution pile up. */
if (resolution->step != RSLV_STEP_NONE)
return 0;
@ -624,17 +624,21 @@ int resolv_read_name(unsigned char *buffer, unsigned char *bufend,
/* Name compression is in use */
if ((*reader & 0xc0) == 0xc0) {
uint16_t ptr_offset;
if (reader + 1 >= bufend)
goto err;
ptr_offset = (*reader & 0x3f) * 256 + reader[1];
/* Must point BEFORE current position */
if ((buffer + reader[1]) > reader)
if ((buffer + ptr_offset) >= reader)
goto err;
if (depth++ > 100)
goto err;
n = resolv_read_name(buffer, bufend, buffer + (*reader & 0x3f)*256 + reader[1],
n = resolv_read_name(buffer, bufend, buffer + ptr_offset,
dest, dest_len - nb_bytes, offset, depth);
if (n == 0)
goto err;
@ -1232,8 +1236,7 @@ static int resolv_validate_dns_response(unsigned char *resp, unsigned char *bufe
if (reader + 4 > bufend)
goto invalid_resp;
answer_record->ttl = reader[0] * 16777216 + reader[1] * 65536
+ reader[2] * 256 + reader[3];
answer_record->ttl = read_n32(reader);
reader += 4;
/* Now reading data len */
@ -1430,7 +1433,7 @@ static int resolv_validate_dns_response(unsigned char *resp, unsigned char *bufe
len = resolv_read_name(resp, bufend, reader, tmpname, DNS_MAX_NAME_SIZE,
&offset, 0);
if (len == 0)
continue;
goto invalid_resp;
if (reader + offset + 10 >= bufend)
goto invalid_resp;
@ -1466,11 +1469,8 @@ static int resolv_validate_dns_response(unsigned char *resp, unsigned char *bufe
offset = 0;
len = resolv_read_name(resp, bufend, reader, tmpname, DNS_MAX_NAME_SIZE, &offset, 0);
if (len == 0) {
pool_free(resolv_answer_item_pool, answer_record);
answer_record = NULL;
continue;
}
if (len == 0)
goto invalid_resp;
memcpy(answer_record->name, tmpname, len);
answer_record->name[len] = 0;
@ -1497,8 +1497,7 @@ static int resolv_validate_dns_response(unsigned char *resp, unsigned char *bufe
if (reader + 4 > bufend)
goto invalid_resp;
answer_record->ttl = reader[0] * 16777216 + reader[1] * 65536
+ reader[2] * 256 + reader[3];
answer_record->ttl = read_n32(reader);
reader += 4;
/* Now reading data len */
@ -1598,7 +1597,6 @@ static int resolv_validate_dns_response(unsigned char *resp, unsigned char *bufe
tmp_record->ar_item == NULL &&
memcmp(tmp_record->data.target, answer_record->name, tmp_record->data_len) == 0) {
/* Always use the received additional record to refresh info */
pool_free(resolv_answer_item_pool, tmp_record->ar_item);
tmp_record->ar_item = answer_record;
answer_record = NULL;
break;
@ -1854,7 +1852,15 @@ int resolv_dn_label_to_str(const char *dn, int dn_len, char *str, int str_len)
ptr = str;
for (i = 0; i < dn_len; ++i) {
sz = dn[i];
sz = (unsigned char)dn[i];
if (!sz)
break;
/* Check str_len adding 1 for the dot if (i!=0) and 1 for null terminator */
if (str_len < sz+i+(!!i)+1)
return -1;
if (i)
*ptr++ = '.';
/* copy the string at i+1 to lower case */
@ -2695,6 +2701,7 @@ static void resolvers_destroy(struct resolvers *resolvers)
dns_ring_free(ns->stream->ring_req);
task_destroy(ns->stream->task_req);
task_destroy(ns->stream->task_rsp);
task_destroy(ns->stream->task_idle);
free(ns->stream);
}
LIST_DEL_INIT(&ns->list);
@ -3198,7 +3205,7 @@ enum act_return resolv_action_do_resolve(struct act_rule *rule, struct proxy *px
if (resolution->status == RSLV_STATUS_NONE)
goto yield;
if (resolution->status == RSLV_STATUS_VALID) {
struct sample smp;
struct sample smp = { 0 };
short ip_sin_family = 0;
void *ip = NULL;
@ -3404,8 +3411,8 @@ enum act_parse_ret resolv_parse_do_resolve(const char **args, int *orig_arg, str
ha_free(&rule->arg.resolv.varname);
ha_free(&rule->arg.resolv.resolvers_id);
ha_free(&rule->arg.resolv.opts);
memprintf(err, "Can't parse '%s'. Expects 'do-resolve(<varname>,<resolvers>[,<options>]) <expr>'. Available options are 'ipv4' and 'ipv6'",
args[cur_arg]);
memprintf(err, "Can't parse '%s'%s%s. Expects 'do-resolve(<varname>,<resolvers>[,<options>]) <expr>'. Available options are 'ipv4' and 'ipv6'",
args[cur_arg], *err ? ": " : "", *err ? *err : "");
return ACT_RET_PRS_ERR;
}
@ -3476,7 +3483,7 @@ static int parse_resolve_conf(char **errmsg, char **warnmsg)
int duplicate_name = 0;
int err_code = 0;
if ((resolv_line = malloc(sizeof(*resolv_line) * LINESIZE)) == NULL) {
if ((resolv_line = malloc(array_size_or_fail(sizeof(*resolv_line), LINESIZE))) == NULL) {
memprintf(errmsg, "out of memory.\n");
err_code |= ERR_ALERT | ERR_FATAL;
goto resolv_out;
@ -3568,6 +3575,8 @@ static int parse_resolve_conf(char **errmsg, char **warnmsg)
if (errmsg)
memprintf(errmsg, "parsing [/etc/resolv.conf:%d] : out of memory.", resolv_linenum);
err_code |= ERR_ALERT | ERR_FATAL;
dns_ring_free(newnameserver->dgram->ring_req);
free(newnameserver->dgram);
free(newnameserver);
goto resolv_out;
}
@ -3578,6 +3587,8 @@ static int parse_resolve_conf(char **errmsg, char **warnmsg)
memprintf(errmsg, "parsing [/etc/resolv.conf:%d] : out of memory.", resolv_linenum);
err_code |= ERR_ALERT | ERR_FATAL;
free((char *)newnameserver->conf.file);
dns_ring_free(newnameserver->dgram->ring_req);
free(newnameserver->dgram);
free(newnameserver);
goto resolv_out;
}
@ -3668,6 +3679,7 @@ err_free_conf_file:
ha_free((void **)&r->conf.file);
err_free_p:
proxy_drop(p);
LIST_DEL_INIT(&r->list);
err_free_r:
ha_free(&r);
return err_code;
@ -3712,6 +3724,7 @@ int cfg_parse_resolvers(const char *file, int linenum, char **args, int kwm)
ha_alert("Parsing [%s:%d]: resolvers '%s' has same name as another resolvers (declared at %s:%d).\n",
file, linenum, args[1], curr_resolvers->conf.file, curr_resolvers->conf.line);
err_code |= ERR_ALERT | ERR_ABORT;
goto out;
}
}
@ -3749,6 +3762,7 @@ int cfg_parse_resolvers(const char *file, int linenum, char **args, int kwm)
ha_alert("Parsing [%s:%d]: nameserver '%s' has same name as another nameserver (declared at %s:%d).\n",
file, linenum, args[1], newnameserver->conf.file, newnameserver->conf.line);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
}
@ -3772,30 +3786,56 @@ int cfg_parse_resolvers(const char *file, int linenum, char **args, int kwm)
SRV_PARSE_PARSE_ADDR|SRV_PARSE_INITIAL_RESOLVE);
if (err_code & (ERR_FATAL|ERR_ABORT)) {
err_code |= ERR_ABORT;
free(newnameserver);
goto out;
}
if (dns_stream_init(newnameserver, curr_resolvers->px->srv) < 0) {
ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum);
err_code |= ERR_ALERT|ERR_ABORT;
free(newnameserver);
goto out;
}
}
else if (dns_dgram_init(newnameserver, sk) < 0) {
ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum);
err_code |= ERR_ALERT | ERR_ABORT;
free(newnameserver);
goto out;
}
if ((newnameserver->conf.file = strdup(file)) == NULL) {
ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum);
err_code |= ERR_ALERT | ERR_ABORT;
if (newnameserver->stream) {
dns_ring_free(newnameserver->stream->ring_req);
task_destroy(newnameserver->stream->task_req);
task_destroy(newnameserver->stream->task_rsp);
task_destroy(newnameserver->stream->task_idle);
free(newnameserver->stream);
} else if (newnameserver->dgram) {
dns_ring_free(newnameserver->dgram->ring_req);
free(newnameserver->dgram);
}
free(newnameserver);
goto out;
}
if ((newnameserver->id = strdup(args[1])) == NULL) {
ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum);
err_code |= ERR_ALERT | ERR_ABORT;
free((char *)newnameserver->conf.file);
if (newnameserver->stream) {
dns_ring_free(newnameserver->stream->ring_req);
task_destroy(newnameserver->stream->task_req);
task_destroy(newnameserver->stream->task_rsp);
task_destroy(newnameserver->stream->task_idle);
free(newnameserver->stream);
} else if (newnameserver->dgram) {
dns_ring_free(newnameserver->dgram->ring_req);
free(newnameserver->dgram);
}
free(newnameserver);
goto out;
}

View file

@ -2150,11 +2150,11 @@ static int sample_conv_be2hex_check(struct arg *args, struct sample_conv *conv,
*/
static int sample_conv_be2hex(const struct arg *args, struct sample *smp, void *private)
{
struct buffer *trash = get_trash_chunk_sz(smp->data.u.str.data);
struct buffer *trash = get_trash_chunk_sz(smp->data.u.str.data * 2);
int chunk_size = args[1].data.sint;
const int last = args[2].data.sint ? smp->data.u.str.data - chunk_size + 1 : smp->data.u.str.data;
int i;
int max_size;
size_t max_size;
int ptr = 0;
unsigned char c;
@ -2163,7 +2163,9 @@ static int sample_conv_be2hex(const struct arg *args, struct sample *smp, void *
trash->data = 0;
if (args[0].data.str.data == 0 && args[2].data.sint == 0)
chunk_size = smp->data.u.str.data;
max_size = trash->size - 2 * chunk_size;
if (2 * (size_t)chunk_size > trash->size)
return 0;
max_size = trash->size - 2 * (size_t)chunk_size;
while (ptr < last && trash->data <= max_size) {
if (ptr) {

View file

@ -145,6 +145,7 @@ static void srv_reset_path_parameters(struct server *s)
{
HA_RWLOCK_WRLOCK(SERVER_LOCK, &s->path_params.param_lock);
s->path_params.nego_alpn[0] = 0;
s->path_params.srv_hash = 0;
HA_RWLOCK_WRUNLOCK(SERVER_LOCK, &s->path_params.param_lock);
}
@ -643,7 +644,7 @@ int srv_check_reuse_ws(struct server *srv)
* for mux selection.
*/
const struct ist srv_mux = srv->mux_proto ?
srv->mux_proto->token : IST_NULL;
srv->mux_proto->mux_proto : IST_NULL;
switch (srv->ws) {
/* "auto" means use the same protocol : reuse is possible. */
@ -935,6 +936,8 @@ static int srv_parse_enabled(char **args, int *cur_arg,
newsrv->next_state = SRV_ST_RUNNING;
newsrv->check.state &= ~CHK_ST_PAUSED;
newsrv->check.health = newsrv->check.rise;
srv_set_init_state(newsrv);
return 0;
}
@ -1013,13 +1016,19 @@ static int srv_parse_hash_key(char **args, int *cur_arg,
struct proxy *curproxy, struct server *newsrv, char **err)
{
if (!args[*cur_arg + 1]) {
memprintf(err, "'%s expects 'id', 'addr', or 'addr-port' value", args[*cur_arg]);
memprintf(err, "'%s expects 'id', 'id32', 'guid', 'addr', or 'addr-port' value", args[*cur_arg]);
return ERR_ALERT | ERR_FATAL;
}
if (strcmp(args[*cur_arg + 1], "id") == 0) {
newsrv->hash_key = SRV_HASH_KEY_ID;
}
else if (strcmp(args[*cur_arg + 1], "id32") == 0) {
newsrv->hash_key = SRV_HASH_KEY_ID32;
}
else if (strcmp(args[*cur_arg + 1], "guid") == 0) {
newsrv->hash_key = SRV_HASH_KEY_GUID;
}
else if (strcmp(args[*cur_arg + 1], "addr") == 0) {
newsrv->hash_key = SRV_HASH_KEY_ADDR;
}
@ -1027,7 +1036,7 @@ static int srv_parse_hash_key(char **args, int *cur_arg,
newsrv->hash_key = SRV_HASH_KEY_ADDR_PORT;
}
else {
memprintf(err, "'%s' has to be 'id', 'addr', or 'addr-port'", args[*cur_arg]);
memprintf(err, "'%s' has to be 'id', 'id32', 'guid', 'addr', or 'addr-port'", args[*cur_arg]);
return ERR_ALERT | ERR_FATAL;
}
@ -1124,22 +1133,25 @@ static int srv_parse_init_addr(char **args, int *cur_arg,
/* Parse the "init-state" server keyword */
static int srv_parse_init_state(char **args, int *cur_arg,
struct proxy *curproxy, struct server *newsrv, char **err)
struct proxy *curproxy, struct server *newsrv, char **err)
{
if (strcmp(args[*cur_arg + 1], "fully-up") == 0)
newsrv->init_state= SRV_INIT_STATE_FULLY_UP;
if (strcmp(args[*cur_arg + 1], "none") == 0)
newsrv->init_state = SRV_INIT_STATE_NONE;
else if (strcmp(args[*cur_arg + 1], "fully-up") == 0)
newsrv->init_state = SRV_INIT_STATE_FULLY_UP;
else if (strcmp(args[*cur_arg + 1], "up") == 0)
newsrv->init_state = SRV_INIT_STATE_UP;
else if (strcmp(args[*cur_arg + 1], "down") == 0)
newsrv->init_state= SRV_INIT_STATE_DOWN;
newsrv->init_state = SRV_INIT_STATE_DOWN;
else if (strcmp(args[*cur_arg + 1], "fully-down") == 0)
newsrv->init_state= SRV_INIT_STATE_FULLY_DOWN;
newsrv->init_state = SRV_INIT_STATE_FULLY_DOWN;
else {
memprintf(err, "'%s' expects one of 'fully-up', 'up', 'down', or 'fully-down' but got '%s'",
memprintf(err, "'%s' expects one of 'none', 'fully-up', 'up', 'down', or 'fully-down' but got '%s'",
args[*cur_arg], args[*cur_arg + 1]);
return ERR_ALERT | ERR_FATAL;
}
srv_set_init_state(newsrv);
return 0;
}
@ -1308,19 +1320,20 @@ static int srv_parse_pool_max_conn(char **args, int *cur_arg, struct proxy *curp
static int srv_parse_id(char **args, int *cur_arg, struct proxy *curproxy, struct server *newsrv, char **err)
{
struct server *target;
llong id;
if (!*args[*cur_arg + 1]) {
memprintf(err, "'%s' : expects an integer argument", args[*cur_arg]);
return ERR_ALERT | ERR_FATAL;
}
newsrv->puid = atol(args[*cur_arg + 1]);
if (newsrv->puid <= 0) {
memprintf(err, "'%s' : custom id has to be > 0", args[*cur_arg]);
id = atol(args[*cur_arg + 1]);
if (id < 1 || id > ~0U) {
memprintf(err, "'%s' : custom id has to be between 1 and 4294967295.", args[*cur_arg]);
return ERR_ALERT | ERR_FATAL;
}
newsrv->puid = id;
target = server_find_by_id(curproxy, newsrv->puid);
if (target) {
memprintf(err, "'%s' : custom id %d already used at %s:%d ('server %s')",
@ -2880,7 +2893,7 @@ void srv_settings_init(struct server *srv)
srv->agent.fall = DEF_AGENT_FALLTIME;
srv->agent.port = 0;
srv->init_state = SRV_INIT_STATE_UP;
srv->init_state = SRV_INIT_STATE_NONE;
srv->maxqueue = 0;
srv->minconn = 0;
@ -3014,7 +3027,9 @@ void srv_settings_cpy(struct server *srv, const struct server *src, int srv_tmpl
srv->check.rise = srv->check.health = src->check.rise;
srv->check.fall = src->check.fall;
/* Here we check if 'disabled' is the default server state */
/* Here we check if 'disabled' is the default server state. Otherwise,
* we check 'init-state' parameter
*/
if (src->next_admin & (SRV_ADMF_CMAINT | SRV_ADMF_FMAINT)) {
srv->next_admin |= SRV_ADMF_CMAINT | SRV_ADMF_FMAINT;
srv->next_state = SRV_ST_STOPPED;
@ -3042,6 +3057,7 @@ void srv_settings_cpy(struct server *srv, const struct server *src, int srv_tmpl
srv->init_addr = src->init_addr;
srv->init_state = src->init_state;
srv_set_init_state(srv);
#if defined(USE_OPENSSL)
srv_ssl_settings_cpy(srv, src);
#endif
@ -3055,8 +3071,8 @@ void srv_settings_cpy(struct server *srv, const struct server *src, int srv_tmpl
srv->tcp_ut = src->tcp_ut;
#endif
srv->mux_proto = src->mux_proto;
if (srv->pool_conn_name)
srv->pool_conn_name = strdup(srv->pool_conn_name);
if (src->pool_conn_name)
srv->pool_conn_name = strdup(src->pool_conn_name);
srv->pool_purge_delay = src->pool_purge_delay;
srv->low_idle_conns = src->low_idle_conns;
srv->max_idle_conns = src->max_idle_conns;
@ -3616,7 +3632,7 @@ int srv_postinit(struct server *srv)
/* initialize idle conns lists */
if (srv->max_idle_conns != 0) {
srv->curr_idle_thr = ha_aligned_zalloc(64, global.nbthread * sizeof(*srv->curr_idle_thr));
srv->curr_idle_thr = ha_aligned_zalloc(64, array_size_or_fail(global.nbthread, sizeof(*srv->curr_idle_thr)));
if (!srv->curr_idle_thr) {
ha_alert("memory error during idle conn list init for %s/%s server\n",
srv->proxy->id, srv->id);
@ -3958,6 +3974,10 @@ static int _srv_parse_finalize(char **args, int cur_arg,
ha_alert("unable to enable checks and tracking at the same time!\n");
return ERR_ALERT | ERR_FATAL;
}
if (srv->init_state != SRV_INIT_STATE_NONE && srv->trackit) {
ha_alert("unable to set init-state and tracking at the same time!\n");
return ERR_ALERT | ERR_FATAL;
}
if (srv->do_agent && !srv->agent.port) {
ha_alert("server %s does not have agent port. Agent check has been disabled.\n",
@ -4024,6 +4044,18 @@ static int _srv_parse_finalize(char **args, int cur_arg,
return ERR_ALERT | ERR_FATAL;
#endif
}
else {
if (srv->proxy->mode == PR_MODE_HTTP && !srv->mux_proto &&
srv->ssl_ctx.alpn_str && strstr(srv->ssl_ctx.alpn_str, "\002h3")) {
if (!experimental_directives_allowed) {
ha_alert("HTTP/3 on TCP listed via ALPN requires the QMUX protocol which is experimental, "
"must be allowed via a global 'expose-experimental-directives'.\n");
return ERR_ALERT | ERR_FATAL;
}
mark_tainted(TAINTED_CONFIG_EXP_KW_DECLARED);
}
}
if (!(srv->proxy->cap & PR_CAP_LB)) {
/* No need to wait for effective proxy mode, it is already known:
@ -5226,6 +5258,8 @@ int srv_set_fqdn(struct server *srv, const char *hostname, int resolv_locked)
struct resolv_resolution *resolution;
char *hostname_dn;
int hostname_len, hostname_dn_len;
char *hostname_dup = NULL;
char *hostname_dn_dup = NULL;
/* Note that the server lock is already held. */
if (!srv->resolvers)
@ -5256,13 +5290,18 @@ int srv_set_fqdn(struct server *srv, const char *hostname, int resolv_locked)
resolv_unlink_resolution(srv->resolv_requester);
hostname_dup = strdup(hostname);
hostname_dn_dup = strdup(hostname_dn);
if (!hostname_dup || !hostname_dn_dup)
goto err;
free(srv->hostname);
free(srv->hostname_dn);
srv->hostname = strdup(hostname);
srv->hostname_dn = strdup(hostname_dn);
srv->hostname = hostname_dup;
srv->hostname_dn = hostname_dn_dup;
srv->hostname_dn_len = hostname_dn_len;
if (!srv->hostname || !srv->hostname_dn)
goto err;
hostname_dup = NULL;
hostname_dn_dup = NULL;
if (srv->flags & SRV_F_NO_RESOLUTION)
goto end;
@ -5278,6 +5317,8 @@ int srv_set_fqdn(struct server *srv, const char *hostname, int resolv_locked)
err:
if (!resolv_locked)
HA_SPIN_UNLOCK(DNS_LOCK, &srv->resolvers->lock);
ha_free(&hostname_dup);
ha_free(&hostname_dn_dup);
return -1;
}
@ -5702,11 +5743,13 @@ static int cli_parse_set_server(char **args, char *payload, struct appctx *appct
HA_SPIN_LOCK(SERVER_LOCK, &sv->lock);
if (strcmp(args[4], "on") == 0) {
if (srv_set_ssl(sv, 1)) {
HA_SPIN_UNLOCK(SERVER_LOCK, &sv->lock);
cli_dynerr(appctx, memprintf(&err, "failed to enable ssl for server %s.\n", args[2]));
goto out;
}
} else if (strcmp(args[4], "off") == 0) {
if (srv_set_ssl(sv, 0)) {
HA_SPIN_UNLOCK(SERVER_LOCK, &sv->lock);
cli_dynerr(appctx, memprintf(&err, "failed to disable ssl for server %s.\n", args[2]));
goto out;
}
@ -5916,6 +5959,7 @@ static int cli_parse_enable_health(char **args, char *payload, struct appctx *ap
HA_SPIN_LOCK(SERVER_LOCK, &sv->lock);
sv->check.state |= CHK_ST_ENABLED;
srv_set_init_state(sv);
HA_SPIN_UNLOCK(SERVER_LOCK, &sv->lock);
return 1;
}
@ -6062,8 +6106,8 @@ static int srv_init_per_thr(struct server *srv)
{
int i;
srv->per_thr = ha_aligned_zalloc(64, global.nbthread * sizeof(*srv->per_thr));
srv->per_tgrp = ha_aligned_zalloc(64, global.nbtgroups * sizeof(*srv->per_tgrp));
srv->per_thr = ha_aligned_zalloc(64, array_size_or_fail(global.nbthread, sizeof(*srv->per_thr)));
srv->per_tgrp = ha_aligned_zalloc(64, array_size_or_fail(global.nbtgroups, sizeof(*srv->per_tgrp)));
if (!srv->per_thr || !srv->per_tgrp)
return -1;
@ -6254,9 +6298,9 @@ static int cli_parse_add_server(char **args, char *payload, struct appctx *appct
int proto_mode = conn_pr_mode_to_proto_mode(be->mode);
const struct mux_proto_list *mux_ent;
mux_ent = conn_get_best_mux_entry(srv->mux_proto->token, PROTO_SIDE_BE, srv_is_quic(srv), proto_mode);
mux_ent = conn_get_best_mux_entry(srv->mux_proto->mux_proto, IST_NULL, PROTO_SIDE_BE, srv_is_quic(srv), proto_mode);
if (!mux_ent || !isteq(mux_ent->token, srv->mux_proto->token)) {
if (!mux_ent || !isteq(mux_ent->mux_proto, srv->mux_proto->mux_proto)) {
ha_alert("MUX protocol is not usable for server.\n");
goto out;
}
@ -6960,20 +7004,12 @@ static int _srv_update_status_adm(struct server *s, enum srv_adm_st_chg_cause ca
*/
if (s->check.state & CHK_ST_ENABLED) {
s->check.state &= ~CHK_ST_PAUSED;
if(s->init_state == SRV_INIT_STATE_FULLY_UP) {
s->check.health = s->check.rise + s->check.fall - 1; /* initially UP, when all checks fail to bring server DOWN */
}
else if(s->init_state == SRV_INIT_STATE_DOWN) {
s->check.health = s->check.rise - 1; /* initially DOWN, when one check is successful bring server UP */
}
else if(s->init_state == SRV_INIT_STATE_FULLY_DOWN) {
s->check.health = 0; /* initially DOWN, when all checks are successful bring server UP */
} else {
s->check.health = s->check.rise; /* initially UP, when one check fails check brings server DOWN */
}
s->check.health = s->check.rise; /* start OK but check immediately */
}
srv_set_init_state(s);
if ((!s->track || s->track->next_state != SRV_ST_STOPPED) &&
if (s->init_state == SRV_INIT_STATE_NONE &&
(!s->track || s->track->next_state != SRV_ST_STOPPED) &&
(!(s->agent.state & CHK_ST_ENABLED) || (s->agent.health >= s->agent.rise)) &&
(!(s->check.state & CHK_ST_ENABLED) || (s->check.health >= s->check.rise))) {
if (s->track && s->track->next_state == SRV_ST_STOPPING) {

View file

@ -39,7 +39,7 @@ static const struct trace_event sess_trace_events[] = {
{ .mask = SESS_EV_NEW, .name = "sess_new", .desc = "new session creation" },
#define SESS_EV_END (1ULL << 1)
{ .mask = SESS_EV_END, .name = "sess_end", .desc = "session termination" },
#define SESS_EV_ERR (1ULL << 1)
#define SESS_EV_ERR (1ULL << 2)
{ .mask = SESS_EV_ERR, .name = "sess_err", .desc = "session error" },
{ }
};
@ -241,14 +241,17 @@ int session_accept_fd(struct connection *cli_conn)
if (l->bind_conf->options & BC_O_ACC_CIP)
cli_conn->flags |= CO_FL_ACCEPT_CIP;
if (l->bind_conf->mux_proto && isteq(l->bind_conf->mux_proto->token, ist("qmux")))
cli_conn->flags |= (CO_FL_QMUX_RECV|CO_FL_QMUX_SEND);
/* Add the handshake pseudo-XPRT */
if (cli_conn->flags & (CO_FL_ACCEPT_PROXY | CO_FL_ACCEPT_CIP)) {
if (xprt_add_hs(cli_conn) != 0)
goto out_free_conn;
}
/* Add handshake layer prior to MUX init if required. Does nothing if SSL layer is active though. */
if (l->bind_conf->mux_proto && l->bind_conf->mux_proto->init_xprt) {
if (xprt_add_l6hs(cli_conn, l->bind_conf->mux_proto->init_xprt))
goto out_free_conn;
}
}
/* Reversed conns already have an assigned session, do not recreate it. */
@ -351,7 +354,7 @@ int session_accept_fd(struct connection *cli_conn)
* v | | |
* conn -- owner ---> task <-----+
*/
if (cli_conn->flags & (CO_FL_WAIT_XPRT | CO_FL_EARLY_SSL_HS)) {
if (cli_conn->flags & (CO_FL_WAIT_XPRT | CO_FL_EARLY_SSL_HS | CO_FL_WAIT_XPRT_L6)) {
int timeout;
int clt_tmt = p->timeout.client;
int hs_tmt = p->timeout.client_hs;

View file

@ -533,7 +533,7 @@ int sock_get_old_sockets(const char *unixsocket)
}
memset(&msghdr, 0, sizeof(msghdr));
cmsgbuf = malloc(CMSG_SPACE(sizeof(int)) * MAX_SEND_FD);
cmsgbuf = malloc(array_size_or_fail(CMSG_SPACE(sizeof(int)), MAX_SEND_FD));
if (!cmsgbuf) {
ha_warning("Failed to allocate memory to send sockets\n");
goto out;
@ -561,13 +561,13 @@ int sock_get_old_sockets(const char *unixsocket)
goto out;
}
tmpbuf = malloc(fd_nb * (1 + MAXPATHLEN + 1 + IFNAMSIZ + sizeof(int)));
tmpbuf = malloc(array_size_or_fail(fd_nb, (1 + MAXPATHLEN + 1 + IFNAMSIZ + sizeof(int))));
if (tmpbuf == NULL) {
ha_warning("Failed to allocate memory while receiving sockets\n");
goto out;
}
tmpfd = malloc(fd_nb * sizeof(int));
tmpfd = malloc(array_size_or_fail(fd_nb, sizeof(int)));
if (tmpfd == NULL) {
ha_warning("Failed to allocate memory while receiving sockets\n");
goto out;
@ -1011,6 +1011,7 @@ int sock_conn_check(struct connection *conn)
goto wait;
if (errno && errno != EISCONN) {
conn_set_errno(conn, errno);
conn_report_term_evt(conn, tevt_loc_fd, fd_tevt_type_connect_err);
goto out_error;
}

View file

@ -1099,7 +1099,7 @@ struct ckch_store *ckchs_dup(const struct ckch_store *src)
/* copy the array of domain strings */
while (src->conf.acme.domains[n]) {
r = my_realloc2(r, sizeof(char *) * (n + 2));
r = my_realloc2(r, array_size_or_fail(sizeof(char *), (n + 2)));
if (!r)
goto error;
@ -1120,7 +1120,7 @@ struct ckch_store *ckchs_dup(const struct ckch_store *src)
/* copy the array of IP strings */
while (src->conf.acme.ips[n]) {
r = my_realloc2(r, sizeof(char *) * (n + 2));
r = my_realloc2(r, array_size_or_fail(sizeof(char *), (n + 2)));
if (!r)
goto error;
@ -5329,7 +5329,7 @@ int ckch_conf_parse(char **args, int cur_arg, struct ckch_conf *f, int *found, c
do {
while (*e != ',' && *e != '\0')
e++;
r = my_realloc2(r, sizeof(char *) * (n + 2));
r = my_realloc2(r, array_size_or_fail(sizeof(char *), (n + 2)));
if (!r) {
ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum);
err_code |= ERR_ALERT | ERR_ABORT;

View file

@ -349,7 +349,7 @@ int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg)
if ((TRACE_SOURCE)->verbosity >= SSL_VERB_ADVANCED) {
if (TRACE_ENABLED(TRACE_LEVEL_DATA, SSL_EV_CONN_CIPHERS_EXT, conn, 0, 0, 0)) {
const uint8_t *cipher_suites;
size_t len;
size_t __maybe_unused len;
#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)
len = ctx->cipher_suites_len;

View file

@ -356,8 +356,10 @@ int ssl_sock_generate_certificate(const char *servername, struct bind_conf *bind
ssl_ctx = (SSL_CTX *)lru->data;
if (!ssl_ctx && lru) {
ssl_ctx = ssl_sock_do_create_cert(servername, bind_conf, ssl);
if (!ssl_ctx)
if (!ssl_ctx) {
HA_RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock);
goto error;
}
lru64_commit(lru, ssl_ctx, cacert, 0, (void (*)(void *))SSL_CTX_free);
}
SSL_set_SSL_CTX(ssl, ssl_ctx);

View file

@ -290,6 +290,8 @@ int ssl_sock_load_ocsp_response(struct buffer *ocsp_response,
int ret = 1;
#ifdef HAVE_ASN1_TIME_TO_TM
struct tm nextupd_tm = {0};
#else
long expire = 0;
#endif
resp = d2i_OCSP_RESPONSE(NULL, (const unsigned char **)&p,
@ -391,11 +393,12 @@ int ssl_sock_load_ocsp_response(struct buffer *ocsp_response,
}
ocsp->expire = my_timegm(&nextupd_tm) - OCSP_MAX_RESPONSE_TIME_SKEW;
#else
ocsp->expire = asn1_generalizedtime_to_epoch(nextupd) - OCSP_MAX_RESPONSE_TIME_SKEW;
if (ocsp->expire < 0) {
expire = asn1_generalizedtime_to_epoch(nextupd) - OCSP_MAX_RESPONSE_TIME_SKEW;
if (expire < 0) {
memprintf(err, "OCSP single response: Invalid \"Next Update\" time");
goto out;
}
ocsp->expire = expire;
#endif
if (ocsp->expire < date.tv_sec) {

View file

@ -3743,7 +3743,7 @@ static void ssl_sock_resize_passphrase_cache(void)
int idx;
int new_size = passphrase_cache_size << 1;
passphrase_randoms = my_realloc2(passphrase_randoms, sizeof(*passphrase_randoms) * (new_size));
passphrase_randoms = my_realloc2(passphrase_randoms, array_size_or_fail(sizeof(*passphrase_randoms), (new_size)));
if (!passphrase_randoms) {
ha_alert("ssl_sock_passwd_cb: passphrase randoms realloc failed");
passphrase_idx = -1;
@ -3759,7 +3759,7 @@ static void ssl_sock_resize_passphrase_cache(void)
if (passphrase_cache_size) {
passphrase_cache_size = new_size;
passphrase_cache = my_realloc2(passphrase_cache, sizeof(*passphrase_cache) * passphrase_cache_size);
passphrase_cache = my_realloc2(passphrase_cache, array_size_or_fail(sizeof(*passphrase_cache), passphrase_cache_size));
if (!passphrase_cache) {
ha_alert("ssl_sock_passwd_cb: passphrase cache realloc failed");
passphrase_idx = -1;
@ -4287,13 +4287,15 @@ static int ssl_sess_new_srv_cb(SSL *ssl, SSL_SESSION *sess)
if (ssl_sock_get_alpn(conn, qc->xprt_ctx, &alpn, &len)) {
struct quic_early_transport_params *etps = &s->path_params.tps;
if (len < sizeof(s->path_params.nego_alpn) &&
(len != strlen(s->path_params.nego_alpn) ||
memcmp(&s->path_params.nego_alpn, alpn, len) != 0)) {
if (s->path_params.srv_hash != conn->hash_node.key ||
(len < sizeof(s->path_params.nego_alpn) &&
(len != strlen(s->path_params.nego_alpn) ||
memcmp(&s->path_params.nego_alpn, alpn, len) != 0))) {
HA_RWLOCK_WRLOCK(SERVER_LOCK, &s->path_params.param_lock);
memcpy(&s->path_params.nego_alpn, alpn, len);
s->path_params.nego_alpn[len] = 0;
/* The transport parameters are not stored without ALPN */
s->path_params.srv_hash = conn->hash_node.key;
qc_early_transport_params_cpy(qc, etps, &qc->tx.params);
HA_RWLOCK_WRUNLOCK(SERVER_LOCK, &s->path_params.param_lock);
}
@ -6920,12 +6922,14 @@ struct task *ssl_sock_io_cb(struct task *t, void *context, unsigned int state)
srv = objt_server(conn->target);
if (srv && ssl_sock_get_alpn(conn, ctx, &alpn, &len)) {
if (len < sizeof(srv->path_params.nego_alpn) &&
(len != strlen(srv->path_params.nego_alpn) ||
memcmp(&srv->path_params.nego_alpn, alpn, len) != 0)) {
if (srv->path_params.srv_hash != conn->hash_node.key ||
(len < sizeof(srv->path_params.nego_alpn) &&
(len != strlen(srv->path_params.nego_alpn) ||
memcmp(&srv->path_params.nego_alpn, alpn, len) != 0))) {
HA_RWLOCK_WRLOCK(SERVER_LOCK, &srv->path_params.param_lock);
memcpy(&srv->path_params.nego_alpn, alpn, len);
srv->path_params.nego_alpn[len] = 0;
srv->path_params.srv_hash = conn->hash_node.key;
HA_RWLOCK_WRUNLOCK(SERVER_LOCK, &srv->path_params.param_lock);
}
}
@ -6962,29 +6966,35 @@ struct task *ssl_sock_io_cb(struct task *t, void *context, unsigned int state)
* woke a tasklet already.
*/
if (ctx->conn->xprt_ctx == ctx) {
const struct mux_proto_list *mux;
int closed_connection = 0;
if (!ctx->conn->mux) {
if (ctx->conn->flags & (CO_FL_QMUX_RECV|CO_FL_QMUX_SEND)) {
const struct xprt_ops *ops = xprt_get(XPRT_QMUX);
void *xprt_ctx_hs = NULL;
mux = !conn_is_back(conn) ?
conn_select_mux_fe(conn) : conn_select_mux_be(conn);
ret = ops->init(conn, &xprt_ctx_hs);
BUG_ON(ret);
if (mux->init_xprt) {
ret = xprt_add_l6hs(conn, mux->init_xprt);
/* Frontend conn must be freed in case of XPRT init failure. */
if (ret) {
if (!conn_is_back(conn)) {
conn->flags |= CO_FL_ERROR; /* Ensure conn will be freed on next call. */
ret = conn_complete_session(conn);
BUG_ON(ret >= 0); /* conn_complete_session() expected to fail on CO_FL_ERROR */
t = NULL;
}
goto leave;
}
ret = ops->add_xprt(conn, xprt_ctx_hs,
conn->xprt_ctx, conn->xprt, NULL, NULL);
BUG_ON(ret);
conn->xprt = ops;
conn->xprt_ctx = xprt_ctx_hs;
ret = conn->xprt->start(conn, xprt_ctx_hs);
BUG_ON(ret);
ret = conn_xprt_start(conn);
}
else
else {
/* TODO MUX selection already performs by conn_select_mux_fe/be().
* Implement an alternative to conn_create_mux() to skip this
* part and directly init the connection and its MUX.
*/
ret = conn_create_mux(ctx->conn, &closed_connection);
}
}
if (ret >= 0 && ctx->conn->mux && !woke && ctx->conn->mux && ctx->conn->mux->wake) {

View file

@ -417,7 +417,7 @@ void apply_stats_file(void)
goto out;
}
line = malloc(sizeof(char) * LINESIZE);
line = malloc(array_size_or_fail(sizeof(char), LINESIZE));
if (!line) {
ha_warning("config: Can't load stats-file '%s': line alloc error.\n", global.stats_file);
goto out;

View file

@ -1208,7 +1208,7 @@ static int allocate_stats_px_postcheck(void)
stat_cols_len[STATS_DOMAIN_PROXY] += ST_I_PX_MAX;
stat_cols[STATS_DOMAIN_PROXY] = malloc(stat_cols_len[STATS_DOMAIN_PROXY] * sizeof(struct name_desc));
stat_cols[STATS_DOMAIN_PROXY] = malloc(array_size_or_fail(stat_cols_len[STATS_DOMAIN_PROXY], sizeof(struct name_desc)));
if (!stat_cols[STATS_DOMAIN_PROXY]) {
ha_alert("stats: cannot allocate all fields for proxy statistics\n");
err_code |= ERR_ALERT | ERR_FATAL;
@ -1247,7 +1247,7 @@ static int allocate_stats_rslv_postcheck(void)
size_t i = 0, offset;
int err_code = 0;
stat_cols[STATS_DOMAIN_RESOLVERS] = malloc(stat_cols_len[STATS_DOMAIN_RESOLVERS] * sizeof(struct name_desc));
stat_cols[STATS_DOMAIN_RESOLVERS] = malloc(array_size_or_fail(stat_cols_len[STATS_DOMAIN_RESOLVERS], sizeof(struct name_desc)));
if (!stat_cols[STATS_DOMAIN_RESOLVERS]) {
ha_alert("stats: cannot allocate all fields for resolver statistics\n");
err_code |= ERR_ALERT | ERR_FATAL;
@ -1282,7 +1282,7 @@ static int allocate_stat_lines_per_thread(void)
for (i = 0; i < STATS_DOMAIN_COUNT; ++i) {
const int domain = domains[i];
stat_lines[domain] = malloc(stat_cols_len[domain] * sizeof(struct field));
stat_lines[domain] = malloc(array_size_or_fail(stat_cols_len[domain], sizeof(struct field)));
if (!stat_lines[domain])
return 0;
}

View file

@ -276,6 +276,7 @@ int stktable_trash_oldest(struct stktable *t)
{
struct stksess *ts;
struct eb32_node *eb;
struct list tofree_list;
int max_search; // no more than 50% misses
int max_per_bucket;
int done_per_bucket;
@ -289,7 +290,7 @@ int stktable_trash_oldest(struct stktable *t)
/* start from a random bucket number to avoid starvation in the last ones */
bucket = init_bucket = statistical_prng_range(CONFIG_HAP_TBL_BUCKETS - 1);
LIST_INIT(&tofree_list);
to_batch = STKTABLE_MAX_UPDATES_AT_ONCE;
max_search = to_batch * 2; // no more than 50% misses
@ -390,7 +391,8 @@ int stktable_trash_oldest(struct stktable *t)
ebmb_delete(&ts->key);
MT_LIST_DELETE(&ts->pend_updts);
eb32_delete(&ts->upd);
__stksess_free(t, ts);
LIST_APPEND(&tofree_list, mt_list_to_list(&ts->pend_updts));
batched++;
done_per_bucket++;
@ -417,6 +419,12 @@ int stktable_trash_oldest(struct stktable *t)
bucket = 0;
} while (max_search > 0 && bucket != init_bucket);
while (!LIST_ISEMPTY(&tofree_list)) {
ts = LIST_ELEM(tofree_list.n, struct stksess *, pend_updts);
LIST_DELETE(mt_list_to_list(&ts->pend_updts));
__stksess_free(t, ts);
}
return batched;
}
@ -953,6 +961,7 @@ struct task *process_tables_expire(struct task *task, void *context, unsigned in
struct stktable *t;
struct stksess *ts;
struct eb32_node *table_eb, *eb;
struct list tofree_list;
int updt_locked;
int to_visit;
int task_exp;
@ -960,6 +969,8 @@ struct task *process_tables_expire(struct task *task, void *context, unsigned in
task_exp = TICK_ETERNITY;
LIST_INIT(&tofree_list);
bucket = (ps - &per_bucket[0]);
to_visit = STKTABLE_MAX_UPDATES_AT_ONCE;
@ -1088,7 +1099,7 @@ struct task *process_tables_expire(struct task *task, void *context, unsigned in
ebmb_delete(&ts->key);
MT_LIST_DELETE(&ts->pend_updts);
eb32_delete(&ts->upd);
__stksess_free(t, ts);
LIST_APPEND(&tofree_list, mt_list_to_list(&ts->pend_updts));
}
if (updt_locked)
@ -1110,6 +1121,13 @@ struct task *process_tables_expire(struct task *task, void *context, unsigned in
if (!tick_isset(task_exp) || (tick_isset(next_exp_table) && tick_is_lt(next_exp_table, task_exp)))
task_exp = next_exp_table;
HA_RWLOCK_WRUNLOCK(STK_TABLE_LOCK, &t->buckets[bucket].sh_lock);
while (!LIST_ISEMPTY(&tofree_list)) {
ts = LIST_ELEM(tofree_list.n, struct stksess *, pend_updts);
LIST_DELETE(mt_list_to_list(&ts->pend_updts));
__stksess_free(t, ts);
}
tmpnode = eb32_next(table_eb);
if (table_eb->key != next_exp_table) {
@ -2675,10 +2693,12 @@ static enum act_return action_inc_gpc(struct act_rule *rule, struct proxy *px,
struct stkctr *stkctr;
/* Extract the stksess, return OK if no stksess available. */
if (s)
if (s && s->stkctr)
stkctr = &s->stkctr[rule->arg.gpc.sc];
else
else if (sess->stkctr)
stkctr = &sess->stkctr[rule->arg.gpc.sc];
else
return ACT_RET_CONT;
ts = stkctr_entry(stkctr);
if (ts) {
@ -2716,10 +2736,12 @@ static enum act_return action_inc_gpc0(struct act_rule *rule, struct proxy *px,
unsigned int period = 0;
/* Extract the stksess, return OK if no stksess available. */
if (s)
if (s && s->stkctr)
stkctr = &s->stkctr[rule->arg.gpc.sc];
else
else if (sess->stkctr)
stkctr = &sess->stkctr[rule->arg.gpc.sc];
else
return ACT_RET_CONT;
ts = stkctr_entry(stkctr);
if (ts) {
@ -5956,7 +5978,7 @@ static int stkt_create_stk_ctr_pool(void)
if (!global.tune.nb_stk_ctr)
return 0;
pool_head_stk_ctr = create_pool("stk_ctr", sizeof(*((struct session*)0)->stkctr) * global.tune.nb_stk_ctr, MEM_F_SHARED);
pool_head_stk_ctr = create_pool("stk_ctr", array_size_or_fail(sizeof(*((struct session*)0)->stkctr), global.tune.nb_stk_ctr), MEM_F_SHARED);
if (!pool_head_stk_ctr) {
ha_alert("out of memory while creating the stick-counters pool.\n");
return ERR_ABORT;

View file

@ -1617,7 +1617,7 @@ int stream_set_http_mode(struct stream *s, const struct mux_proto_list *mux_prot
sc_conn_prepare_endp_upgrade(sc);
if (conn_upgrade_mux_fe(conn, sc, &s->req.buf,
(mux_proto ? mux_proto->token : ist("")),
(mux_proto ? mux_proto->mux_proto : ist("")),
PROTO_MODE_HTTP) == -1) {
sc_conn_abort_endp_upgrade(sc);
return 0;
@ -3268,15 +3268,15 @@ static int check_tcp_switch_stream_mode(struct act_rule *rule, struct proxy *px,
px->options |= PR_O_HTTP_UPG;
if (mux_proto) {
mux_ent = conn_get_best_mux_entry(mux_proto->token, PROTO_SIDE_FE, 0, mode);
if (!mux_ent || !isteq(mux_ent->token, mux_proto->token)) {
mux_ent = conn_get_best_mux_entry(mux_proto->mux_proto, IST_NULL, PROTO_SIDE_FE, 0, mode);
if (!mux_ent || !isteq(mux_ent->mux_proto, mux_proto->mux_proto)) {
memprintf(err, "MUX protocol '%.*s' is not compatible with the selected mode",
(int)mux_proto->token.len, mux_proto->token.ptr);
(int)mux_proto->mux_proto.len, mux_proto->mux_proto.ptr);
return 0;
}
}
else {
mux_ent = conn_get_best_mux_entry(IST_NULL, PROTO_SIDE_FE, 0, mode);
mux_ent = conn_get_best_mux_entry(IST_NULL, IST_NULL, PROTO_SIDE_FE, 0, mode);
if (!mux_ent) {
memprintf(err, "Unable to find compatible MUX protocol with the selected mode");
return 0;

View file

@ -834,6 +834,8 @@ enum tcpcheck_eval_ret tcpcheck_spop_expect_hello(struct check *check, struct tc
goto invalid_frame;
if (decode_varint(&ptr, end, &sz) == -1)
goto invalid_frame;
if (sz >= SPOP_ERR_ENTRIES)
sz = SPOP_ERR_UNKNOWN;
check->code = sz;
}
@ -989,7 +991,7 @@ enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct t
const char *sc = NULL; /* maxconn */
const char *err = NULL; /* first error to report */
const char *wrn = NULL; /* first warning to report */
char *cmd, *p;
char *cmd, *p, *end;
TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
@ -1018,10 +1020,11 @@ enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct t
*/
p = b_head(&check->bi);
while (*p && *p != '\n' && *p != '\r')
end = b_tail(&check->bi);
while (p < end && *p && *p != '\n' && *p != '\r')
p++;
if (!*p) {
if (!*p || p == end) {
if (!last_read)
goto wait_more_data;
@ -1570,7 +1573,7 @@ enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpchec
else {
int mode = tcpchk_rules_type_to_proto_mode(check->tcpcheck->rs->flags);
mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
mux_ops = conn_get_best_mux(conn, IST_NULL, IST_NULL, PROTO_SIDE_BE, mode);
}
if (mux_ops && conn_install_mux(conn, mux_ops, check->sc, proxy, check->sess) < 0) {
TRACE_ERROR("failed to install mux", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
@ -1611,6 +1614,7 @@ enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpchec
case SF_ERR_RESOURCE:
case SF_ERR_INTERNAL:
TRACE_ERROR("report connection error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check, 0, 0, (size_t[]){status});
/* note: errno is no longer guaranteed here */
chk_report_conn_err(check, errno, 0);
ret = TCPCHK_EVAL_STOP;
goto out;
@ -2648,6 +2652,7 @@ int tcpcheck_main(struct check *check)
out_end_tcpcheck:
if ((conn && conn->flags & CO_FL_ERROR) || sc_ep_test(sc, SE_FL_ERROR)) {
TRACE_ERROR("report connection error", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
/* note: errno is no longer guaranteed here */
chk_report_conn_err(check, errno, 0);
}

View file

@ -12,6 +12,9 @@
#include <haproxy/quic_frame.h>
#include <haproxy/quic_tp-t.h>
/* Default protocol when not running over SSL layer. */
#define XPRT_QMUX_DEFAULT_ALPN "h3"
struct xprt_qmux_ctx {
struct connection *conn;
struct wait_event wait_event;
@ -207,7 +210,7 @@ struct task *xprt_qmux_io_cb(struct task *t, void *context, unsigned int state)
out:
if ((conn->flags & CO_FL_ERROR) ||
!(conn->flags & (CO_FL_QMUX_RECV|CO_FL_QMUX_SEND))) {
!(conn->flags & CO_FL_WAIT_XPRT_L6)) {
/* XPRT should be unsubscribed when transfer done or on error. */
BUG_ON(ctx->wait_event.events);
@ -300,6 +303,11 @@ static int xprt_qmux_init(struct connection *conn, void **xprt_ctx)
ctx->lparams.initial_max_stream_data_bidi_remote = qcm_stream_rx_bufsz();
ctx->lparams.initial_max_stream_data_uni = qcm_stream_rx_bufsz();
/* Ensure the connection flags are set. Necessary when current XPRT is
* activated without explicit "proto qmux" configuration.
*/
conn->flags |= (CO_FL_QMUX_RECV|CO_FL_QMUX_SEND);
*xprt_ctx = ctx;
return 0;
@ -327,7 +335,7 @@ static void xprt_qmux_close(struct connection *conn, void *xprt_ctx)
if (ctx->ops_lower && ctx->ops_lower->close)
ctx->ops_lower->close(conn, ctx->ctx_lower);
conn->flags &= ~(CO_FL_QMUX_RECV|CO_FL_QMUX_SEND);
conn->flags &= ~CO_FL_WAIT_XPRT_L6;
BUG_ON(conn->xprt_ctx != ctx);
conn->xprt_ctx = ctx->ctx_lower;
@ -341,6 +349,14 @@ static int xprt_qmux_get_alpn(const struct connection *conn, void *xprt_ctx,
const char **str, int *len)
{
struct xprt_qmux_ctx *ctx = xprt_ctx;
/* Return a the default ALPN if lower layer is not able to negotiate it. */
if (!ctx->ops_lower || !ctx->ops_lower->get_alpn) {
*str = XPRT_QMUX_DEFAULT_ALPN;
*len = strlen(XPRT_QMUX_DEFAULT_ALPN);
return 1;
}
return ctx->ops_lower->get_alpn(conn, ctx->ctx_lower, str, len);
}