]> git.ipfire.org Git - thirdparty/haproxy.git/log
thirdparty/haproxy.git
22 months agoMINOR: pools: use EBO to wait for unlock during pool_flush()
Willy Tarreau [Thu, 17 Aug 2023 07:04:35 +0000 (09:04 +0200)] 
MINOR: pools: use EBO to wait for unlock during pool_flush()

pool_flush() could become a source of contention on the pool's free list
if there are many competing thread using that pool. Let's make sure we
use EBO and not just a simple CPU relaxation there, to avoid disturbing
them.

22 months agoMINOR: atomic: make sure to always relax after a failed CAS
Willy Tarreau [Thu, 17 Aug 2023 06:59:15 +0000 (08:59 +0200)] 
MINOR: atomic: make sure to always relax after a failed CAS

There were a few places left where we forgot to call __ha_cpu_relax()
after a failed CAS, in the HA_ATOMIC_UPDATE_{MIN,MAX} macros, and in
a few sync_* API macros (the same as above plus HA_ATOMIC_CAS and
HA_ATOMIC_XCHG). Let's add them now.

This could have been a cause of contention, particularly with
process_stream() calling stream_update_time_stats() which uses 8 of them
in a call (4 for the server, 4 for the proxy). This may be a possible
explanation for the high CPU consumption reported in GH issue #2251.

This should be backported at least to 2.6 as it's harmless.

22 months agoMINOR: threads: inline the wait function for pthread_rwlock emulation
Willy Tarreau [Wed, 16 Aug 2023 20:51:37 +0000 (22:51 +0200)] 
MINOR: threads: inline the wait function for pthread_rwlock emulation

When using pthread_rwlock emulation, contention is reported on
pl_wait_unlock_long(). This is really not convenient to analyse what is
happening. Now plock supports inlining the wait call for just the lorw
functions by enabling PLOCK_LORW_INLINE_WAIT. Let's do this so that now
the wait time will be precisely reported as either pthread_rwlock_rdlock()
or pthread_rwlock_wrlock() depending on the contended function, but no
more on pl_wait_unlock_long(), which will still be reported for all
other locks.

22 months agoIMPORT: lorw: support inlining the wait call
Willy Tarreau [Wed, 16 Aug 2023 20:40:05 +0000 (22:40 +0200)] 
IMPORT: lorw: support inlining the wait call

Now when PLOCK_LORW_INLINE_WAIT is defined, the pl_wait_unlock_long()
calls in pl_lorw_rdlock() and pl_lorw_wrlock() will be inlined so that
all the CPU time is accounted for in the calling function.

This is plock upstream commit c993f81d581732a6eb8fe3033f21970420d21e5e.

22 months agoIMPORT: plock: always expose the inline version of the lock wait function
Willy Tarreau [Wed, 16 Aug 2023 20:29:45 +0000 (22:29 +0200)] 
IMPORT: plock: always expose the inline version of the lock wait function

Doing so will allow to expose the time spent in certain highly
contended functions, which can be desirable for more accurate CPU
profiling. For example this could be done in locking functions that
are already not inlined so that they are the ones being reported as
those consuming the CPU instead of just pl_wait_unlock_long().

This is plock upstream commit 7505c2e2c8c4aa0ab8f52a2288e1334ae6412be4.

22 months agoIMPORT: plock: also support inlining the int code
Willy Tarreau [Wed, 16 Aug 2023 20:25:34 +0000 (22:25 +0200)] 
IMPORT: plock: also support inlining the int code

Commit 9db830b ("plock: support inlining exponential backoff code")
added an option to support inlining of the wait code for longs but
forgot to do it for ints. Let's do it now.

This is plock upstream commit b1f9f0d252fa40577d11cfb2bc0a809d6960a297.

22 months agoDEV: makefile: fix POSIX compatibility for "range" target
Aurelien DARRAGON [Wed, 16 Aug 2023 16:16:16 +0000 (18:16 +0200)] 
DEV: makefile: fix POSIX compatibility for "range" target

