Compare commits

...

38 commits

Author SHA1 Message Date
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
32 changed files with 220 additions and 106 deletions

View file

@ -3329,7 +3329,7 @@ setenv <name> <value>
the configuration file sees the new value. See also "presetenv", "resetenv", the configuration file sees the new value. See also "presetenv", "resetenv",
and "unsetenv". 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 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 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 memory at a unique location. It also means that the directive is only
@ -3345,7 +3345,7 @@ shm-stats-file <name> [ EXPERIMENTAL ]
See also "guid", "guid-prefix" and "shm-stats-file-max-objects" 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 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 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 related to the maximum memory size of the shm and is used to "premap" the

View file

@ -179,6 +179,8 @@ enum {
/* below we have all handshake flags grouped into one */ /* 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_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, 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 */ CO_FL_SSL_WAIT_HS = 0x08000000, /* wait for an SSL handshake to complete */

View file

@ -114,6 +114,7 @@ int conn_reverse(struct connection *conn);
const char *conn_err_code_name(struct connection *c); const char *conn_err_code_name(struct connection *c);
const char *conn_err_code_str(struct connection *c); const char *conn_err_code_str(struct connection *c);
int xprt_add_hs(struct connection *conn); 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); 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); static inline void conn_report_term_evt(struct connection *conn, enum term_event_loc loc, unsigned char type);

View file

@ -121,8 +121,8 @@ static inline size_t array_size_or_fail(size_t m, size_t n)
{ {
size_t size; size_t size;
if (mulsz_overflow(m, n, &size)) if (unlikely(mulsz_overflow(m, n, &size)))
return ~(size_t)0; return DISGUISE(~(size_t)0);
return size; return size;
} }

View file

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

View file

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

View file

@ -1562,6 +1562,16 @@ int acme_res_certificate(struct task *task, struct acme_ctx *ctx, char **errmsg)
key = ctx->store->data->key; key = ctx->store->data->key;
ctx->store->data->key = NULL; 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 */ /* 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) if (ssl_sock_load_pem_into_ckch(ctx->store->path, hc->res.buf.area, ctx->store->data , errmsg) != 0)
goto error; 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)) if (applet_fl_test(appctx, APPCTX_FL_OUTBLK_ALLOC))
goto end; goto end;
if (!count)
goto end;
if (!appctx_get_buf(appctx, &appctx->outbuf)) { if (!appctx_get_buf(appctx, &appctx->outbuf)) {
TRACE_STATE("waiting for appctx outbuf allocation", APPLET_EV_RECV|APPLET_EV_BLK, appctx); TRACE_STATE("waiting for appctx outbuf allocation", APPLET_EV_RECV|APPLET_EV_BLK, appctx);
goto end; 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) if (flags & CO_RFL_BUF_FLUSH)
applet_fl_set(appctx, APPCTX_FL_FASTFWD); 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) if (ret)
applet_fl_clr(appctx, APPCTX_FL_OUTBLK_FULL); applet_fl_clr(appctx, APPCTX_FL_OUTBLK_FULL);

View file

@ -1818,7 +1818,7 @@ int connect_server(struct stream *s)
{ {
struct connection *cli_conn = objt_conn(strm_orig(s)); struct connection *cli_conn = objt_conn(strm_orig(s));
struct connection *srv_conn = NULL; struct connection *srv_conn = NULL;
const struct mux_proto_list *mux_proto; const struct mux_proto_list *mux_proto = NULL;
struct server *srv; struct server *srv;
struct ist name = IST_NULL; struct ist name = IST_NULL;
struct sample *name_smp; struct sample *name_smp;
@ -2139,12 +2139,10 @@ int connect_server(struct stream *s)
} }
if (may_start_mux_now) { if (may_start_mux_now) {
/* Delay QMux MUX init to let xprt_qmux handshake runs first. */ /* Delay MUX init if an XPRT handshake is required prior. */
mux_proto = conn_select_mux_be(srv_conn); mux_proto = conn_select_mux_be(srv_conn);
if (mux_proto && mux_proto->init_xprt == XPRT_QMUX) { if (mux_proto && mux_proto->init_xprt)
srv_conn->flags |= (CO_FL_QMUX_RECV|CO_FL_QMUX_SEND);
may_start_mux_now = 0; may_start_mux_now = 0;
}
} }
#if defined(USE_OPENSSL) && defined(TLSEXT_TYPE_application_layer_protocol_negotiation) #if defined(USE_OPENSSL) && defined(TLSEXT_TYPE_application_layer_protocol_negotiation)
@ -2254,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. * 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, sec_entry = get_secondary_entry(cache_tree, res,
s->txn.http->cache_secondary_hash, s->txn.http->cache_secondary_hash,
0); 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. */ /* The wrong row was added to the hot list. */
release_entry(cache_tree, res, 0); release_entry(cache_tree, res, 0);
retain_entry(sec_entry); 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, struct proxy *curpx, const struct proxy *defpx,
const char *file, int line, char **err) 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) { if (global.shm_stats_file != NULL) {
memprintf(err, "'%s' already specified.\n", args[0]); memprintf(err, "'%s' already specified.\n", args[0]);
return -1; return -1;
@ -1644,7 +1639,6 @@ static int cfg_parse_global_shm_stats_file(char **args, int section_type,
return -1; return -1;
} }
mark_tainted(TAINTED_CONFIG_EXP_KW_DECLARED);
global.shm_stats_file = strdup(args[1]); global.shm_stats_file = strdup(args[1]);
return 0; 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, struct proxy *curpx, const struct proxy *defpx,
const char *file, int line, char **err) 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) { if (shm_stats_file_max_objects != -1) {
memprintf(err, "'%s' already specified.\n", args[0]); memprintf(err, "'%s' already specified.\n", args[0]);
return -1; return -1;
@ -1668,7 +1657,6 @@ static int cfg_parse_global_shm_stats_file_max_objects(char **args, int section_
return -1; return -1;
} }
mark_tainted(TAINTED_CONFIG_EXP_KW_DECLARED);
shm_stats_file_max_objects = atoi(args[1]); shm_stats_file_max_objects = atoi(args[1]);
return 0; return 0;
} }

View file

@ -1151,8 +1151,13 @@ int cli_parse_cmdline(struct appctx *appctx)
*/ */
if (len-1 == strlen(appctx->cli_ctx.payload_pat)) { if (len-1 == strlen(appctx->cli_ctx.payload_pat)) {
if (strncmp(str, appctx->cli_ctx.payload_pat, len-1) == 0) { 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 */ /* end of payload was reached, rewind before the previous \n, if any, and replace it by a \0
b_sub(buf, strlen(appctx->cli_ctx.payload_pat) + 2); * 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'; *b_tail(buf) = '\0';
appctx->st1 &= ~APPCTX_CLI_ST1_PAYLOAD; appctx->st1 &= ~APPCTX_CLI_ST1_PAYLOAD;
} }

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 * information to create one, typically from the ALPN. If we're
* done with the handshake, attempt to create one. * 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); ret = conn_create_mux(conn, NULL);
if (ret < 0) if (ret < 0)
goto done; goto done;
@ -847,6 +847,43 @@ int xprt_add_hs(struct connection *conn)
return 0; 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 /* 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 * 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. * catching in logs). This is more compact for some debug cases.

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 *dict_insert(struct dict *d, char *s)
{ {
struct dict_entry *de; struct dict_entry *de, *tree_de;
struct ebpt_node *n; struct ebpt_node *n;
HA_RWLOCK_RDLOCK(DICT_LOCK, &d->rwlock); 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); HA_RWLOCK_WRLOCK(DICT_LOCK, &d->rwlock);
n = ebis_insert(&d->values, &de->value); n = ebis_insert(&d->values, &de->value);
HA_RWLOCK_WRUNLOCK(DICT_LOCK, &d->rwlock); tree_de = container_of(n, struct dict_entry, value);
if (n != &de->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); free_dict_entry(de);
de = container_of(n, struct dict_entry, value);
} }
return tree_de;
return de;
} }
@ -117,10 +122,11 @@ void dict_entry_unref(struct dict *d, struct dict_entry *de)
if (!de) if (!de)
return; return;
if (HA_ATOMIC_SUB_FETCH(&de->refcount, 1) != 0)
return;
HA_RWLOCK_WRLOCK(DICT_LOCK, &d->rwlock); 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); ebpt_delete(&de->value);
HA_RWLOCK_WRUNLOCK(DICT_LOCK, &d->rwlock); HA_RWLOCK_WRUNLOCK(DICT_LOCK, &d->rwlock);

View file

@ -1926,20 +1926,37 @@ static void dump_registered_keywords(void)
/* Generate a random cluster-secret in case the setting is not provided in the /* 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 * 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() static void generate_random_cluster_secret()
{ {
/* used as a default random cluster-secret if none defined. */ /* 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. */ /* The caller must not overwrite an already defined secret. */
BUG_ON(cluster_secret_isset); 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)); memcpy(global.cluster_secret, &rand, sizeof(rand));
rand = ha_random64();
memcpy(global.cluster_secret + sizeof(rand), &rand, sizeof(rand));
cluster_secret_isset = 1; cluster_secret_isset = 1;
} }