make "range" which was introduced with 06d34d4 ("DEV: makefile: add a
new "range" target to iteratively build all commits") does not work with
POSIX shells (namely: bourne shell), and will fail with this kind of
errors:

   |/bin/sh: 6: Syntax error: "(" unexpected (expecting ")")
   |make: *** [Makefile:1226: range] Error 2

This is because arrays and arithmetic expressions which are used for the
"range" target are not supported by sh (unlike bash and other "modern"
interpreters).

However the make "all" target already complies with POSIX, so in this
commit we try to make "range" target POSIX compliant to ensure that the
makefile works as expected on systems where make uses /bin/sh as default
intepreter and where /bin/sh points to POSIX shell.

22 months agoBUILD: Makefile: realigned USE_* options in make help
William Lallemand [Wed, 16 Aug 2023 22:03:01 +0000 (00:03 +0200)] 
BUILD: Makefile: realigned USE_* options in make help

Realigned the USE_* options of `make help` because of the length of
USE_QUIC_OPENSSL_COMPAT.

No backport needed.

22 months agoBUILD: Makefile: add USE_QUIC_OPENSSL_COMPAT to make help
William Lallemand [Wed, 16 Aug 2023 22:01:27 +0000 (00:01 +0200)] 
BUILD: Makefile: add USE_QUIC_OPENSSL_COMPAT to make help

Add the missing USE_QUIC_OPENSSL_COMPAT option to `make help`.

No backport needed.

22 months agoBUILD: Makefile: add the USE_QUIC option to make help
William Lallemand [Wed, 16 Aug 2023 21:41:15 +0000 (23:41 +0200)] 
BUILD: Makefile: add the USE_QUIC option to make help

Add the missing "USE_QUIC" option to `make help`.

Must be backported as far as 2.4.

22 months agoDOC: jwt: Add explicit list of supported algorithms
Remi Tricot-Le Breton [Thu, 10 Aug 2023 14:11:27 +0000 (16:11 +0200)] 
DOC: jwt: Add explicit list of supported algorithms

Add explicit list of algorithms supported by the jwt_verify converter.

22 months agoREGTESTS: Do not use REQUIRE_VERSION for HAProxy 2.5+ (3)
Tim Duesterhus [Mon, 7 Aug 2023 13:46:23 +0000 (15:46 +0200)] 
REGTESTS: Do not use REQUIRE_VERSION for HAProxy 2.5+ (3)

Introduced in:

424981cde REGTEST: add ifnone-forwardfor test
b015b3eb1 REGTEST: add RFC7239 forwarded header tests

see also:

fbbbc33df REGTESTS: Do not use REQUIRE_VERSION for HAProxy 2.5+

22 months agoSCRIPTS: git-show-backports: automatic ref and base detection with -m
Willy Tarreau [Mon, 14 Aug 2023 11:03:46 +0000 (13:03 +0200)] 
SCRIPTS: git-show-backports: automatic ref and base detection with -m

When running with -m (check for missing backports) we often have to fill
lots of information that can be determined automatically the vast majority
of the time:
  - restart point (last cherry-picked ID from one of the last commits)
  - current branch (HEAD)
  - reference branch (the one that contains most of the last commits)

These elements are not that hard to determine, so let's make sure we
can fall back to them when running in missing mode.

The reference branch is guessed by looking at the upstream branch that
most frequently contains some of the last 10 commits. It can be inaccurate
if multiple branches exist with these commits, or when upstream changes
due to a non-LTS branch disappearing in the middle of the series, in which
case passing "-r" will help. But most of the time it works OK. It also gives
precedence to local branches over remote ones for such choices. A test in
2.4 at commit 793a4b520 correctly shows 2.6/master as the upstream despite
2.5 having been used for the early ones of the tag.

For the restart point, we assume that the most recent commit that was
backported serves as a reference (and not the most recently backported
commit). This means that the usual case where an old commit was found
to be missing will not fool the analysis. Commits are inspected from
2 commits before the last tag, and reordered from the parent's tree
to see which one is the last one.

With this, it's sufficient to issue "git-show-backports -q -m" to get
the list of backports from the upstream branch, restarting from the
last backported one.

22 months agoDOC: typo: fix sc-set-gpt references
Johannes Naab [Thu, 10 Aug 2023 12:10:37 +0000 (14:10 +0200)] 
DOC: typo: fix sc-set-gpt references

Only sc-inc-gpc and sc-set-gpt do exist. The mix-up sc-inc-gpt crept in
in 71d189219 (DOC: config: Rework and uniformize how TCP/HTTP rules are
documented, 2021-10-14) and got copied in a92480462 (MINOR: http-rules:
Add missing actions in http-after-response ruleset, 2023-01-05).

22 months agoBUG/MINOR: stktable: allow sc-add-gpc from tcp-request connection
Aurelien DARRAGON [Wed, 9 Aug 2023 15:39:29 +0000 (17:39 +0200)] 
BUG/MINOR: stktable: allow sc-add-gpc from tcp-request connection

Following the previous commit's logic, we enable the use of sc-add-gpc
from tcp-request connection since it was probably forgotten in the first
place for sc-set-gpt0, and since sc-add-gpc was inspired from it, it also
lacks its.

As sc-add-gpc was implemented in 5a72d03a58 ("MINOR: stick-table: implement
the sc-add-gpc() action"), this should only be backported to 2.8

22 months agoBUG/MINOR: stktable: allow sc-set-gpt(0) from tcp-request connection
Aurelien DARRAGON [Wed, 9 Aug 2023 15:23:32 +0000 (17:23 +0200)] 
BUG/MINOR: stktable: allow sc-set-gpt(0) from tcp-request connection

Both the documentation and original developer intents seem to suggest
that sc-set-gpt/sc-set-gpt0 actions should be available from
tcp-request connection.

Yet because it was probably forgotten when expr support was added to
sc-set-gpt0 in 0d7712dff0 ("MINOR: stick-table: allow sc-set-gpt0 to
set value from an expression") it doesn't work and will report this
kind of errors:
 "internal error, unexpected rule->from=0, please report this bug!"

Fixing the code to comply with the documentation and the expected
behavior.

This must be backported to every stable versions.

[for < 2.5, as only sc-set-gpt0 existed back then, the patch must be
manually applied to skip irrelevant parts]

22 months agoDEV: flags/show-sess-to-flags: properly decode fd.state
Willy Tarreau [Mon, 14 Aug 2023 06:47:43 +0000 (08:47 +0200)] 
DEV: flags/show-sess-to-flags: properly decode fd.state

fd.state is reported without the "0x" prefix in show sess, let's support
this during decoding.

This may be backported to all versions supporting this utility.

23 months ago[RELEASE] Released version 2.9-dev3 v2.9-dev3
Willy Tarreau [Sat, 12 Aug 2023 17:59:27 +0000 (19:59 +0200)] 
[RELEASE] Released version 2.9-dev3

Released version 2.9-dev3 with the following main changes :
    - BUG/MINOR: ssl: OCSP callback only registered for first SSL_CTX
    - BUG/MEDIUM: h3: Properly report a C-L header was found to the HTX start-line
    - MINOR: sample: add pid sample
    - MINOR: sample: implement act_conn sample fetch
    - MINOR: sample: accept_date / request_date return %Ts / %tr timestamp values
    - MEDIUM: sample: implement us and ms variant of utime and ltime
    - BUG/MINOR: sample: check alloc_trash_chunk() in conv_time_common()
    - DOC: configuration: describe Td in Timing events
    - MINOR: sample: implement the T* timer tags from the log-format as fetches
    - DOC: configuration: add sample fetches for timing events
    - BUG/MINOR: quic: Possible crash when acknowledging Initial v2 packets
    - MINOR: quic: Export QUIC traces code from quic_conn.c
    - MINOR: quic: Export QUIC CLI code from quic_conn.c
    - MINOR: quic: Move TLS related code to quic_tls.c
    - MINOR: quic: Add new "QUIC over SSL" C module.
    - MINOR: quic: Add a new quic_ack.c C module for QUIC acknowledgements
    - CLEANUP: quic: Defined but no more used function (quic_get_tls_enc_levels())
    - MINOR: quic: Split QUIC connection code into three parts
    - CLEANUP: quic: quic_conn struct cleanup
    - MINOR: quic; Move the QUIC frame pool to its proper location
    - BUG/MINOR: chunk: fix chunk_appendf() to not write a zero if buffer is full
    - BUG/MEDIUM: h3: Be sure to handle fin bit on the last DATA frame
    - DOC: configuration: rework the custom log format table
    - BUG/MINOR: quic+openssl_compat: Non initialized TLS encryption levels
    - CLEANUP: acl: remove cache_idx from acl struct
    - REORG: cfgparse: extract curproxy as a global variable
    - MINOR: acl: add acl() sample fetch
    - BUILD: cfgparse: keep a single "curproxy"
    - BUG/MEDIUM: bwlim: Reset analyse expiration date when then channel analyse ends
    - MEDIUM: stream: Reset response analyse expiration date if there is no analyzer
    - BUG/MINOR: htx/mux-h1: Properly handle bodyless responses when splicing is used
    - BUG/MEDIUM: quic: consume contig space on requeue datagram
    - BUG/MINOR: http-client: Don't forget to commit changes on HTX message
    - CLEANUP: stconn: Move comment about sedesc fields on the field line
    - REGTESTS: http: Create a dedicated script to test spliced bodyless responses
    - REGTESTS: Test SPLICE feature is enabled to execute script about splicing
    - BUG/MINOR: quic: reappend rxbuf buffer on fake dgram alloc error
    - BUILD: quic: fix wrong potential NULL dereference
    - MINOR: h3: abort request if not completed before full response
    - BUG/MAJOR: http-ana: Get a fresh trash buffer for each header value replacement
    - CLEANUP: quic: Remove quic_path_room().
    - MINOR: quic: Amplification limit handling sanitization.
    - MINOR: quic: Move some counters from [rt]x quic_conn anonymous struct
    - MEDIUM: quic: Send CONNECTION_CLOSE packets from a dedicated buffer.
    - MINOR: quic: Use a pool for the connection ID tree.
    - MEDIUM: quic: Allow the quic_conn memory to be asap released.
    - MINOR: quic: Release asap quic_conn memory (application level)
    - MINOR: quic: Release asap quic_conn memory from ->close() xprt callback.
    - MINOR: quic: Warning for OpenSSL wrapper QUIC bindings without "limited-quic"
    - REORG: http: move has_forbidden_char() from h2.c to http.h
    - BUG/MAJOR: h3: reject header values containing invalid chars
    - MINOR: mux-h2/traces: also suggest invalid header upon parsing error
    - MINOR: ist: add new function ist_find_range() to find a character range
    - MINOR: http: add new function http_path_has_forbidden_char()
    - MINOR: h2: pass accept-invalid-http-request down the request parser
    - REGTESTS: http-rules: add accept-invalid-http-request for normalize-uri tests
    - BUG/MINOR: h1: do not accept '#' as part of the URI component
    - BUG/MINOR: h2: reject more chars from the :path pseudo header
    - BUG/MINOR: h3: reject more chars from the :path pseudo header
    - REGTESTS: http-rules: verify that we block '#' by default for normalize-uri
    - DOC: clarify the handling of URL fragments in requests
    - BUG/MAJOR: http: reject any empty content-length header value
    - BUG/MINOR: http: skip leading zeroes in content-length values
    - BUG/MEDIUM: mux-h1: fix incorrect state checking in h1_process_mux()
    - BUG/MEDIUM: mux-h1: do not forget EOH even when no header is sent
    - BUILD: mux-h1: shut a build warning on clang from previous commit
    - DEV: makefile: add a new "range" target to iteratively build all commits
    - CI: do not use "groupinstall" for Fedora Rawhide builds
    - CI: get rid of travis-ci wrapper for Coverity scan
    - BUG/MINOR: quic: mux started when releasing quic_conn
    - BUG/MINOR: quic: Possible crash in quic_cc_conn_io_cb() traces.
    - MINOR: quic: Add a trace for QUIC conn fd ready for receive
    - BUG/MINOR: quic: Possible crash when issuing "show fd/sess" CLI commands
    - BUG/MINOR: quic: Missing tasklet (quic_cc_conn_io_cb) memory release (leak)
    - BUG/MEDIUM: quic: fix tasklet_wakeup loop on connection closing
    - BUG/MINOR: hlua: fix invalid use of lua_pop on error paths
    - MINOR: hlua: add hlua_stream_ctx_prepare helper function
    - BUG/MEDIUM: hlua: streams don't support mixing lua-load with lua-load-per-thread
    - MAJOR: threads/plock: update the embedded library again
    - MINOR: stick-table: move the task_queue() call outside of the lock
    - MINOR: stick-table: move the task_wakeup() call outside of the lock
    - MEDIUM: stick-table: change the ref_cnt atomically
    - MINOR: stick-table: better organize the struct stktable
    - MEDIUM: peers: update ->commitupdate out of the lock using a CAS
    - MEDIUM: peers: drop then re-acquire the wrlock in peer_send_teachmsgs()
    - MEDIUM: peers: only read-lock peer_send_teachmsgs()
    - MEDIUM: stick-table: use a distinct lock for the updates tree
    - MEDIUM: stick-table: touch updates under an upgradable read lock
    - MEDIUM: peers: drop the stick-table lock before entering peer_send_teachmsgs()
    - MINOR: stick-table: move the update lock into its own cache line
    - CLEANUP: stick-table: slightly reorder the stktable struct
    - BUILD: defaults: use __WORDSIZE not LONGBITS for MAX_THREADS_PER_GROUP
    - MINOR: tools: make ptr_hash() support 0-bit outputs
    - MINOR: tools: improve ptr hash distribution on 64 bits
    - OPTIM: tools: improve hash distribution using a better prime seed
    - OPTIM: pools: use exponential back-off on shared pool allocation/release
    - OPTIM: pools: make pool_get_from_os() / pool_put_to_os() not update ->allocated
    - MINOR: pools: introduce the use of multiple buckets
    - MEDIUM: pools: spread the allocated counter over a few buckets
    - MEDIUM: pools: move the used counter over a few buckets
    - MEDIUM: pools: move the needed_avg counter over a few buckets
    - MINOR: pools: move the failed allocation counter over a few buckets
    - MAJOR: pools: move the shared pool's free_list over multiple buckets
    - MINOR: pools: make pool_evict_last_items() use pool_put_to_os_no_dec()
    - BUILD: pools: fix build error on clang with inline vs forceinline

23 months agoBUILD: pools: fix build error on clang with inline vs forceinline
Willy Tarreau [Sat, 12 Aug 2023 17:58:17 +0000 (19:58 +0200)] 
BUILD: pools: fix build error on clang with inline vs forceinline

clang is more picky than gcc regarding duplicate "inline". The functions
declared with "forceinline" don't need to have "inline" since it's already
in the macro.

23 months agoMINOR: pools: make pool_evict_last_items() use pool_put_to_os_no_dec()
Willy Tarreau [Sat, 12 Aug 2023 10:34:09 +0000 (12:34 +0200)] 
MINOR: pools: make pool_evict_last_items() use pool_put_to_os_no_dec()

The bucket is already known, no need to calculate it again. Let's just
include the lower level functions.

23 months agoMAJOR: pools: move the shared pool's free_list over multiple buckets
Willy Tarreau [Mon, 24 Jul 2023 15:02:22 +0000 (17:02 +0200)] 
MAJOR: pools: move the shared pool's free_list over multiple buckets

This aims at further reducing the contention on the free_list when using
global pools. The free_list pointer now appears for each bucket, and both
the alloc and the release code skip to a next bucket when ending on a
contended entry. The default entry used for allocations and releases
depend on the thread ID so that locality is preserved as much as possible
under low contention.

It would be nice to improve the situation to make sure that releases to
the shared pools doesn't consider the first entry's pointer but only an
argument that would be passed and that would correspond to the bucket in
the thread's cache. This would reduce computations and make sure that the
shared cache only contains items whose pointers match the same bucket.
This was not yet done. One possibility could be to keep the same splitting
in the local cache.

With this change, an h2load test with 5 * 160 conns & 40 streams on 80
threads that was limited to 368k RPS with the shared cache jumped to
3.5M RPS for 8 buckets, 4M RPS for 16 buckets, 4.7M RPS for 32 buckets
and 5.5M RPS for 64 buckets.

23 months agoMINOR: pools: move the failed allocation counter over a few buckets
Willy Tarreau [Mon, 24 Jul 2023 14:38:09 +0000 (16:38 +0200)] 
MINOR: pools: move the failed allocation counter over a few buckets

The failed allocation counter cannot depend on a pointer, but since it's
a perpetually increasing counter and not a gauge, we don't care where
it's incremented. Thus instead we're hashing on the TID. There's no
contention there anyway, but it's better not to waste the room in
the pool's heads and to move that with the other counters.

23 months agoMEDIUM: pools: move the needed_avg counter over a few buckets
Willy Tarreau [Mon, 24 Jul 2023 14:18:25 +0000 (16:18 +0200)] 
MEDIUM: pools: move the needed_avg counter over a few buckets

That's the same principle as for ->allocated and ->used. Here we return
the summ of the raw values, so the result still needs to be fed to
swrate_avg(). It also means that we now use the local ->used instead
of the global one for the calculations and do not need to call pool_used()
anymore on fast paths. The number of samples should likely be divided by
the number of buckets, but that's not done yet (better observe first).

A function pool_needed_avg() was added to report aggregated values for
the "show pools" command.

With this change, an h2load made of 5 * 160 conn * 40 streams on 80
threads raised from 1.5M RPS to 6.7M RPS.

23 months agoMEDIUM: pools: move the used counter over a few buckets
Willy Tarreau [Mon, 24 Jul 2023 14:12:18 +0000 (16:12 +0200)] 
MEDIUM: pools: move the used counter over a few buckets

That's the same principle as for ->allocated. The small difference here
is that it's no longer possible to decrement ->used in batches when
releasing clusters from the cache to the shared cache, so the counter
has to be decremented for each of them. But as it provides less
contention and it's done only during forced eviction, it shouldn't be
a problem.

A function "pool_used()" was added to return the sum of the entries.
It's used by pool_alloc_nocache() and pool_free_nocache() which need
to count the number of used entries. It's not a problem since such
operations are done when picking/releasing objects to/from the OS,
but it is a reminder that the number of buckets should remain small.

With this change, an h2load test made of 5 * 160 conn * 40 streams on
80 threads raised from 812k RPS to 1.5M RPS.

23 months agoMEDIUM: pools: spread the allocated counter over a few buckets
Willy Tarreau [Mon, 24 Jul 2023 13:53:17 +0000 (15:53 +0200)] 
MEDIUM: pools: spread the allocated counter over a few buckets

The ->used counter is one of the most stressed, and it heavily
depends on the ->allocated one, so let's first move ->allocated
to a few buckets.

A function "pool_allocated()" was added to return the sum of the entries.
It's important not to abuse it as it does iterate, so everywhere it's
possible to avoid it by keeping a local counter, it's better. Currently
it's used for limited pools which need to make sure they do not allocate
too many objects. That's an acceptable tradeoff to save CPU on large
machines at the expense of spending a little bit more on small ones which
normally are not under load.

23 months agoMINOR: pools: introduce the use of multiple buckets
Willy Tarreau [Sat, 12 Aug 2023 09:22:27 +0000 (11:22 +0200)] 
MINOR: pools: introduce the use of multiple buckets

On many threads and without the shared cache, there can be extreme
contention on the ->allocated counter, the ->free_list pointer, and
the ->used counter. It's possible to limit this contention by spreading
the counters a little bit over multiple entries, that are summed up when
a consultation is needed. The criterion used to spread the values cannot
be related to the thread ID due to migrations, since we need to keep
consistent stats (allocated vs used).

Instead we'll just hash the pointer, it provides an index that does the
job and that is consistent for the object. When having just a few entries
(16 here as it showed almost identical performance between global and
non-global pools) even iterations should be short enough during
measurements to not be a problem.

A pair of functions designed to ease pointer hash bucket calculation were
added, with one of them doing it for thread IDs because allocation failures
will be associated with a thread and not a pointer.

For now this patch only brings in the relevant parts of the infrastructure,
the CONFIG_HAP_POOL_BUCKETS_BITS macro that defaults to 6 bits when 512
threads or more are supported, 5 bits when 128 or more are supported, 4
bits when 16 or more are supported, otherwise 3 bits for small setups.
The array in the pool_head and the two utility functions are already
added. It should have no measurable impact beyond inflating the pool_head
structure.

23 months agoOPTIM: pools: make pool_get_from_os() / pool_put_to_os() not update ->allocated
Willy Tarreau [Sat, 12 Aug 2023 09:07:48 +0000 (11:07 +0200)] 
OPTIM: pools: make pool_get_from_os() / pool_put_to_os() not update ->allocated

The pool's allocation counter doesn't strictly require to be updated
from these functions, it may more efficiently be done in the caller
(even out of a loop for pool_flush() and pool_gc()), and doing so will
also help us spread the counters over an array later. The functions
were renamed _noinc and _nodec to make sure we catch any possible
user in an external patch. If needed, the original functions may easily
be reimplemented in an inline function.

23 months agoOPTIM: pools: use exponential back-off on shared pool allocation/release
Willy Tarreau [Mon, 24 Jul 2023 13:12:31 +0000 (15:12 +0200)] 
OPTIM: pools: use exponential back-off on shared pool allocation/release

Running a stick-table stress with -dMglobal under 56 threads shows
extreme contention on the pool's free_list because it has to be
processed in two phases and only used to implement a cpu_relax() on
the retry path.

Let's at least implement exponential back-off here to limit the neighbor's
noise and reduce the time needed to successfully acquire the pointer. Just
doing so shows there's still contention but almost doubled the performance,
from 1.1 to 2.1M req/s.

23 months agoOPTIM: tools: improve hash distribution using a better prime seed
Willy Tarreau [Sat, 12 Aug 2023 15:28:13 +0000 (17:28 +0200)] 
OPTIM: tools: improve hash distribution using a better prime seed

During tests it was noticed that the current hash is not that good
on 4- and 5- bit hashes. About 7.5% of all the 32-bit primes were tested
as candidates for the hash function, by submitting them 128 arrangements
of N pointers among 40k extracted from haproxy's pools, and the average
fill rates for 1- to 12- bit hashes were measured and compared. It was
clear that some values do not provide great hashes and other ones are
way more resistant.

The current value is not bad at all but delivers 42.6% unique 2-bit
outputs, 41.6% 3-bit, 38.0% 4-bit, 38.2% 5-bit and 37.1% 10-bit. Some
values did perform significantly better, among which 0xacd1be85 which
does 43.2% 2-bit, 42.5% 3-bit, 42.2% 4-bit, 39.2% 5-bit and 37.3% 10-bit.

The reverse value used in the ptr2_hash() was really underperforming and
was replaced with 0x9d28e4e9 which does 49.6%, 40.4%, 42.6%, 39.1%, and
37.2% respectvely.

This should slightly improve the accuracy of the task and memory
profiling, and will be useful for pools.

23 months agoMINOR: tools: improve ptr hash distribution on 64 bits
Willy Tarreau [Fri, 11 Aug 2023 18:11:30 +0000 (20:11 +0200)] 
MINOR: tools: improve ptr hash distribution on 64 bits

When testing the pointer hash on 64-bit real pointers (map entries),
it appeared that the shift by 33 bits that hoped to compensate for the
3 nul LSB degrades the hash, and the centering is more optimal on
31-(bits+1)/2. This makes sense since the topmost bit of the
multiplicator is 31, so for an input of 1 bit and 1 bit of output we
would always get zero. With the formula adjusted this way, we can get
up to ~15% more unique entries at 10 bits and ~24% more at 11 bits.

23 months agoMINOR: tools: make ptr_hash() support 0-bit outputs
Willy Tarreau [Tue, 25 Jul 2023 14:59:48 +0000 (16:59 +0200)] 
MINOR: tools: make ptr_hash() support 0-bit outputs

When dealing with macro-based size definitions, it is useful to be able
to hash pointers on zero bits so that the macro automatically returns a
constant 0. For now it only supports 1-32. Let's just add this special
case. It's automatically optimized out by the compiler since the function
is inlined.

23 months agoBUILD: defaults: use __WORDSIZE not LONGBITS for MAX_THREADS_PER_GROUP
Willy Tarreau [Sat, 12 Aug 2023 17:01:10 +0000 (19:01 +0200)] 
BUILD: defaults: use __WORDSIZE not LONGBITS for MAX_THREADS_PER_GROUP

LONGBITS was defined long ago with old compilers that didn't provide the
word size. It's still present as being referenced in various places in the
code, but we must not use it to define other macros that may be evaluated
at pre-processing time since it contains sizeof() and casts that are not
compatible with preprocessor conditions. Let's switch MAX_THREADS_PER_GROUP
to __WORDSIZE so that we can condition blocks of code on it if needed.

LONGBITS should really be removed by now, given that we don't support
compilers not providing __WORDSIZE anymore (gcc < 4.2).

23 months agoCLEANUP: stick-table: slightly reorder the stktable struct
Willy Tarreau [Mon, 7 Aug 2023 19:32:36 +0000 (21:32 +0200)] 
CLEANUP: stick-table: slightly reorder the stktable struct

By moving the config-time stuff after the updt_lock, we can plug some
holes without interfering with it. This allows us to get back to the
768-bytes struct. The performance was not affected at all.

23 months agoMINOR: stick-table: move the update lock into its own cache line
Willy Tarreau [Mon, 7 Aug 2023 19:15:40 +0000 (21:15 +0200)] 
MINOR: stick-table: move the update lock into its own cache line

The read-lock contention observed on the update lock while turning it
into an upgradable lock were due to false sharing with the nearby
updates. Simply moving the lock alone into its own cache line is
sufficient to almost double the performance again, raising from 2355
to 4480k RPS with very low contention:

  Samples: 1M of event 'cycles', 4000 Hz, Event count (approx.): 743422995452 lost
  Overhead  Shared Object          Symbol
    15.88%  haproxy                [.] stktable_lookup_key
     5.94%  haproxy                [.] ebmb_lookup
     5.69%  haproxy                [.] http_wait_for_request
     3.66%  haproxy                [.] stktable_touch_with_exp
     2.62%  [kernel]               [k] _raw_spin_unlock_irqrestore
     1.86%  haproxy                [.] http_action_return
     1.79%  haproxy                [.] stream_process_counters
     1.78%  [kernel]               [k] skb_release_data
     1.77%  haproxy                [.] process_stream

Unfortunately, trying to move the line anywhere else didn't work,
despite the remaining holes, because this structure is not quite
clean. This adds 64 bytes to a struct that was already 768 long,
so it's now 832. It's possible to repack it a little bit and regain
these bytes by removing the THREAD_ALIGN before "keys" because we
rarely use the config stuff, but that's a bit unsafe.

23 months agoMEDIUM: peers: drop the stick-table lock before entering peer_send_teachmsgs()
Willy Tarreau [Mon, 7 Aug 2023 18:17:50 +0000 (20:17 +0200)] 
MEDIUM: peers: drop the stick-table lock before entering peer_send_teachmsgs()

The function drops the lock very early, and the only operations that
are performed on the entry code are updating the current peer's
last_local_table, which doesn't need to be protected. Thus it's
easier to drop the lock before entering the function and it further
limits its scope.

This has raised the peak RPS from 2050 to 2355k/s with a peers section on
the 80-core machine.

23 months agoMEDIUM: stick-table: touch updates under an upgradable read lock
Willy Tarreau [Mon, 7 Aug 2023 19:03:24 +0000 (21:03 +0200)] 
MEDIUM: stick-table: touch updates under an upgradable read lock

Instead of taking the update's write lock in stktable_touch_with_exp(),
while most of the time under high load there is nothing to update because
the entry is touched before having been synchronized present, let's do
the check under a read lock and upgrade it to perform the update if
needed. These updates are rare and the contention is not expected to be
very high, so at the first failure to upgrade we retry directly with a
write lock.

By doing so the performance has almost doubled again, from 1140 to 2050k
with a peers section enabled. The contention is now on taking the read
lock itself, so there's little to be gained beyond this in this function.

23 months agoMEDIUM: stick-table: use a distinct lock for the updates tree
Willy Tarreau [Sat, 27 May 2023 17:55:15 +0000 (17:55 +0000)] 
MEDIUM: stick-table: use a distinct lock for the updates tree

Updating an entry in the updates tree is currently performed under the
table's write lock, which causes huge contention with other accesses
such as lookups and free. Aside the updates tree, the update,
localupdate and commitupdate variables, nothing is manipulated, so
let's create a distinct lock (updt_lock) to protect these together
to remove this contention. It required to add an extra lock in the
few places where we delete the update (though only if we're really
going to delete it) to protect the tree. This is very convenient
because now peer_send_teachmsgs() only needs to take this read lock,
and there is very little contention left on the stick-table.

With this alone, the performance jumped from 614k to 1140k/s on a
80-thread machine with a peers section! Stick-table updates with
no peers however now has to stand two locks and slightly regressed
from 4.0-4.1M/s to 3.9-4.0. This is fairly minimal compared to the
significant unlocking of the peers updates and considered totally
acceptable.

23 months agoMEDIUM: peers: only read-lock peer_send_teachmsgs()
Willy Tarreau [Mon, 7 Aug 2023 17:39:27 +0000 (19:39 +0200)] 
MEDIUM: peers: only read-lock peer_send_teachmsgs()

This function doesn't need to be write-locked. It performs a lookup
of the next update at its index, atomically updates the ref_cnt on
the stksess, updates some shared_table fields on the local thread,
and updates the table's commitupdate. Now that this update is atomic
we don't need to keep the write lock during that period. In addition
this function's callers do not rely on the write lock to be held
either since it was droped during peer_send_updatemsg() anyway.

Now, when the function is entered with a write lock, it's downgraded
to a read lock, otherwise a read lock is grabbed. Updates are looked
up under the read lock and the message is sent without the lock. The
commitupdate is still performed under the read lock (so as not to
break the code too much), and the write lock is re-acquired when
leaving if needed. This allows multiple peers to look up updates in
parallel and to avoid stalling stick-table lookups.

23 months agoMEDIUM: peers: drop then re-acquire the wrlock in peer_send_teachmsgs()
Willy Tarreau [Fri, 28 Jul 2023 14:03:27 +0000 (14:03 +0000)] 
MEDIUM: peers: drop then re-acquire the wrlock in peer_send_teachmsgs()

This function maintains the write lock for a while. In practice it does
not need to hold it that long, and some parts could be performed under a
read lock. This patch first drops then re-acquires the write lock at the
function's entry. The purpose is simply to break the end-to-end atomicity
to prove that it has no impact in case something needs to be bisected
later. In fact the write lock is already dropped while calling
peer_send_updatemsg().

23 months agoMEDIUM: peers: update ->commitupdate out of the lock using a CAS
Willy Tarreau [Fri, 28 Jul 2023 14:02:16 +0000 (14:02 +0000)] 
MEDIUM: peers: update ->commitupdate out of the lock using a CAS

The ->commitupdate index doesn't need to be kept consistent with other
operations, it only needs to be correct and to reflect the last known
value. Right now it's updated under the stick-table lock, which is
expensive and maintains this lock longer than needed. Let's move it
outside of the lock, and update it using a CAS. This patch simply
replaces the assignment with a CAS and makes sure all reads are atomic.
On failed CAS we use a simple cpu_relax(), no need for more as there
should not be that much contention here (updates are not that fast).

23 months agoMINOR: stick-table: better organize the struct stktable
Willy Tarreau [Sat, 27 May 2023 18:07:41 +0000 (18:07 +0000)] 
MINOR: stick-table: better organize the struct stktable

The structure currently mixes R/O and R/W fields, let's organize them
by access type, focusing mainly on splitting the updates from the rest
so that peers activity does not affect the rest. For now it doesn't
bring any benefit but it paves the way for splitting the lock.

23 months agoMEDIUM: stick-table: change the ref_cnt atomically
Willy Tarreau [Sat, 27 May 2023 16:55:48 +0000 (16:55 +0000)] 
MEDIUM: stick-table: change the ref_cnt atomically

Due to the ts->ref_cnt being manipulated and checked inside wrlocks,
we continue to have it updated under plenty of read locks, which have
an important cost on many-thread machines.

This patch turns them all to atomic ops and carefully moves them outside
of locks every time this is possible:
  - the ref_cnt is incremented before write-unlocking on creation otherwise
    the element could vanish before we can do it
  - the ref_cnt is decremented after write-locking on release
  - for all other cases it's updated out of locks since it's guaranteed by
    the sequence that it cannot vanish
  - checks are done before locking every time it's used to decide
    whether we're going to release the element (saves several write locks)
  - expiration tests are just done using atomic loads, since there's no
    particular ordering constraint there, we just want consistent values.

For Lua, the loop that is used to dump stick-tables could switch to read
locks only, but this was not done.

For peers, the loop that builds updates in peer_send_teachmsgs is extremely
expensive in write locks and it doesn't seem this is really needed since
the only updated variables are last_pushed and commitupdate, the first
one being on the shared table (thus not used by other threads) and the
commitupdate could likely be changed using a CAS. Thus all of this could
theoretically move under a read lock, but that was not done here.

On a 80-thread machine with a peers section enabled, the request rate
increased from 415 to 520k rps.

23 months agoMINOR: stick-table: move the task_wakeup() call outside of the lock
Willy Tarreau [Sat, 27 May 2023 18:35:15 +0000 (18:35 +0000)] 
MINOR: stick-table: move the task_wakeup() call outside of the lock

The write lock in stktable_touch_with_exp() is quite expensive and should
be shortened as much as possible. There's no need for it when calling
task_wakeup() so let's move it out.

On a 80-thread machine with a peers section, the request rate increased
from 397k to 415k rps.

23 months agoMINOR: stick-table: move the task_queue() call outside of the lock
Willy Tarreau [Sat, 27 May 2023 18:32:05 +0000 (18:32 +0000)] 
MINOR: stick-table: move the task_queue() call outside of the lock

The write lock in stktable_requeue_exp() is quite expensive and should
be shortened as much as possible. There's no need for it when calling
task_queue() so let's move it out.

On a 80-thread machine with a peers section, the request rate increased
from 368k to 397k rps.

23 months agoMAJOR: threads/plock: update the embedded library again
Willy Tarreau [Mon, 7 Aug 2023 17:16:33 +0000 (19:16 +0200)] 
MAJOR: threads/plock: update the embedded library again

This updates the local copy of the plock library to benefit from finer
memory ordering, EBO on more operations such as when take_w() and stow()
wait for readers to leave  and refined EBO, especially on common operation
such as attempts to upgade R to S, and avoids a counter-productive prior
read in rtos() and take_r().

These changes have shown a 5% increase on regular operations on ARM,
a 33% performance increase on ARM on stick-tables and 2% on x86, and
a 14% and 4% improvements on peers updates respectively on ARM and x86.

The availability of relaxed operations will probably be useful for stats
counters which are still extremely expensive to update.

The following plock commits were included in this update:

  9db830b plock: support inlining exponential backoff code
  008d3c2 plock: make the rtos upgrade faster
  2f76dde atomic: clean up the generic xchg()
  3c6919b atomic: make sure that the no-return macros do not return a value
  97c2bb7 atomic: make the fallback bts use the pointed type for the shift
  f4c1880 atomic: also implement the missing pl_btr()
  8329b82 atomic: guard all generic definitions to make it easier to provide specific ones
  7c5cb62 atomic: use C11 atomics when available
  96afaf9 atomic: prefer the C11 definitions in general
  f3ec7a6 atomic: implement load/store/atomic barriers
  8bdbd1e atomic: add atomic load/stores
  0f604c0 atomic: add more _noret operations
  3fe35db atomic: remove the (void) cast from the C11 operations
  3b08a7c atomic: allow to define the fallback _noret variants
  28deb22 atomic: make x86 arithmetic operations the _noret variants
  8061fe2 atomic: handle modern compilers that support returning flags
  b8b91b7 atomic: add the fetch-and-<op> operations (pl_ld<op>)
  59817ca atomic: add memory order variants for most operations
  a40774f plock: explicitly make use of the pl_*_noret operations
  6f1861b plock: switch to pl_sub_noret_lax() for cancellation
  c013980 plock: use pl_ldadd{_lax,_acq,} instead of pl_xadd()
  382eea3 plock: use a release ordering when dropping the lock
  60d750d plock: use EBO when waiting for readers to leave in take_w() and stow()
  fc01c4f plock: improve EBO a little bit
  1ef6390 plock: switch to CAS + XADD for pl_take_r()

23 months agoBUG/MEDIUM: hlua: streams don't support mixing lua-load with lua-load-per-thread
Aurelien DARRAGON [Wed, 9 Aug 2023 13:19:56 +0000 (15:19 +0200)] 
BUG/MEDIUM: hlua: streams don't support mixing lua-load with lua-load-per-thread

Michel Mayen reported that mixing lua actions loaded from 'lua-load'
and 'lua-load-per-thread' directives within a single http/tcp session
yields unexpected results.

When executing action defined in another running context from the one of
the previously executed action (from lua-load, then from
lua-load-per-thread or the opposite, order doesn't matter), it would yield
this kind of error:

"Lua function 'name': [state-id x] runtime error: attempt to call a nil value from ."

He also noted that when loading all actions using the same loading
directive, the issue is gone.

This is due to the fact that for lua actions, fetches and converters, lua
code is being executed from the stream lua context. However, the stream
lua context, which is created on the fly when first executing some lua
code related to the stream, is reused between multiple lua executions.

But the thing is, despite successive executions referring to the same
parent "stream" (which is also assigned to a given thread id), they don't
necessarily depend on the same running context from lua point of view.

Indeed, since the function which is about to be executed could have been
loaded from either 'lua-load' or 'lua-load-per-thread', the function
declaration and related dependencies are defined in a specific stack ID
which is known by calling fcn_ref_to_stack_id() on the given function.

Thus, in order to make streams capable of chaining lua actions, fetches and
converters loaded in different lua stacks, we add a new detection logic
in hlua_stream_ctx_prepare() to be able to recreate the lua context in the
proper stack space when the existing one conflicts with the expected stack
id.

This must be backported in every stable versions.

It depends on:
 - "MINOR: hlua: add hlua_stream_prepare helper function"
   [for < 2.5, skip the filter part since they didn't exist]

[wt: warning, wait a little bit before backporting too far, we
 need to be certain the added BUG_ON() will never trigger]

23 months agoMINOR: hlua: add hlua_stream_ctx_prepare helper function
Aurelien DARRAGON [Wed, 9 Aug 2023 12:56:19 +0000 (14:56 +0200)] 
MINOR: hlua: add hlua_stream_ctx_prepare helper function

Stream-dedicated hlua ctx creation and attachment is now performed in
hlua_stream_ctx_prepare() helper function to ease code maintenance.

No functional behavior change should be expected.

23 months agoBUG/MINOR: hlua: fix invalid use of lua_pop on error paths
Aurelien DARRAGON [Wed, 9 Aug 2023 08:11:49 +0000 (10:11 +0200)] 
BUG/MINOR: hlua: fix invalid use of lua_pop on error paths

Multiple error paths made invalid use of lua_pop():

When the stack is emptied using lua_settop(0), lua_pop() (which is
implemented as a lua_settop() macro) should not be used right after,
because it could lead to invalid reads since the stack is already empty.

Unfortunately, some remnants from initial lua stack implementation kept
doing so, resulting in haproxy crashs on some lua runtime errors paths
from time to time (ie: ERRRUN, ERRMEM).

Moreover, the extra lua_pop() instruction, even if it was safe, is totally
pointless in such case.

Removing such unsafe lua_pop() statements when we know that the stack is
already empty.

This must be backported in every stable versions.

23 months agoBUG/MEDIUM: quic: fix tasklet_wakeup loop on connection closing
Amaury Denoyelle [Fri, 11 Aug 2023 14:10:34 +0000 (16:10 +0200)] 
BUG/MEDIUM: quic: fix tasklet_wakeup loop on connection closing

It is possible to trigger a loop of tasklets calls if a QUIC connection
is interrupted abruptly by the client. This is caused by the following
interaction :
* FD iocb is woken up for read. This causes a wakeup on quic_conn
  tasklet.
* quic_conn_io_cb is run and try to read but fails as the connection
  socket is closed (typically with a ECONNREFUSED). FD read is
  subscribed to the poller via qc_rcv_buf() which will cause the loop.

The looping will stop automatically once the idle-timeout is expired and
the connection instance is finally released.

To fix this, ensure FD read is subscribed only for transient error cases
(EAGAIN or similar). All other cases are considered as fatal and thus
all future read operations will fail. Note that for the moment, nothing
is reported on the quic_conn which may not skip future reception. This
should be improved in a future commit to accelerate connection closing.

This bug can be reproduced on a frequent occurence by interrupting the
following command. Quic traces should be activated on haproxy side to
detect the loop :
$ ngtcp2-client --tp-file=/tmp/ngtcp2-tp.txt \
  --session-file=/tmp/ngtcp2-session.txt \
  -r 0.3 -t 0.3 --exit-on-all-streams-close 127.0.0.1 20443 \
  "http://127.0.0.1:20443/?s=1024"

This must be backported up to 2.7.

23 months agoBUG/MINOR: quic: Missing tasklet (quic_cc_conn_io_cb) memory release (leak)
Frédéric Lécaille [Fri, 11 Aug 2023 09:32:10 +0000 (11:32 +0200)] 
BUG/MINOR: quic: Missing tasklet (quic_cc_conn_io_cb) memory release (leak)

The tasklet responsible of handling the remaining QUIC connection object
and its traffic was not released, leading to a memory leak. Furthermore its
callback, quic_cc_conn_io_cb(), should return NULL after this tasklet is
released.

23 months agoBUG/MINOR: quic: Possible crash when issuing "show fd/sess" CLI commands
Frédéric Lécaille [Fri, 11 Aug 2023 09:21:31 +0000 (11:21 +0200)] 
BUG/MINOR: quic: Possible crash when issuing "show fd/sess" CLI commands

->xprt_ctx (struct ssl_sock_ctx) and ->conn (struct connection) must be kept
by the remaining QUIC connection object (struct quic_cc_conn) after having
release the previous one (struct quic_conn) to allow "show fd/sess" commands
to be functional without causing haproxy crashes.

No need to backport.

23 months agoMINOR: quic: Add a trace for QUIC conn fd ready for receive
Frédéric Lécaille [Fri, 11 Aug 2023 06:57:47 +0000 (08:57 +0200)] 
MINOR: quic: Add a trace for QUIC conn fd ready for receive

Add a trace as this is done for the "send ready" fd state.

23 months agoBUG/MINOR: quic: Possible crash in quic_cc_conn_io_cb() traces.
Frédéric Lécaille [Thu, 10 Aug 2023 15:21:19 +0000 (17:21 +0200)] 
BUG/MINOR: quic: Possible crash in quic_cc_conn_io_cb() traces.

Reset the local cc_qc and qc after having released cc_qc. Note that
cc_qc == qc. This is required to prevent haproxy from crashing
when TRACE_LEAVE() is called.

No need to backport.

23 months agoBUG/MINOR: quic: mux started when releasing quic_conn
Frédéric Lécaille [Thu, 10 Aug 2023 15:15:21 +0000 (17:15 +0200)] 
BUG/MINOR: quic: mux started when releasing quic_conn

There are cases where the mux is started before the handshake is completed:
during 0-RTT sessions.

So, it was a bad idea to try to release the quic_conn object from quic_conn_io_cb()
without checking if the mux is started.

No need to backport.

23 months agoCI: get rid of travis-ci wrapper for Coverity scan
Ilya Shipitsin [Sat, 5 Aug 2023 22:07:39 +0000 (00:07 +0200)] 
CI: get rid of travis-ci wrapper for Coverity scan

historically coverity scan was performed by travis-ci script, let us
rewrite it in bash

23 months agoCI: do not use "groupinstall" for Fedora Rawhide builds
Ilya Shipitsin [Sat, 5 Aug 2023 22:07:38 +0000 (00:07 +0200)] 
CI: do not use "groupinstall" for Fedora Rawhide builds

Fedora Rawhide migrated to dnf5, which does not support "groupinstall"

23 months agoDEV: makefile: add a new "range" target to iteratively build all commits
Willy Tarreau [Wed, 9 Aug 2023 14:52:28 +0000 (16:52 +0200)] 
DEV: makefile: add a new "range" target to iteratively build all commits

This will iterate over all commits in the range passed in RANGE, or all
those from master to RANGE if no ".." exists in RANGE, and run "make all"
with the exact same variables. This aims to ease the verification that
no build failure exists inside a series. In case of error, it prints the
faulty commit and stops there with the tree checked out. Example:

  $ make-disctcc range RANGE=HEAD
  Found 14 commit(s) in range master..HEAD.
  Current branch is 20230809-plock+tbl+peers-4
  Starting to building now...
  [ 1/14 ]   392922bc5 #############################
  (...)
  Done! 14 commit(s) built successfully for RANGE master..HEAD

Maybe in the future it will automatically use HEAD as a default for RANGE
depending on the feedback.

It's not listed in the help target so as not to encourage users to try it
as it can very quickly become confusing due to the checkouts.

23 months agoBUILD: mux-h1: shut a build warning on clang from previous commit
Willy Tarreau [Wed, 9 Aug 2023 14:02:44 +0000 (16:02 +0200)] 
BUILD: mux-h1: shut a build warning on clang from previous commit

Commit 5201b4abd ("BUG/MEDIUM: mux-h1: do not forget EOH even when no
header is sent") introduced a build warning on clang due to the remaining
two parenthesis in the expression. Let's fix this. No backport needed.

23 months agoBUG/MEDIUM: mux-h1: do not forget EOH even when no header is sent
Willy Tarreau [Wed, 9 Aug 2023 09:58:15 +0000 (11:58 +0200)] 
BUG/MEDIUM: mux-h1: do not forget EOH even when no header is sent

Since commit 723c73f8a ("MEDIUM: mux-h1: Split h1_process_mux() to make
code more readable"), outgoing H1 requests with no header at all (i.e.
essentially HTTP/1.0 requests) get delayed by 200ms. Christopher found
that it's due to the fact that we end processing too early and we don't
have the opportunity to send the EOH in this case.

This fix addresses it by verifying if it's required to emit EOH when
retruning from h1_make_headers(). But maybe that block could be moved
after the while loop in fact, or the stop condition in the loop be
revisited not to stop of !htx_is_empty(). The current solution gets the
job done at least.

No backport is needed, this was in 2.9-dev.

23 months agoBUG/MEDIUM: mux-h1: fix incorrect state checking in h1_process_mux()
Willy Tarreau [Wed, 9 Aug 2023 09:51:58 +0000 (11:51 +0200)] 
BUG/MEDIUM: mux-h1: fix incorrect state checking in h1_process_mux()

That's a regression introduced in 2.9-dev by commit 723c73f8a ("MEDIUM:
mux-h1: Split h1_process_mux() to make code more readable") and found
by Christopher. The consequence is uncertain but the test definitely was
not right in that it would catch most existing states (H1_MSG_DONE=30).
At least it would emit too many "H1 request fully xferred".

No backport needed.

23 months agoBUG/MINOR: http: skip leading zeroes in content-length values
Willy Tarreau [Wed, 9 Aug 2023 09:02:34 +0000 (11:02 +0200)] 
BUG/MINOR: http: skip leading zeroes in content-length values

Ben Kallus also noticed that we preserve leading zeroes on content-length
values. While this is totally valid, it would be safer to at least trim
them before passing the value, because a bogus server written to parse
using "strtol(value, NULL, 0)" could inadvertently take a leading zero
as a prefix for an octal value. While there is not much that can be done
to protect such servers in general (e.g. lack of check for overflows etc),
at least it's quite cheap to make sure the transmitted value is normalized
and not taken for an octal one.

This is not really a bug, rather a missed opportunity to sanitize the
input, but is marked as a bug so that we don't forget to backport it to
stable branches.

A combined regtest was added to h1or2_to_h1c which already validates
end-to-end syntax consistency on aggregate headers.

23 months agoBUG/MAJOR: http: reject any empty content-length header value
Willy Tarreau [Wed, 9 Aug 2023 06:32:48 +0000 (08:32 +0200)] 
BUG/MAJOR: http: reject any empty content-length header value

The content-length header parser has its dedicated function, in order
to take extreme care about invalid, unparsable, or conflicting values.
But there's a corner case in it, by which it stops comparing values
when reaching the end of the header. This has for a side effect that
an empty value or a value that ends with a comma does not deserve
further analysis, and it acts as if the header was absent.

While this is not necessarily a problem for the value ending with a
comma as it will be cause a header folding and will disappear, it is a
problem for the first isolated empty header because this one will not
be recontructed when next ones are seen, and will be passed as-is to the
backend server. A vulnerable HTTP/1 server hosted behind haproxy that
would just use this first value as "0" and ignore the valid one would
then not be protected by haproxy and could be attacked this way, taking
the payload for an extra request.

In field the risk depends on the server. Most commonly used servers
already have safe content-length parsers, but users relying on haproxy
to protect a known-vulnerable server might be at risk (and the risk of
a bug even in a reputable server should never be dismissed).

A configuration-based work-around consists in adding the following rule
in the frontend, to explicitly reject requests featuring an empty
content-length header that would have not be folded into an existing
one:

    http-request deny if { hdr_len(content-length) 0 }

The real fix consists in adjusting the parser so that it always expects a
value at the beginning of the header or after a comma. It will now reject
requests and responses having empty values anywhere in the C-L header.

This needs to be backported to all supported versions. Note that the
modification was made to functions h1_parse_cont_len_header() and
http_parse_cont_len_header(). Prior to 2.8 the latter was in
h2_parse_cont_len_header(). One day the two should be refused but the
former is also used by Lua.

The HTTP messaging reg-tests were completed to test these cases.

Thanks to Ben Kallus of Dartmouth College and Narf Industries for
reporting this! (this is in GH #2237).

23 months agoDOC: clarify the handling of URL fragments in requests
Willy Tarreau [Tue, 8 Aug 2023 17:35:25 +0000 (19:35 +0200)] 
DOC: clarify the handling of URL fragments in requests

We indicate in path/pathq/url that they may contain '#' if the frontend
is configured with "option accept-invalid-http-request", and that option
mentions the fragment as well.

23 months agoREGTESTS: http-rules: verify that we block '#' by default for normalize-uri
Willy Tarreau [Tue, 8 Aug 2023 17:53:51 +0000 (19:53 +0200)] 
REGTESTS: http-rules: verify that we block '#' by default for normalize-uri

Since we now block fragments by default, let's add an extra test there
to confirm that it's blocked even when stripping it.

23 months agoBUG/MINOR: h3: reject more chars from the :path pseudo header
Willy Tarreau [Tue, 8 Aug 2023 15:54:26 +0000 (17:54 +0200)] 
BUG/MINOR: h3: reject more chars from the :path pseudo header

This is the h3 version of this previous fix:

   BUG/MINOR: h2: reject more chars from the :path pseudo header

In addition to the current NUL/CR/LF, this will also reject all other
control chars, the space and '#' from the :path pseudo-header, to avoid
taking the '#' for a part of the path. It's still possible to fall back
to the previous behavior using "option accept-invalid-http-request".

Here the :path header value is scanned a second time to look for
forbidden chars because we don't know upfront if we're dealing with a
path header field or another one. This is no big deal anyway for now.

This should be progressively backported to 2.6, along with the
following commits it relies on (the same as for h2):

   REGTESTS: http-rules: add accept-invalid-http-request for normalize-uri tests
   REORG: http: move has_forbidden_char() from h2.c to http.h
   MINOR: ist: add new function ist_find_range() to find a character range
   MINOR: http: add new function http_path_has_forbidden_char()

23 months agoBUG/MINOR: h2: reject more chars from the :path pseudo header
Willy Tarreau [Tue, 8 Aug 2023 13:40:49 +0000 (15:40 +0200)] 
BUG/MINOR: h2: reject more chars from the :path pseudo header

This is the h2 version of this previous fix:

    BUG/MINOR: h1: do not accept '#' as part of the URI component

In addition to the current NUL/CR/LF, this will also reject all other
control chars, the space and '#' from the :path pseudo-header, to avoid
taking the '#' for a part of the path. It's still possible to fall back
to the previous behavior using "option accept-invalid-http-request".

This patch modifies the request parser to change the ":path" pseudo header
validation function with a new one that rejects 0x00-0x1F (control chars),
space and '#'. This way such chars will be dropped early in the chain, and
the search for '#' doesn't incur a second pass over the header's value.

This should be progressively backported to stable versions, along with the
following commits it relies on:

     REGTESTS: http-rules: add accept-invalid-http-request for normalize-uri tests
     REORG: http: move has_forbidden_char() from h2.c to http.h
     MINOR: ist: add new function ist_find_range() to find a character range
     MINOR: http: add new function http_path_has_forbidden_char()
     MINOR: h2: pass accept-invalid-http-request down the request parser

23 months agoBUG/MINOR: h1: do not accept '#' as part of the URI component
Willy Tarreau [Tue, 8 Aug 2023 14:17:22 +0000 (16:17 +0200)] 
BUG/MINOR: h1: do not accept '#' as part of the URI component

Seth Manesse and Paul Plasil reported that the "path" sample fetch
function incorrectly accepts '#' as part of the path component. This
can in some cases lead to misrouted requests for rules that would apply
on the suffix:

    use_backend static if { path_end .png .jpg .gif .css .js }

Note that this behavior can be selectively configured using
"normalize-uri fragment-encode" and "normalize-uri fragment-strip".

The problem is that while the RFC says that this '#' must never be
emitted, as often it doesn't suggest how servers should handle it. A
diminishing number of servers still do accept it and trim it silently,
while others are rejecting it, as indicated in the conversation below
with other implementers:

   https://lists.w3.org/Archives/Public/ietf-http-wg/2023JulSep/0070.html

Looking at logs from publicly exposed servers, such requests appear at
a rate of roughly 1 per million and only come from attacks or poorly
written web crawlers incorrectly following links found on various pages.

Thus it looks like the best solution to this problem is to simply reject
such ambiguous requests by default, and include this in the list of
controls that can be disabled using "option accept-invalid-http-request".

We're already rejecting URIs containing any control char anyway, so we
should also reject '#'.

In the H1 parser for the H1_MSG_RQURI state, there is an accelerated
parser for bytes 0x21..0x7e that has been tightened to 0x24..0x7e (it
should not impact perf since 0x21..0x23 are not supposed to appear in
a URI anyway). This way '#' falls through the fine-grained filter and
we can add the special case for it also conditionned by a check on the
proxy's option "accept-invalid-http-request", with no overhead for the
vast majority of valid URIs. Here this information is available through
h1m->err_pos that's set to -2 when the option is here (so we don't need
to change the API to expose the proxy). Example with a trivial GET
through netcat:

  [08/Aug/2023:16:16:52.651] frontend layer1 (#2): invalid request
    backend <NONE> (#-1), server <NONE> (#-1), event #0, src 127.0.0.1:50812
    buffer starts at 0 (including 0 out), 16361 free,
    len 23, wraps at 16336, error at position 7
    H1 connection flags 0x00000000, H1 stream flags 0x00000810
    H1 msg state MSG_RQURI(4), H1 msg flags 0x00001400
    H1 chunk len 0 bytes, H1 body len 0 bytes :

    00000  GET /aa#bb HTTP/1.0\r\n
    00021  \r\n

This should be progressively backported to all stable versions along with
the following patch:

    REGTESTS: http-rules: add accept-invalid-http-request for normalize-uri tests

Similar fixes for h2 and h3 will come in followup patches.

Thanks to Seth Manesse and Paul Plasil for reporting this problem with
detailed explanations.

23 months agoREGTESTS: http-rules: add accept-invalid-http-request for normalize-uri tests
Willy Tarreau [Tue, 8 Aug 2023 17:52:45 +0000 (19:52 +0200)] 
REGTESTS: http-rules: add accept-invalid-http-request for normalize-uri tests

We'll soon block the '#' by default so let's prepare the test to continue
to work.

23 months agoMINOR: h2: pass accept-invalid-http-request down the request parser
Willy Tarreau [Tue, 8 Aug 2023 13:38:28 +0000 (15:38 +0200)] 
MINOR: h2: pass accept-invalid-http-request down the request parser

We're adding a new argument "relaxed" to h2_make_htx_request() so that
we can control its level of acceptance of certain invalid requests at
the proxy level with "option accept-invalid-http-request". The goal
will be to add deactivable checks that are still desirable to have by
default. For now no test is subject to it.

23 months agoMINOR: http: add new function http_path_has_forbidden_char()
Willy Tarreau [Tue, 8 Aug 2023 13:24:54 +0000 (15:24 +0200)] 
MINOR: http: add new function http_path_has_forbidden_char()

As its name implies, this function checks if a path component has any
forbidden headers starting at the designated location. The goal is to
seek from the result of a successful ist_find_range() for more precise
chars. Here we're focusing on 0x00-0x1F, 0x20 and 0x23 to make sure
we're not too strict at this point.

23 months agoMINOR: ist: add new function ist_find_range() to find a character range
Willy Tarreau [Tue, 8 Aug 2023 13:23:19 +0000 (15:23 +0200)] 
MINOR: ist: add new function ist_find_range() to find a character range

This looks up the character range <min>..<max> in the input string and
returns a pointer to the first one found. It's essentially the equivalent
of ist_find_ctl() in that it searches by 32 or 64 bits at once, but deals
with a range.

23 months agoMINOR: mux-h2/traces: also suggest invalid header upon parsing error
Willy Tarreau [Tue, 8 Aug 2023 13:27:02 +0000 (15:27 +0200)] 
MINOR: mux-h2/traces: also suggest invalid header upon parsing error

Historically the parsing error used to apply only to too large headers,
so this is what has been reported in traces. But nowadays we can also
reject invalid characters, and when this happens the trace is a bit
misleading, so let's mention "or invalid".

23 months agoBUG/MAJOR: h3: reject header values containing invalid chars
Willy Tarreau [Tue, 8 Aug 2023 15:18:27 +0000 (17:18 +0200)] 
BUG/MAJOR: h3: reject header values containing invalid chars

In practice it's exactly the same for h3 as 54f53ef7c ("BUG/MAJOR: h2:
reject header values containing invalid chars") was for h2: we must
make sure never to accept NUL/CR/LF in any header value because this
may be used to construct splitted headers on the backend. Hence we
apply the same solution. Here pseudo-headers, headers and trailers are
checked separately, which explains why we have 3 locations instead of
2 for h2 (+1 for response which we don't have here).

This is marked major for consistency and due to the impact if abused,
but the reality is that at the time of writing, this problem is limited
by the scarcity of the tools which would permit to build such a request
in the first place. But this may change over time.

This must be backported to 2.6. This depends on the following commit
that exposes the filtering function:

    REORG: http: move has_forbidden_char() from h2.c to http.h

23 months agoREORG: http: move has_forbidden_char() from h2.c to http.h
Willy Tarreau [Tue, 8 Aug 2023 15:00:50 +0000 (17:00 +0200)] 
REORG: http: move has_forbidden_char() from h2.c to http.h

This function is not H2 specific but rather generic to HTTP. We'll
need it in H3 soon, so let's move it to HTTP and rename it to
http_header_has_forbidden_char().

23 months agoMINOR: quic: Warning for OpenSSL wrapper QUIC bindings without "limited-quic"
Frédéric Lécaille [Tue, 8 Aug 2023 09:41:13 +0000 (11:41 +0200)] 
MINOR: quic: Warning for OpenSSL wrapper QUIC bindings without "limited-quic"

If the "limited-quic" globale option wa not set, the QUIC listener bindings were
not bound, this is ok, but silently ignored.

Add a warning in these cases to ask the user to explicitely enable the QUIC
bindings when building QUIC support against a TLS/SSL library without QUIC
support (OpenSSL).

23 months agoMINOR: quic: Release asap quic_conn memory from ->close() xprt callback.
Frédéric Lécaille [Tue, 8 Aug 2023 08:50:51 +0000 (10:50 +0200)] 
MINOR: quic: Release asap quic_conn memory from ->close() xprt callback.

Add a condition to release asap the quic_conn memory when the connection is
in "connection close" state from ->close() QUIC xprt callback.

23 months agoMINOR: quic: Release asap quic_conn memory (application level)
Frédéric Lécaille [Tue, 8 Aug 2023 08:47:11 +0000 (10:47 +0200)] 
MINOR: quic: Release asap quic_conn memory (application level)

Add a check to the QUIC packet handler running at application level (after the
handshake is complete) to release the quic_conn memory calling quic_conn_release().
This is done only if the mux is not started.

23 months agoMEDIUM: quic: Allow the quic_conn memory to be asap released.
Frédéric Lécaille [Mon, 7 Aug 2023 15:45:23 +0000 (17:45 +0200)] 
MEDIUM: quic: Allow the quic_conn memory to be asap released.

When the connection enters the "connection closing" state after having sent
a datagram with CONNECTION_CLOSE frames inside its packets, a lot of memory
may be freed from quic_conn objects (QUIC connection). This is done allocating
a reduced sized object which keeps enough information to handle the remaining
incoming packets for the connection in "connection closing" state, and to
continue to send again the previous datagram with CONNECTION_CLOSE frames inside
which has already been sent.

Define a new quic_cc_conn struct which represents the connection object after
entering the "connection close" state and after having release the quic_conn
connection object.

Define <pool_head_quic_cc_conn> new pool for these quic_cc_conn struct objects.

Define QUIC_CONN_COMMON structure which is shared between quic_conn struct object
(the connection before entering "connection close" state), and new quic_cc_conn
struct object (the connection after entering "connection close"). So, all the
members inside QUIC_CONN_COMMON may be indifferently dereferenced from a
quic_conn struct or a quic_cc_conn struct pointer.

Implement qc_new_cc_conn() function to allocate such connections in
"connection close" state. This function is responsible of copying the
required information from the original connection (quic_conn) to the remaining
connection (quic_cc_conn). Among others initialization, it redefined the
QUIC packet handler task to quic_cc_conn_io_cb() and the idle timer task
to qc_cc_idle_timer_task(). quic_cc_conn_io_cb() drains the received and
resend the datagram which CONNECTION_CLOSE frame which has already been sent
when entering "connection close" state. qc_cc_idle_timer_task() only releases
the remaining quic_cc_conn struct object.

Modify quic_conn_release() to allocate quic_cc_conn struct objects from the
original connection passed as argument. It does nothing if this original
connection is not in closing state, or if the idle timer has already expired.

Implement quic_release_cc_conn() to release a "connection close" connection.
It is called when its timer expires or if an error occured when sending
a packet from this connection when the peer is no more reachable.

23 months agoMINOR: quic: Use a pool for the connection ID tree.
Frédéric Lécaille [Mon, 7 Aug 2023 07:50:04 +0000 (09:50 +0200)] 
MINOR: quic: Use a pool for the connection ID tree.

Add "quic_cids" new pool to allocate the ->cids trees of quic_conn objects.
Replace ->cids member of quic_conn objects by pointer to "quic_cids" and
adapt the code consequently. Nothing special.

23 months agoMEDIUM: quic: Send CONNECTION_CLOSE packets from a dedicated buffer.
Frédéric Lécaille [Fri, 4 Aug 2023 15:59:28 +0000 (17:59 +0200)] 
MEDIUM: quic: Send CONNECTION_CLOSE packets from a dedicated buffer.

Add a new pool <pool_head_quic_cc_buf> for buffer used when building datagram
wich CONNECTION_CLOSE frames inside with QUIC_MIN_CC_PKTSIZE(128) as minimum
size.
Add ->cc_buf_area to quic_conn struct to store such buffers.
Add ->cc_dgram_len to store the size of the "connection close" datagrams
and ->cc_buf a buffer struct to be used with ->cc_buf_area as ->area member
value.
Implement qc_get_txb() to be called in place of qc_txb_alloc() to allocate
a struct "quic_cc_buf" buffer when the connection needs an immediate close
or a buffer struct if not.
Modify qc_prep_hptks() and qc_prep_app_pkts() to allow them to use such
"quic_cc_buf" buffer when an immediate close is required.

23 months agoMINOR: quic: Move some counters from [rt]x quic_conn anonymous struct
Frédéric Lécaille [Fri, 4 Aug 2023 15:02:25 +0000 (17:02 +0200)] 
MINOR: quic: Move some counters from [rt]x quic_conn anonymous struct

Move rx.bytes, tx.bytes and tx.prep_bytes quic_conn struct member to
bytes anonymous struct (bytes.rx, bytes.tx and bytes.prep member respectively).
They are moved before being defined into a bytes anonoymous struct common to
a future struct to be defined.

Consequently adapt the code.

23 months agoMINOR: quic: Amplification limit handling sanitization.
Frédéric Lécaille [Fri, 4 Aug 2023 13:10:56 +0000 (15:10 +0200)] 
MINOR: quic: Amplification limit handling sanitization.

Add a BUG_ON() to quic_peer_validated_addr() to check the amplification limit
is respected when it return false(0), i.e. when the connection is not validated.

Implement quic_may_send_bytes() which returns the number of bytes which may be
sent when the connection has not already been validated and call this functions
at several places when this is the case (after having called
quic_peer_validated_addr()).

Furthermore, this patch improves the code maintainability. Some patches to
come will have to rename ->[rt]x.bytes quic_conn struct members.

23 months agoCLEANUP: quic: Remove quic_path_room().
Frédéric Lécaille [Thu, 3 Aug 2023 09:01:59 +0000 (11:01 +0200)] 
CLEANUP: quic: Remove quic_path_room().

This function is definitively no more needed/used.

23 months agoBUG/MAJOR: http-ana: Get a fresh trash buffer for each header value replacement
Christopher Faulet [Fri, 4 Aug 2023 14:51:11 +0000 (16:51 +0200)] 
BUG/MAJOR: http-ana: Get a fresh trash buffer for each header value replacement

When a "replace-header" action is used, we loop on all headers in the
message to change value of all headers matching a name. The new value is
placed in a trash. However, there is a race here because if the message must
be defragmented, another trash is used. If several defragmentation are
performed because several headers must be updated at same time, the first
trash, used to store the new value, may be crushed. Indeed, there are only 2
pre-allocated trash used in rotation. and the trash to store the new value
is never renewed. As consequece, random data may be inserted into the header
value.

Here, to fix the issue, we must take care to refresh the trash buffer when
we evaluated a new header. This way, a trash used for the new value, and
eventually another way for the htx defragmentation. But that's all.

Thanks to Christian Ruppert for his detailed report.

This patch must be to all stable versions. On the 2.0, the patch must be
applied on src/proto_htx.c and the function is named
htx_transform_header_str().

23 months agoMINOR: h3: abort request if not completed before full response
Amaury Denoyelle [Fri, 4 Aug 2023 14:10:18 +0000 (16:10 +0200)] 
MINOR: h3: abort request if not completed before full response

A HTTP server may provide a complete response even prior receiving the
full request. In this case, RFC 9114 allows the server to abort read
with a STOP_SENDING with error code H3_NO_ERROR.

This scenario was notably reproduced with haproxy and an inactive
server. If the client send a POST request, haproxy may provide a full
HTTP 503 response before the end of the full request.

23 months agoBUILD: quic: fix wrong potential NULL dereference
Amaury Denoyelle [Fri, 4 Aug 2023 13:34:34 +0000 (15:34 +0200)] 
BUILD: quic: fix wrong potential NULL dereference

GCC warns about a possible NULL dereference when requeuing a datagram on
the connection socket. This happens due to a MT_LIST_POP to retrieve a
rxbuf instance.

In fact, this can never be NULL there is enough rxbuf allocated for each
thread. Once a thread has finished to work with it, it must always
reappend it.

This issue was introduced with the following patch :
  commit b34d353968db7f646e83871cb6b21a246af84ddc
  BUG/MEDIUM: quic: consume contig space on requeue datagram
As such, it must be backported in every version with the above commit.

This should fix the github CI compilation error.

23 months agoBUG/MINOR: quic: reappend rxbuf buffer on fake dgram alloc error
Amaury Denoyelle [Fri, 4 Aug 2023 13:37:29 +0000 (15:37 +0200)] 
BUG/MINOR: quic: reappend rxbuf buffer on fake dgram alloc error

A thread must always reappend the rxbuf instance after finishing
datagram reception treatment. This was not the case on one error code
path : when fake datagram allocation fails on datagram requeing.

This issue was introduced with the following patch :
  commit b34d353968db7f646e83871cb6b21a246af84ddc
  BUG/MEDIUM: quic: consume contig space on requeue datagram
As such, it must be backported in every version with the above commit.

23 months agoREGTESTS: Test SPLICE feature is enabled to execute script about splicing
Christopher Faulet [Fri, 4 Aug 2023 13:08:04 +0000 (15:08 +0200)] 
REGTESTS: Test SPLICE feature is enabled to execute script about splicing

There are 3 scripts relying on the splicing. We must take care the feature
is not explicitly disabled to execute them.

23 months agoREGTESTS: http: Create a dedicated script to test spliced bodyless responses
Christopher Faulet [Fri, 4 Aug 2023 12:54:17 +0000 (14:54 +0200)] 
REGTESTS: http: Create a dedicated script to test spliced bodyless responses

Splicing is not available on all platform. Thus a dedicated script is used
to check we properly skip payload for bodyless response when splicing is
used. This way, we are still able to test the feature with the original
script on all platform.

This patch fixes an issue on the CI introduced by commit ef2b15998
("BUG/MINOR: htx/mux-h1: Properly handle bodyless responses when splicing is
used"). It must be backported with the above commit.

23 months agoCLEANUP: stconn: Move comment about sedesc fields on the field line
Christopher Faulet [Thu, 22 Jun 2023 08:55:29 +0000 (10:55 +0200)] 
CLEANUP: stconn: Move comment about sedesc fields on the field line

Fields of sedesc structure were documented in the comment about the
structure itself. It was not really convenient, hard to read, hard to
update. So comments about the fields are moved on the corresponding field
line, as usual.

23 months agoBUG/MINOR: http-client: Don't forget to commit changes on HTX message
Christopher Faulet [Fri, 4 Aug 2023 12:28:23 +0000 (14:28 +0200)] 
BUG/MINOR: http-client: Don't forget to commit changes on HTX message

In the http-client I/O handler, HTX request and response are loaded from the
channels buffer. Some changes are preformed in these messages. So, we must
take care to commit changes into the underlying buffer by calling
htx_to_buf().

It is especially important when the HTX message becoms empty to be able to
quickly release the buffer.

This patch should be backported as far as 2.6.

23 months agoBUG/MEDIUM: quic: consume contig space on requeue datagram
Amaury Denoyelle [Fri, 4 Aug 2023 07:57:04 +0000 (09:57 +0200)] 
BUG/MEDIUM: quic: consume contig space on requeue datagram

When handling UDP datagram reception, it is possible to receive a QUIC
packet for one connection to the socket attached to another connection.
To protect against this, an explicit comparison is done against the
packet DCID and the quic-conn CID. On no match, the datagram is requeued
and dispatched via rxbuf and will be treated as if it arrived on the
listener socket.

One reason for this wrong reception is explained by the small race
condition that exists between bind() and connect() syscalls during
connection socket initialization. However, one other reason which was
not thought initially is when clients reuse the same IP:PORT for
different connections. In this case the current FD attribution is not
optimal and this can cause a substantial number of requeuing.

This situation has revealed a bug during requeuing. If rxbuf contig
space is not big enough for the datagram, the incoming datagram was
dropped, even if there is space at buffer origin. This can cause several
datagrams to be dropped in a series until eventually buffer head is
moved when passing through the listener FD.

To fix this, allocate a fake datagram to consume contig space. This is
similar to the handling of datagrams on the listener FD. This allows
then to store the datagram to requeue on buffer head and continue.

This can be reproduced by starting a lot of connections. To increase the
phenomena, POST are used to increase the number of datagram dropping :

$ while true; do curl -F "a=@~/50k" -k --http3-only -o /dev/null https://127.0.0.1:20443/; done

23 months agoBUG/MINOR: htx/mux-h1: Properly handle bodyless responses when splicing is used
Christopher Faulet [Wed, 2 Aug 2023 07:48:55 +0000 (09:48 +0200)] 
BUG/MINOR: htx/mux-h1: Properly handle bodyless responses when splicing is used

There is a mechanisme in the H1 and H2 multiplexer to skip the payload when
a response is returned to the client when it must not contain any payload
(response to a HEAD request or a 204/304 response). However, this does not
work when the splicing is used. The H2 multiplexer does not support the
splicing, so there is no issue. But with the mux-h1, when data are sent
using the kernel splicing, the mux on the server side is not aware the
client side should skip the payload. And once the data are put in a pipe,
there is no way to stop the sending.

It is a defect of the current design. This will be easier to deal with this
case when the mux-to-mux forwarding will be implemented. But for now, to fix
the issue, we should add an HTX flag on the start-line to pass the info from
the client side to the server side and be able to disable the splicing in
necessary.

The associated reg-test was improved to be sure it does not fail when the
splicing is configured.

This patch should be backported as far as 2.4..

23 months agoMEDIUM: stream: Reset response analyse expiration date if there is no analyzer
Christopher Faulet [Tue, 1 Aug 2023 06:25:01 +0000 (08:25 +0200)] 
MEDIUM: stream: Reset response analyse expiration date if there is no analyzer

When the stream expiration date is computed at the end of process_stream(),
if there is no longer analyzer on the request channel, its analyse
expiration date is reset. The same is now performed on the response
channel. This way, we are sure to not inherit of an orphan expired date.

This should prevent spinning loop on process_stream().

23 months agoBUG/MEDIUM: bwlim: Reset analyse expiration date when then channel analyse ends
Christopher Faulet [Tue, 1 Aug 2023 06:16:42 +0000 (08:16 +0200)] 
BUG/MEDIUM: bwlim: Reset analyse expiration date when then channel analyse ends

The bandwidth limitation filter sets the analyse expiration date on the
channel to restart the data forwarding and thus limit the bandwidth.
However, this expiration date is not reset on abort. So it is possible to
reuse the same expiration date to set the stream one. If it expired before
the end of the stream, this will lead to a spinning loop on process_stream()
because the task expiration date is always set in past.

To fix the issue, when the analyse ends on a channel, the bandwidth
limitation filter reset the corrsponding analyse expiration date.

This patch should fix the issue #2230. It must be backported as far as 2.7.

23 months agoBUILD: cfgparse: keep a single "curproxy"
Willy Tarreau [Tue, 1 Aug 2023 09:18:00 +0000 (11:18 +0200)] 
BUILD: cfgparse: keep a single "curproxy"

Surprisingly, commit 00e00fb42 ("REORG: cfgparse: extract curproxy as a
global variable") caused a build breakage on the CI but not on two
developers' machines. It looks like it's dependent on the linker version
used. What happens is that flt_spoe.c already has a curproxy struct which
already is a copy of the one passed by the parser because it also needed
it to be exported, so they now conflict. Let's just drop this unused copy.

23 months agoMINOR: acl: add acl() sample fetch
Patrick Hemmer [Mon, 24 Jul 2023 18:10:13 +0000 (14:10 -0400)] 
MINOR: acl: add acl() sample fetch

This provides a sample fetch which returns the evaluation result
of the conjunction of named ACLs.

23 months agoREORG: cfgparse: extract curproxy as a global variable
Patrick Hemmer [Thu, 27 Jul 2023 15:09:14 +0000 (11:09 -0400)] 
REORG: cfgparse: extract curproxy as a global variable

This extracts curproxy from cfg_parse_listen so that it can be referenced by
keywords that need the context of the proxy they are being used within.

23 months agoCLEANUP: acl: remove cache_idx from acl struct
Patrick Hemmer [Thu, 27 Jul 2023 15:39:09 +0000 (11:39 -0400)] 
CLEANUP: acl: remove cache_idx from acl struct

It isn't used and never has been.

23 months agoBUG/MINOR: quic+openssl_compat: Non initialized TLS encryption levels
Frédéric Lécaille [Mon, 31 Jul 2023 13:07:06 +0000 (15:07 +0200)] 
BUG/MINOR: quic+openssl_compat: Non initialized TLS encryption levels

The ->openssl_compat struct member of the QUIC connection object was not fully
initialized. This was done on purpose, believing that ->write_level and
->read_level member was initialized by quic_tls_compat_keylog_callback() (the
keylog callback) before entering quic_tls_compat_msg_callback() which
has to parse the TLS messages. In fact this is not the case at all.
quic_tls_compat_msg_callback() is called before quic_tls_compat_keylog_callback()
when receiving the first TLS ClientHello message.

->write_level and ->read_level was not initialized to <ssl_encryption_initial> (= 0)
as this is implicitely done by the originial ngxinx wrapper which calloc()s the openssl
compatibily structure. This could lead to a crash after ssl_to_qel_addr() returns
NULL when called by ha_quic_add_handshake_data().

This patch explicitely initialializes ->write_level and ->read_level to
<ssl_encryption_initial> (=0).

No need to backport.