View file

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

View file

@ -3319,7 +3319,7 @@ struct ist *build_log_header(struct log_header hdr, size_t *nbelem)
break; break;
} }
else if (metadata && metadata[LOG_META_TIME].len >= LOG_ISOTIME_MINLEN) { else if (metadata && metadata[LOG_META_TIME].len >= LOG_ISOTIME_MINLEN) {
int month; uint month;
char *timestamp = metadata[LOG_META_TIME].ptr; char *timestamp = metadata[LOG_META_TIME].ptr;
/* iso time always begins like this: '1970-01-01T00:00:00' */ /* 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; return;
fac_level = 10*fac_level + (*p - '0'); fac_level = 10*fac_level + (*p - '0');
p++; p++;
if ((p - buf) > buflen) if ((p - buf) >= buflen)
return; 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)) { SMP_VAL_FE_LOG_END, &errmsg)) {
ha_alert("Parsing [%s:%d]: failed to parse logformat: %s.\n", ha_alert("Parsing [%s:%d]: failed to parse logformat: %s.\n",
file, linenum, errmsg); file, linenum, errmsg);
lf_expr_deinit(target_lf);
err_code |= ERR_ALERT | ERR_FATAL; err_code |= ERR_ALERT | ERR_FATAL;
goto out; goto out;
} }

View file

@ -6236,6 +6236,13 @@ next_frame:
/* Skip StreamDep and weight for now (we don't support PRIORITY) */ /* Skip StreamDep and weight for now (we don't support PRIORITY) */
if (h2c->dff & H2_F_HEADERS_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) { if (read_n32(hdrs) == h2c->dsi) {
/* RFC7540#5.3.1 : stream dep may not depend on itself */ /* RFC7540#5.3.1 : stream dep may not depend on itself */
h2c_report_glitch(h2c, 1, "PRIORITY frame referencing itself"); h2c_report_glitch(h2c, 1, "PRIORITY frame referencing itself");
@ -6245,13 +6252,6 @@ next_frame:
goto fail; 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 hdrs += 5; // stream dep = 4, weight = 1
flen -= 5; flen -= 5;
} }

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 */ /* kind1 = NOP and is a single byte, others have a length field */
if (smp->data.u.str.area[next] == 1) if (smp->data.u.str.area[next] == 1)
next++; next++;
else if (next + 1 < len) else if (next + 1 < len && smp->data.u.str.area[next + 1] > 1)
next += smp->data.u.str.area[next + 1]; next += (uchar)smp->data.u.str.area[next + 1];
else else
break; break;
if (smp->data.u.str.area[curr] == opt && next <= len) 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 */ /* kind1 = NOP and is a single byte, others have a length field */
if (smp->data.u.str.area[ofs] == 1) if (smp->data.u.str.area[ofs] == 1)
ofs++; 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]; ofs += (uchar)smp->data.u.str.area[ofs + 1];
else else
break; 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 */ /* kind1 = NOP and is a single byte, others have a length field */
if (smp->data.u.str.area[ofs] == 1) if (smp->data.u.str.area[ofs] == 1)
next = 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]; next = ofs + (uchar)smp->data.u.str.area[ofs + 1];
else else
break; break;

View file

@ -69,7 +69,7 @@
#include <haproxy/uri_auth.h> #include <haproxy/uri_auth.h>
/* Lock to ensure multiple backends deletion concurrently is safe */ /* 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 */ int listeners; /* # of proxy listeners, set by cfgparse */
struct proxy *proxies_list = NULL; /* list of main proxies */ struct proxy *proxies_list = NULL; /* list of main proxies */

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; 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); 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) { else if (frm.type == QUIC_FT_MAX_DATA) {
struct qf_max_data *md_frm = &frm.max_data; struct qf_max_data *md_frm = &frm.max_data;
qcc_recv_max_data(qcc, md_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; struct qf_max_streams *ms_frm = &frm.max_streams_bidi;
qcc_recv_max_streams(qcc, ms_frm->max_streams, 1); 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 || else if (frm.type == QUIC_FT_DATA_BLOCKED ||
frm.type == QUIC_FT_STREAM_DATA_BLOCKED || frm.type == QUIC_FT_STREAM_DATA_BLOCKED ||
frm.type == QUIC_FT_STREAMS_BLOCKED_BIDI || frm.type == QUIC_FT_STREAMS_BLOCKED_BIDI ||
frm.type == QUIC_FT_STREAMS_BLOCKED_UNI) { frm.type == QUIC_FT_STREAMS_BLOCKED_UNI) {
/* TODO */ /* 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 { else {
/* qmux_is_frm_valid() must prevent this */
ABORT_NOW(); 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)]; tree = &quic_fe_cid_trees[quic_cid_tree_idx(&derive_cid)];
HA_RWLOCK_RDLOCK(QC_CID_LOCK, &tree->lock); 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) { if (node) {
conn_id = ebmb_entry(node, struct quic_connection_id, node); conn_id = ebmb_entry(node, struct quic_connection_id, node);
cid_tid = HA_ATOMIC_LOAD(&conn_id->tid); cid_tid = HA_ATOMIC_LOAD(&conn_id->tid);

View file

@ -444,7 +444,7 @@ INITCALL0(STG_REGISTER, regex_register_build_options);
#ifdef USE_PCRE2 #ifdef USE_PCRE2
static int init_pcre2_per_thread(void) 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) { if (!local_pcre2_match) {
ha_alert("Failed to allocate PCRE2 match data context for thread %u.\n", tid); ha_alert("Failed to allocate PCRE2 match data context for thread %u.\n", tid);
return 0; return 0;

View file

@ -226,7 +226,7 @@ struct show_resolvers_ctx {
}; };
/* returns the currently accepted address families as a combination of /* 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, * the IPv6 status to sock_inet6_seems_reachable if RSLV_AUTO_FAMILY is set,
* otherwise returns the relevant bits of resolv_accept_families. * otherwise returns the relevant bits of resolv_accept_families.
*/ */
@ -509,7 +509,7 @@ resolv_run_resolution(struct resolv_resolution *resolution)
return 0; return 0;
/* Check if a resolution has already been started for this server return /* 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) if (resolution->step != RSLV_STEP_NONE)
return 0; return 0;
@ -1236,8 +1236,7 @@ static int resolv_validate_dns_response(unsigned char *resp, unsigned char *bufe
if (reader + 4 > bufend) if (reader + 4 > bufend)
goto invalid_resp; goto invalid_resp;
answer_record->ttl = reader[0] * 16777216 + reader[1] * 65536 answer_record->ttl = read_n32(reader);
+ reader[2] * 256 + reader[3];
reader += 4; reader += 4;
/* Now reading data len */ /* Now reading data len */
@ -1498,8 +1497,7 @@ static int resolv_validate_dns_response(unsigned char *resp, unsigned char *bufe
if (reader + 4 > bufend) if (reader + 4 > bufend)
goto invalid_resp; goto invalid_resp;
answer_record->ttl = reader[0] * 16777216 + reader[1] * 65536 answer_record->ttl = read_n32(reader);
+ reader[2] * 256 + reader[3];
reader += 4; reader += 4;
/* Now reading data len */ /* Now reading data len */
@ -1599,7 +1597,6 @@ static int resolv_validate_dns_response(unsigned char *resp, unsigned char *bufe
tmp_record->ar_item == NULL && tmp_record->ar_item == NULL &&
memcmp(tmp_record->data.target, answer_record->name, tmp_record->data_len) == 0) { memcmp(tmp_record->data.target, answer_record->name, tmp_record->data_len) == 0) {
/* Always use the received additional record to refresh info */ /* 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; tmp_record->ar_item = answer_record;
answer_record = NULL; answer_record = NULL;
break; break;
@ -1855,7 +1852,15 @@ int resolv_dn_label_to_str(const char *dn, int dn_len, char *str, int str_len)
ptr = str; ptr = str;
for (i = 0; i < dn_len; ++i) { 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) if (i)
*ptr++ = '.'; *ptr++ = '.';
/* copy the string at i+1 to lower case */ /* copy the string at i+1 to lower case */

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) 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; 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; const int last = args[2].data.sint ? smp->data.u.str.data - chunk_size + 1 : smp->data.u.str.data;
int i; int i;
int max_size; size_t max_size;
int ptr = 0; int ptr = 0;
unsigned char c; unsigned char c;
@ -2163,7 +2163,9 @@ static int sample_conv_be2hex(const struct arg *args, struct sample *smp, void *
trash->data = 0; trash->data = 0;
if (args[0].data.str.data == 0 && args[2].data.sint == 0) if (args[0].data.str.data == 0 && args[2].data.sint == 0)
chunk_size = smp->data.u.str.data; 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) { while (ptr < last && trash->data <= max_size) {
if (ptr) { if (ptr) {

View file

@ -241,14 +241,17 @@ int session_accept_fd(struct connection *cli_conn)
if (l->bind_conf->options & BC_O_ACC_CIP) if (l->bind_conf->options & BC_O_ACC_CIP)
cli_conn->flags |= CO_FL_ACCEPT_CIP; cli_conn->flags |= CO_FL_ACCEPT_CIP;
if (l->bind_conf->mux_proto && l->bind_conf->mux_proto->init_xprt == XPRT_QMUX)
cli_conn->flags |= (CO_FL_QMUX_RECV|CO_FL_QMUX_SEND);
/* Add the handshake pseudo-XPRT */ /* Add the handshake pseudo-XPRT */
if (cli_conn->flags & (CO_FL_ACCEPT_PROXY | CO_FL_ACCEPT_CIP)) { if (cli_conn->flags & (CO_FL_ACCEPT_PROXY | CO_FL_ACCEPT_CIP)) {
if (xprt_add_hs(cli_conn) != 0) if (xprt_add_hs(cli_conn) != 0)
goto out_free_conn; 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. */ /* Reversed conns already have an assigned session, do not recreate it. */
@ -351,7 +354,7 @@ int session_accept_fd(struct connection *cli_conn)
* v | | | * v | | |
* conn -- owner ---> task <-----+ * 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 timeout;
int clt_tmt = p->timeout.client; int clt_tmt = p->timeout.client;
int hs_tmt = p->timeout.client_hs; int hs_tmt = p->timeout.client_hs;

View file

@ -356,8 +356,10 @@ int ssl_sock_generate_certificate(const char *servername, struct bind_conf *bind
ssl_ctx = (SSL_CTX *)lru->data; ssl_ctx = (SSL_CTX *)lru->data;
if (!ssl_ctx && lru) { if (!ssl_ctx && lru) {
ssl_ctx = ssl_sock_do_create_cert(servername, bind_conf, ssl); 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; goto error;
}
lru64_commit(lru, ssl_ctx, cacert, 0, (void (*)(void *))SSL_CTX_free); lru64_commit(lru, ssl_ctx, cacert, 0, (void (*)(void *))SSL_CTX_free);
} }
SSL_set_SSL_CTX(ssl, ssl_ctx); 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; int ret = 1;
#ifdef HAVE_ASN1_TIME_TO_TM #ifdef HAVE_ASN1_TIME_TO_TM
struct tm nextupd_tm = {0}; struct tm nextupd_tm = {0};
#else
long expire = 0;
#endif #endif
resp = d2i_OCSP_RESPONSE(NULL, (const unsigned char **)&p, 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; ocsp->expire = my_timegm(&nextupd_tm) - OCSP_MAX_RESPONSE_TIME_SKEW;
#else #else
ocsp->expire = asn1_generalizedtime_to_epoch(nextupd) - OCSP_MAX_RESPONSE_TIME_SKEW; expire = asn1_generalizedtime_to_epoch(nextupd) - OCSP_MAX_RESPONSE_TIME_SKEW;
if (ocsp->expire < 0) { if (expire < 0) {
memprintf(err, "OCSP single response: Invalid \"Next Update\" time"); memprintf(err, "OCSP single response: Invalid \"Next Update\" time");
goto out; goto out;
} }
ocsp->expire = expire;
#endif #endif
if (ocsp->expire < date.tv_sec) { if (ocsp->expire < date.tv_sec) {

View file

@ -6973,12 +6973,8 @@ struct task *ssl_sock_io_cb(struct task *t, void *context, unsigned int state)
mux = !conn_is_back(conn) ? mux = !conn_is_back(conn) ?
conn_select_mux_fe(conn) : conn_select_mux_be(conn); conn_select_mux_fe(conn) : conn_select_mux_be(conn);
if (ctx->conn->flags & (CO_FL_QMUX_RECV|CO_FL_QMUX_SEND) || if (mux->init_xprt) {
mux->init_xprt == XPRT_QMUX) { ret = xprt_add_l6hs(conn, mux->init_xprt);
const struct xprt_ops *ops = xprt_get(XPRT_QMUX);
void *xprt_ctx_hs = NULL;
ret = ops->init(conn, &xprt_ctx_hs);
/* Frontend conn must be freed in case of XPRT init failure. */ /* Frontend conn must be freed in case of XPRT init failure. */
if (ret) { if (ret) {
if (!conn_is_back(conn)) { if (!conn_is_back(conn)) {
@ -6990,15 +6986,7 @@ struct task *ssl_sock_io_cb(struct task *t, void *context, unsigned int state)
goto leave; goto leave;
} }
ret = ops->add_xprt(conn, xprt_ctx_hs, ret = conn_xprt_start(conn);
conn->xprt_ctx, conn->xprt, NULL, NULL);
BUG_ON(ret); /* xprt_qmux add_xprt always succeeds */
conn->xprt = ops;
conn->xprt_ctx = xprt_ctx_hs;
ret = conn->xprt->start(conn, xprt_ctx_hs);
BUG_ON(ret);
} }
else { else {
/* TODO MUX selection already performs by conn_select_mux_fe/be(). /* TODO MUX selection already performs by conn_select_mux_fe/be().

View file

@ -834,6 +834,8 @@ enum tcpcheck_eval_ret tcpcheck_spop_expect_hello(struct check *check, struct tc
goto invalid_frame; goto invalid_frame;
if (decode_varint(&ptr, end, &sz) == -1) if (decode_varint(&ptr, end, &sz) == -1)
goto invalid_frame; goto invalid_frame;
if (sz >= SPOP_ERR_ENTRIES)
sz = SPOP_ERR_UNKNOWN;
check->code = sz; 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 *sc = NULL; /* maxconn */
const char *err = NULL; /* first error to report */ const char *err = NULL; /* first error to report */
const char *wrn = NULL; /* first warning to report */ const char *wrn = NULL; /* first warning to report */
char *cmd, *p; char *cmd, *p, *end;
TRACE_ENTER(CHK_EV_TCPCHK_EXP, check); 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); 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++; p++;
if (!*p) { if (!*p || p == end) {
if (!last_read) if (!last_read)
goto wait_more_data; goto wait_more_data;

View file

@ -12,6 +12,9 @@
#include <haproxy/quic_frame.h> #include <haproxy/quic_frame.h>
#include <haproxy/quic_tp-t.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 xprt_qmux_ctx {
struct connection *conn; struct connection *conn;
struct wait_event wait_event; struct wait_event wait_event;
@ -207,7 +210,7 @@ struct task *xprt_qmux_io_cb(struct task *t, void *context, unsigned int state)
out: out:
if ((conn->flags & CO_FL_ERROR) || 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. */ /* XPRT should be unsubscribed when transfer done or on error. */
BUG_ON(ctx->wait_event.events); BUG_ON(ctx->wait_event.events);
@ -332,7 +335,7 @@ static void xprt_qmux_close(struct connection *conn, void *xprt_ctx)
if (ctx->ops_lower && ctx->ops_lower->close) if (ctx->ops_lower && ctx->ops_lower->close)
ctx->ops_lower->close(conn, ctx->ctx_lower); 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); BUG_ON(conn->xprt_ctx != ctx);
conn->xprt_ctx = ctx->ctx_lower; conn->xprt_ctx = ctx->ctx_lower;
@ -346,6 +349,14 @@ static int xprt_qmux_get_alpn(const struct connection *conn, void *xprt_ctx,
const char **str, int *len) const char **str, int *len)
{ {
struct xprt_qmux_ctx *ctx = xprt_ctx; 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); return ctx->ops_lower->get_alpn(conn, ctx->ctx_lower, str, len);
} }