MINOR: makefile: add CROSS_TOOLCHAIN to prefix the target's tools
Cross-building also needs the target's nm, objcopy and readelf, since the
host ones do not know a foreign object. Instead of setting each one,
CROSS_TOOLCHAIN=arm-linux-gnueabi- prefixes them all, giving arm-linux-
gnueabi-gcc, -nm and so on. Empty by default.
OPTIM: sym: skip the embedded table when the file provided the symbols
The embedded compressed table and the file's .symtab describe the same
local functions, the latter being a superset, so decompressing it when the
file is readable is pure loss, which is the common case.
Only decompress it when the file gave us nothing, i.e. once stripped or
unreachable. This is what the table is for and saves inflating about 1.5 MB
per object at every start.
MINOR: sym: also read the compressed symbol tables from the ELF files
Distributions ship libraries stripped, keeping local symbols in a
compressed .gnu_debugdata section. It is not loaded in memory, so it was
only reachable for objects mapping it in a segment of their own, i.e. the
ones we build, leaving system libraries unnamed.
Look for that section in the ELF files already mapped to read their
.symtab, and hand it to the same decompressor. A separate debug file still
wins as it is more complete, and the file mappings are released right away
since names point into the decompressed image. On Fedora this names
libcrypto, libssl, liblua, libm, libcrypt and libz, turning
"liblua-5:luaopen_package+0x41a0/0x205" into "liblua-5:luaV_execute+0x90f".
MINOR: sym: read local symbols and debug info from ELF files
The dynamic linker and the embedded table only cover exported symbols and
our own objects, so static functions of system libraries still show as the
nearest export plus a large offset.
When files are readable, parse their .symtab, which lists local functions
too. On Linux the executable is read from /proc/self/exe, so a moved or
replaced file, or a relative exec path after a chdir (-C), still designates
the running image; a file whose program headers, or build-id when present,
do not match the image is ignored. Stripped objects fall back to the
separate debug file, looked up by GNU build-id and .gnu_debuglink as gdb
does, in /usr/lib/debug and the "tune.debug-file-directory" directories,
skipping a stale one whose build-id or CRC32 does not match. Files are
mapped only for the build.
MEDIUM: sym: store a compressed symbol table in a loadable section
Implement the MiniDebugInfo, and store the symtab, compressed with xz,
into the .gnu_debugdata section, provided the needed utilities are
available, and we're building an ELF binary.
Makes it a loadable section, so that it is always available, and teach
haproxy to parse it, so that we will always get all available symbols
for binaries built that way, even if we can't even read the binary
itself.
Import the .xz decoder from xz-embedded v2024-12-30 (0BSD). Only what is
needed for a plain LZMA2 stream is taken: the stream and LZMA2 decoders and
the two check modules; BCJ filters, SHA-256 and the kernel glue are left
out. The Makefile enables CRC64, the integrity check "xz" uses by default,
and links the decoder only when the compiler produces ELF objects, since
its only user in sym.c is not compiled for another object format.
Files are imported verbatim for later resyncing: C files in src/, headers
in include/import/. As the C files include their private headers quoted,
src/ carries small wrappers including the real ones, as src/cebtree-prv.h
does. include/import/xz_config.h maps the few kernel calls onto malloc()
and string functions.
MINOR: sym: add helpers to collect the symbols of an ELF image
The next commits read symbols from ELF images (an embedded compressed table
and the object's file), and both need the same primitives, so introduce
them here: validate an ELF header and its section header table, keep every
read inside the image, find .symtab and its string table, and collect its
function symbols into the builder, applying the object's load address.
Parsing an ELF image needs no help from the dynamic linker, so they are
gated on __ELF__ alone; only enumerating the loaded objects needs
dl_iterate_phdr().
They are tagged __maybe_unused because their users come in later commits,
which may be picked independently.
MINOR: sym: collect the symbols known to the dynamic linker
The dynamic linker keeps .dynsym/.dynstr of every loaded object mapped for
relocation. Where dl_iterate_phdr() exists, walk each object's PT_DYNAMIC
to collect its exported function symbols from the memory image, plus its
address range from PT_LOAD. This works even when files are unreadable (e.g.
after chroot) and gives what dladdr() sees, but lock-free.
When an address falls past the nearest lower symbol, resolve_sym_name() now
prints "sym+off/size" instead of a bare offset, easier to feed to gdb. Add
"tune.disable-elf-symbols" to restore the previous output.
MINOR: sym: add an in-process symbol table for backtraces
Build an address-sorted table at boot and publish immutable snapshots for
lock-free, signal-safe lookups. Seed it with the existing known functions.
The table is built while the process is still starting and no thread is
running yet, so the build needs no locking.
The table is sorted using a radix sort.
CLEANUP: sym: move symbol resolution out of tools.c
Group symbol resolution and the shared dynamic-loader helpers in sym.c
before extending them with the in-process table: resolve_sym_name(),
the dladdr() and dlsym() wrappers, static-build detection, the executable
path, and the dlopen() interposer which verifies the loaded symbols
through the same helpers. Their declarations, and the backtrace helpers
that report the frames they resolve, move into a new sym.h which their
callers now include.
No functional change.
Willy Tarreau [Thu, 1 Oct 2026 12:49:21 +0000 (14:49 +0200)]
[RELEASE] Released version 3.5-dev8
Released version 3.5-dev8 with the following main changes :
- BUG/MEDIUM: task: Do not destroy a task that is not ours in task_destroy
- BUG/MEDIUM: http-client: Don't use the httpclient context if it was released
- MINOR: http-client: Add an option to not stop the reception of the response
- BUG/MEDIUM: ssl/ocsp: Set HTTPCLIENT_O_RES_ACCUM option on the http-client
- BUG/MEDIUM: acme: Set HTTPCLIENT_O_RES_ACCUM option on the http-client
- MINOR: http-client: Add a function to notify some data were consumed
- BUG/MEDIUM: http-client/cli: Notify the http-client when the response is consumed
- MINOR: ssl/ocsp: Remove the useless res_payload callback of the http-client
- BUG/MINOR: ssl/ocsp: Report an error when an empty OCSP response is received
- BUG/MEDIUM: htx: Fix the position returned by htx_defrag()
- BUG/MINOR: tools: Don't try to anonymize a NULL string
- BUG/MAJOR: htx: Check the header/trailer length limits when one is updated
- MINOR: htx: Add macros for the header/trailer name and value max lengths
- BUG/MINOR: mux-h2: Reject trailers received on a tunneled stream
- MEDIUM: htx: Skip UNUSED blocs when getting previous,next and first blocks
- MEDIUM: htx/tree-wide: Remove tests on HTX_BLK_UNUSED when possible
- MEDIUM: htx: Insert headers and trailers before corresponding end-of block
- MINOR: htx: Add support for flags on HTX blocks
- MAJOR: htx: Use htx_set_eom() instead of setting HTX_FL_EOM by hand
- MAJOR: htx: Rename HTX_FL_EOM into HTX_FL_HAS_EOM
- MEDIUM: htx: Add HTX_BLK_FL_EOM flag and set it on last block of the message
- MINOR: compression: Rely on HTX block flags to detect the end of message
- MINOR: fcgi-app: Stop on last HTX block when getting the payload size
- MINOR: mux-h2: Use flags of HTX blocks to detect end of message during sending
- MINOR: mux-h1: Use flags of HTX blocks to detect end of message during sending
- MINOR: mux-fcgi: Use flags of HTX blocks to detect end of message during sending
- MEDIUM: h3: Use flags of HTX blocks to detect end of message during sending
- MINOR: stats-html: Detect a complete request by testing flags on the tail bloc
- MINOR: hlua: Use flags of HTX blocks to detect EOM from an HTTP applet
- MINOR: htx: Add function to know if the tail block carry EOM flag
- MAJOR: tree-wide: No longer use HTX_FL_HAS_EOM to detect end of message
- MAJOR: htx: Remove HTX_FL_HAS_EOM flag
- MEDIUM: mux-h2: Don't mix HTTP and tunneled data in the same buffer
- MEDIUM: htx: Harden HTX API to avoid invalid uses when EOM was reached
- MEDIUM: htx: Remove usless htx_is_unique_blk() function
- DOC: internals: Update the HTX API documentation
- MINOR: htx: Rename htx_has_eom() into htx_msg_ended()
- MINOR: htx: Add HTX_BLK_RAW_DATA block type
- MINOR: htx: Add the block type as argument of the data insertion functions
- MINOR: htx: Handle RAW_DATA blocks like DATA blocks in the HTX API
- MEDIUM: htx: Allow tunneled data to be added in a message already ended
- MINOR: mux-h1: Handle RAW_DATA blocks when sending data
- MINOR: mux-h2: Handle RAW_DATA blocks when sending data
- MEDIUM: mux-h1: Use RAW_DATA blocks to store tunneled data
- MEDIUM: mux-h2: Use RAW_DATA blocks to store tunneled data
- Revert "MEDIUM: mux-h2: Don't mix HTTP and tunneled data in the same buffer"
- MINOR: http-htx: Really test the EOM flag presence in internal.htx.has_eom
- MINOR: http-htx: Report the payload of RAW_DATA blocks in internal.htx_blk.data
- MINOR: mux-h1: Only handle RAW_DATA blocks when emitting tunneled data
- DOC: internals: Update the HTX API documentation for tunneled data
- REGTESTS: http-messaging: Add a test for the CONNECT tunnels
- BUG/MINOR: hlua: release private Lua rule data on teardown
- BUG/MINOR: ssl: Fix possible null deref on the SSL context in ssl_sock_to_buf()
- MEDIUM: ssl: add ocsp-update.timeout global option
- BUG/MINOR: http-client: Convert server timeout ticks when setting it
- BUG/MEDIUM: h1-htx: Don't report EOM for H1 interim responses during parsing
- BUG/MINOR: mux-h2: Don't expect more HTX data on 1xx interim responses
- REG-TESTS: http-messaging: Add a script to test interim responses for H1/H2
- BUG/MEDIUM: log: reserve the trailing 0 in CBOR int and bool encoders
- BUG/MEDIUM: http-htx: don't build the new authority in a rotating trash chunk
- BUG/MEDIUM: http-act: ignore "capture len" rules evaluated from a backend
- BUG/MEDIUM: tcp-rules: ignore "capture len" rules evaluated from a backend
- BUG/MINOR: http-ana: count response body failures only once in http_fail_cnt
- BUG/MINOR: http-fetch: http_auth_bearer() must not match a missing header
- BUG/MINOR: http-fetch: fix the space check of http_auth_bearer(<hdr>)
- BUG/MINOR: mux-h1: only mark C-L and T-E as sent once the header is emitted
- BUG/MINOR: tcpcheck: refresh the start-line after updating the authority
- CLEANUP: mux-h2: replace a non-UTF-8 character in a comment
- BUG/MINOR: http-ana: restore the transaction status after early hints
- REG-TESTS: http-messaging: Send one request per h2 client in h2_trailers.vtc
- BUG/MEDIUM: h3: Set FIN when last DATA block is sent via zero-copy
- BUG/MAJOR: mux_quic: fix leak on RESET_STREAM reception
- BUG/MEDIUM: mux_quic: activate timeout on FE init
- BUG/MINOR: h2: reject :protocol pseudo-header in H2 responses
- MINOR: ssl: cleanse TLS ticket keys before freeing
- MINOR: init: also set the worker-id in file-less mode
- BUG/MINOR: acme: exact domain match in acme_challenge_ready()
- BUG/MINOR: ssl: copy curves, sigalgs and client_sigalgs in srv_ssl_settings_cpy()
- BUG/MINOR: ssl: free curves, sigalgs and client_sigalgs in ssl_sock_free_srv_ctx()
- BUG/MINOR: ssl: free curves, sigalgs and client_sigalgs in srv_parse_*()
- MINOR: log: pass the input end to the _lf_encode_bytes() byte encoders
- MINOR: log: add the +utf8 encoding option to let valid UTF-8 pass
- BUG/MAJOR: mux_quic: fix potential crash on RESET_STREAM receive
- BUG/MEDIUM: sink: reconnect attempt not working after session lasted more than ~25 days
- BUG/MEDIUM: server: skip log backend addr checks for internal proxies
- REGTESTS: log: check that a ring's internal server may target a UNIX socket
- Revert "CLEANUP: proxy: mention that px->conn_retries isn't relevant in some cases"
- BUILD: haring: fix compile issue due to nop warning.
- BUG/MINOR: chunk: Allow large chunks uses from large const buffers
- MINOR: htx: Remove htx_move_blk_before() function
- BUG/MAJOR: mux-h2: Preserve raw data on aborted frontend tunnels
- BUG/MEDIUM: mux-h1: Don't subscribe for sends while output is blocked
- BUG/MINOR: mux-h1: Discard pending raw data after a tunnel rejection
- MINOR: stick-table: provide a function to estimate on-wire data size
- MINOR: stick-table: calculate the on-wire size of each key
- BUG/MEDIUM: stick-table: restrict key sizes to what fits in a buffer
- REGTESTS: http-messaging: consume window update before txdata
- BUG/MEDIUM: pattern: don't dereference static_pattern's data when not filling it
- BUG/MINOR: jwe: Buffer overflow when filling fake cek in case of RSA1.5
- BUG/MINOR: jwe: Avoid buffer overflow in fake cek (RSA1.5)
- BUG/MINOR: startup: don't chroot by default when set-dumpable is set
- BUG/MINOR: http: do not consume the delimiter of a truncated q-factor
- BUG/MINOR: sample: apply the ms_/us_ time offsets in the input unit
- BUG/MINOR: sample: zero-pad the fractional part emitted by http_date()
- BUG/MINOR: sample: make quic_enabled() always succeed
- BUG/MINOR: http-ana: return a 502 when checkcache blocks a response
- BUG/MINOR: stick-table: do not count the lookup itself in table_trackers()
- BUG/MINOR: payload: always report a boolean from req.ssl_ec_ext
- BUG/MINOR: http-fetch: do not count Set-Cookie attributes as cookies
- BUG/MEDIUM: tcpcheck: do not run a regex on an unallocated buffer
- BUG/MINOR: sample: return the decoded JWT part when no path is given
- DOC: config: mention other responses not blocked by option checkcache
- DOC: config: fix doc for hex2i() converter on unparsable input
- DOC: config: mention that url_ip and url_port fail when passed a name
- REGTESTS: converter: add a test for the arithmetic and bitwise converters
- REGTESTS: converter: add a test for the string trimming and parsing converters
- REGTESTS: converter: add a test for the date formatting converters
- REGTESTS: converter: cover all the map match methods and output types
- REGTESTS: http-rules: test the run-time ACL and map update actions
- REGTESTS: http-rules: test the ACL and map management commands of the CLI
- REGTESTS: sample_fetches: add a test for the HTTP response fetches
- REGTESTS: sample_fetches: add a test for the HTTP request fetches
- REGTESTS: sample_fetches: add a test for the connection address fetches
- REGTESTS: sample_fetches: add a test for the proxy and server state fetches
- REGTESTS: stick-table: test the general purpose counters and tags
- REGTESTS: stick-table: test the traffic counters of the stick counters
- REGTESTS: http-rules: test the HTTP authentication action, fetches and ACL
- REGTESTS: log: test the transaction state fetches and the logging actions
- REGTESTS: http-rules: test the binary header manipulation actions
- REGTESTS: http-rules: test replace-uri and the header replacement actions
- REGTESTS: tcp-rules: test the set-src, set-dst and port rewriting actions
- REGTESTS: sample_fetches: add a test for the constant and process fetches
- REGTESTS: converter: add a test for the when() and debug() converters
- REGTESTS: http-errorfiles: test the errorloc directives
- REGTESTS: proxy: test the "option checkcache" response filter
- REGTESTS: proxy: test the connection retries, retry-on and redispatch
- REGTESTS: http-rules: test the tarpit, deny and silent-drop actions
- REGTESTS: log: test the log filtering options of a frontend
- REGTESTS: proxy: test monitor-uri and the monitor fail condition
- REGTESTS: cli: add a smoke test for the read-only CLI commands
- REGTESTS: cli: test the commands which change the run-time settings
- REGTESTS: http-rules: test the declared capture slots
- REGTESTS: tcp-rules: test the raw payload fetches
- REGTESTS: sample_fetches: pin one thread in the connection address test
- REGTESTS: sample_fetches: pin one thread in the proxy state test
- REGTESTS: server: test the server settings reported by the runtime
- REGTESTS: checks: test the health adjusting from observed traffic
- REGTESTS: cli: test the privilege levels of the CLI
- REGTESTS: connection: test the expect-proxy action
- REGTESTS: sample_fetches: test the raw TLS hello fetches
- REGTESTS: connection: do not race with the reset in the expect-proxy test
- REGTESTS: ssl: test the ssl_fc_* and ssl_bc_* connection fetches
- REGTESTS: protobuf: add a test for the ungrpc converter
- REGTESTS: http-messaging: test the options dropping the HTTP trailers
- REGTESTS: protobuf: cover all the field types of the ungrpc converter
- REGTESTS: sample_fetches: test req.ssl_ver on an SSLv2 CLIENT-HELLO
- REGTESTS: sample_fetches: test the req.ssl_alpn fetch
- REGTESTS: ssl: test ssl_c_used, ssl_c_san and ssl_c_ca_err_depth
- REGTESTS: stick-table: read the counters through sc1_ and sc2_ as well
- REGTESTS: stick-table: cover the remaining gpc and gpt spellings
- REGTESTS: http-rules: cover the rfc7239_nn and rfc7239_np converters
- REGTESTS: proxy: test the disable-l7-retry action
- REGTESTS: connection: test the NetScaler CIP protocol
- REGTESTS: tcp-rules: test the RDP cookie fetches
- REGTESTS: tcp-rules: read the payload through the deprecated spellings
- REGTESTS: sample_fetches: test the TCP_INFO counters
- REGTESTS: sample_fetches: check the rtt headers this test was filling
- REGTESTS: sample_fetches: test the req.cook() fetch itself
- REGTESTS: sample_fetches: test fe_tarpit_timeout
- REGTESTS: sample_fetches: test res.body, res.body_len and server_status
- REGTESTS: sample_fetches: test the request_date fetch
- REGTESTS: sample_fetches: test the stream state fetches over HTTP/2
- REGTESTS: ssl: test the wait-for-handshake action and fc.timer.handshake
- REGTESTS: http-messaging: test the option httpclose
- REGTESTS: http-messaging: test the option http-no-delay
- REGTESTS: http-messaging: check that a truncated response loses no byte
- REGTESTS: sample_fetches: do not race with the server close in proxy_state
- REGTESTS: sample_fetches: make the raw hello test parallel-safe
- REGTESTS: http-messaging: widen the abort window of the abortonclose test
- REGTESTS: log: serve the transaction state test on a single connection
- REGTESTS: http-messaging: give each frontend its own syslog server
- REGTESTS: converter: serve the two sha2 requests on one connection
- REGTESTS: http-messaging: make the /c5 abort land during the retries
- REGTESTS: http-rules: one connection per request in the replace-uri test
- MINOR: vars: Add a helper to parse a variable scope name
- MINOR: vars: Add 'var_name_is_valid' helper function
- MEDIUM: vars: Add the set-vars-from-map action
- REGTESTS: vars: Add a test for the set-vars-from-map action
Add a new action, set-vars-from-map(<scope>,<map-file>[,<type>]), that
loads a two-column (key/value) map file and sets one variable per entry,
named "<scope>.<key>", to the entry's value. The scope is one of sess,
txn, req, res or check and the variable name is relative to it.
The new "tune.vars.from-map.max" global option defines the maximum
number of entries taken into account in each map. It default to 100.
Values are read as strings and converted according to the optional
<type>, which may be "str" (default), "int" or "bool". Integer
conversion is strict, so an entry with trailing garbage is rejected
rather than silently truncated, and booleans accept 0/1 as well as
true/false in any case. An entry that cannot be parsed is skipped
without aborting the remaining entries.
The file is loaded as a pattern reference flagged PAT_REF_SMP, or reused
if it was already loaded, in which case it must also have been loaded as
a two-column file. The action is registered for all rule sets that
already support set-var.
MINOR: vars: Add 'var_name_is_valid' helper function
This function checks a variable name (scope excluded) and fails if any
invalid character is found. Variable names must contain alphanumeric
characters, '-', '_' or '.'.
REGTESTS: http-rules: one connection per request in the replace-uri test
The verification campaign caught this test with "HTTP header is
incomplete" when the whole suite runs with "--j 32" on a loaded
machine. Its first client was sending its six requests over a single
connection while the server answers each of them in its own iteration,
so the server closed right when the next request was sent over the
connection haproxy reuses, and the client got that close instead of a
response.
The test is about rewriting the request target, not about keep-alive,
so the six requests are now sent by six clients, each on its own
connection, which is also what the three other clients of the test
already do. Twenty-four runs in parallel with the machine loaded pass.
REGTESTS: http-messaging: make the /c5 abort land during the retries
The previous attempt at making this case robust, which raised the
connect timeout so that the client abort is noticed before the retries
are over, was not enough: the client closes immediately after its
request, so under load the close is sometimes seen while the request is
still being parsed, and the transaction is then logged as "CH--" with a
400 instead of the expected "CC--".
The client now waits fifty milliseconds before closing, which leaves
haproxy the time to read the request and to start connecting, and the
raised connect timeout gives the abort a full second to be noticed.
Twenty runs in parallel with the machine loaded pass.
REGTESTS: converter: serve the two sha2 requests on one connection
The final validation campaign caught this test with "HTTP header is
incomplete" when the whole suite runs with "--j 32" on a loaded
machine. Its client sends its two requests over a single connection
while the server answers each of them in its own iteration, so it
closes after the first response, exactly when the second request is
sent over the connection haproxy reuses, and the client gets that close
instead of a response.
This is the same shape which was fixed in the transaction state test:
the server now keeps its connection for both requests. Twenty runs in
parallel with the machine loaded pass.
REGTESTS: http-messaging: give each frontend its own syslog server
The two frontends of this test were logging to the same syslog server
and their two messages were expected in order. A stream is logged when
it ends, which may be after the next client has been served by the
other frontend, so that order is not guaranteed. This is the same race
which was caught in the raw hello test when the suite runs in parallel,
found here by inspection rather than by a failure.
REGTESTS: log: serve the transaction state test on a single connection
Running the whole suite with "--j 32" caught the client of this test
receiving a close instead of the response to its second request. The
server was answering each of the four requests in its own iteration,
so it closed its connection after every response, and the debug trace
of the failing run shows the close reaching haproxy just as the next
request was sent over the connection it had reused:
The four requests are now served on a single connection, which is what
a keep-alive server does, so nothing closes in the middle of the
client's series. Twenty-four runs in parallel with the machine loaded
pass where the previous version failed twice out of sixteen.
REGTESTS: http-messaging: widen the abort window of the abortonclose test
Running the whole suite with "--j 32" caught the /c5 request being
answered with the 503 of the exhausted connection retries and logged
"SC--" where the test expects the "CC--" of a client abort.
That request is sent by a client which closes immediately, and the
abort has to be noticed before the retries are over, which the connect
timeout of a hundred milliseconds does not guarantee on a process
starved by thirty-two parallel tests: the log of the failing run shows
the whole retry sequence completed in exactly those 100 ms.
The connect timeout of the section that request uses is raised to one
second, which leaves ten times more room and does not make the test
any slower, since the expected outcome is precisely that the abort
cuts the retries short. The other section, whose request is expected
to wait for all the retries, is left untouched.
REGTESTS: sample_fetches: make the raw hello test parallel-safe
Running the suite with "--j 4" caught the client of the third
ClientHello getting an empty response. The single server was reading
only the 47 bytes the three hellos have in common, so the rest of each
of them was still unread when it closed its socket, which makes the
system emit a reset instead of a FIN, and that reset discards the
ServerHello which haproxy had not read yet.
Each hello now has its own server, reading exactly the number of bytes
its client sends, and the backend distributes the three connections in
order with round robin. Nothing is left unread, so the close is a plain
FIN again.
That uncovered a second race: the log of a stream is emitted when the
stream ends, which may be after the next client has been served by the
other frontend, and the third message of the first frontend was
sometimes overtaken by the first message of the second one. The two
frontends now log to their own syslog server, so only their own series
has to be ordered, and each client of the first frontend waits for the
close before ending, which finishes its stream.
Thirty-two runs in parallel pass where the previous version failed
about once in twenty.
REGTESTS: sample_fetches: do not race with the server close in proxy_state
A full run of the suite caught srv_conn() reporting zero where the test
expects one. The server closed its connection as soon as it had
answered, and when that close reached haproxy before the response rules
were evaluated, the server connection was already released and the
counter had gone back to zero.
The server now holds its connection until the client has read the
response, which is necessarily after the rules have run.
REGTESTS: http-messaging: check that a truncated response loses no byte
When a server announces a content-length and closes before having sent
that many bytes, haproxy can only forward what it received and abort
the client side. Whether everything which was sent really reaches the
client is a recurring question when such reports are analysed, since a
byte lost there is indistinguishable from a server truncating its
response, and nothing covered it.
The first four cases announce 100000 bytes and stop after 40010, which
spans more than two 16 kB buffers so that the data cannot sit in a
single one and the forwarding has to run several times. They differ by
the way the server ends: an H1 server writing everything at once then
shutting down, the same one writing the last bytes only once the client
has drained the bulk, so that they reach haproxy in the same event as
the close, an H2 server closing without any END_STREAM, and an H2
server setting END_STREAM 60000 bytes too early. The fifth one repeats
the first with a body of 200000 bytes and a client which does not read
for half a second, so that the close lands while the data is still
queued; a larger body would stall it further but vtest asserts in its
own logging code somewhere below the megabyte.
The client counts the bytes it receives, so a missing one leaves it
waiting until the close ends the test with a failure. Asking for one
byte more than the server sent was verified to fail in each of the five
cases.
The two HTTP/2 servers never read the WINDOW_UPDATE frames haproxy
sends them, so closing their socket right away makes the system emit a
reset which discards what is still in flight. This was caught by
running the test 16 times in parallel, and is avoided by shutting the
socket down, which is what a truncating server does anyway, and by
letting vtest close only once the client has seen the whole exchange.
The client is an HTTP/1 one in every case: haproxy always ends such a
stream with an RST_STREAM, which stops "rxdata -all", and the number of
DATA frames it emits before that depends on how its buffers were
filled, so an HTTP/2 client can neither read to the end of the stream
nor use a fixed number of reads.
REGTESTS: http-messaging: test the option http-no-delay
The option was listed as out of reach because it only changes when the
data are pushed out and because no vtest assertion expresses a duration.
It turns out haproxy measures that duration itself: %Tt reports the
whole transaction, so it is enough to build an exchange whose length is
exactly the delay being looked for and to let the syslog expect count
the digits.
The server sends a chunked response, then its first chunk alone, and
only terminates the message once a barrier tells it the client has
received that chunk. Without the option, the incomplete message is held
by the system for its cork timeout, which is 200 ms on Linux, and the
transaction lasts that long; with the option it lasts zero millisecond.
Five runs here gave 200 to 222 ms against 0, so a three digit value
against a one or two digit one leaves a comfortable margin.
The delay comes from MSG_MORE, which is compiled out as a no-op where
it does not exist, leaving nothing to observe, hence the excluded
targets.
REGTESTS: http-messaging: test the option httpclose
The option only appeared in other tests as a convenient way to get rid
of the idle connections, its own behavior was never checked, and
"option http-pretend-keepalive", which modifies it, was not covered
either.
The five proxies here cover the four ways it can be declared. The side
whose connection is closed is the one the option is declared on: the
client one for a frontend, the server one for a backend, both for a
listener, and the "no option" form undoes what a defaults section had
set. The header it adds in that direction is checked by the server for
the request side and by the client for the response side.
Whether the client connection was closed is directly observable, and
whether the server one was is reported by bc_reused, since a connection
which was closed cannot be reused by the next request: the frontend
case reuses its server connection while the listener case cannot. This
is also how "http-pretend-keepalive" is checked, the server not seeing
the header while its connection is closed all the same.
REGTESTS: ssl: test the wait-for-handshake action and fc.timer.handshake
Both were listed as reachable from one of the SSL tests, the action
because it only makes sense on a connection which performs a handshake,
and the timer because it stays at zero elsewhere. They join the test
which already reads the ssl_fc_* family on a TLSv1.2 connection. The
action is a no-op there since the handshake is over before the rules
run, no early data being involved, so it only checks that it is
accepted and does not disturb the exchange.
REGTESTS: sample_fetches: test the stream state fetches over HTTP/2
fs.id was only covered by a QUIC test, and bs.id, fs.aborted,
bs.aborted, fs.rst_code, bs.rst_code, fc_settings_streams_limit,
bc_settings_streams_limit and bc_glitches had no coverage at all. They
describe the multiplexed stream and its connection, so they need HTTP/2
on both sides, which vtest provides.
The first listener runs two streams over the same pair of connections,
which shows the stream ids of both sides and the two stream limits, the
frontend one being what haproxy announces and the backend one what the
server announced. The second listener has its stream reset by the
client and the third one by the server, and since no response reaches
the client in those two cases, the abort flag and the reset code are
read from the log.
REGTESTS: sample_fetches: test the request_date fetch
request_date is the accept date plus the handshake and idle times, so
it is read in seconds, in milliseconds, and as a difference with the
accept date it derives from, which is zero here since the request
follows the accept immediately.
REGTESTS: sample_fetches: test res.body, res.body_len and server_status
The response test covered res.body_size but neither the body itself nor
its length, and not the status as it was received from the server. The
two body fetches only see what has already been received, so an
"http-response wait-for-body" is added in front of them to make the
result deterministic.
The proxy state test reported fe_client_timeout but not its tarpit
counterpart. The frontend gets an explicit "timeout tarpit" so that the
value read back is not the "timeout connect" it silently defaults to.
REGTESTS: sample_fetches: test the req.cook() fetch itself
The cookie test only used the _cnt, _val and _names variants, while
req.cook(), which returns the value of a cookie as a string, was never
read. It is added with a name, without any, which takes the first
cookie of the header, and with a name which is not there.
REGTESTS: sample_fetches: check the rtt headers this test was filling
Three of the four headers the test sets were never read back, so only
bc_rtt was really checked. They are all matched against an integer now,
which is all that can be asserted on a timing value.
REGTESTS: sample_fetches: test the TCP_INFO counters
fc_unacked, fc_sacked, fc_lost, fc_retrans, fc_fackets and
fc_reordering had no coverage. Their values depend on what happened on
the network, so, as the notes said, only their presence and their type
can be asserted, which is what the existing rtt test does for fc_rtt.
They get a file of their own rather than joining that test because
three of them do not exist on macOS, which forces an "#EXCLUDE_TARGETS"
marker that fc_rtt and fc_rttvar do not need.
REGTESTS: tcp-rules: read the payload through the deprecated spellings
The notes listed the historical "payload", "payload_lv" and "req_len"
aliases, without the "req."/"res." prefix, as the only part of this
family still left out. They are read next to the modern spellings and
must report the same values, and since the unprefixed ones work in both
directions, the very same "payload" keyword is also evaluated from a
tcp-response rule on the response buffer.
req.rdp_cookie, req.rdp_cookie_cnt, their deprecated rdp_cookie and
rdp_cookie_cnt spellings and the req_rdp_cookie/req_rdp_cookie_cnt ACL
forms had no coverage. The notes listed the test as doable, the only
obstacle being that the RDP connection request has to be built by hand,
which sendhex does here: the parser only skips the 11 bytes of the TPKT
and X.224 headers and looks for a "Cookie:" line behind them, without
checking any of the lengths.
Four packets are sent to a TCP frontend which reports the fetches in
its log: one carrying "mstshash=hello", one whose line is not a cookie,
one carrying another cookie name, which only the unnamed form returns,
and one with extra spaces between the colon and the name.
REGTESTS: connection: test the NetScaler CIP protocol
Neither the "accept-netscaler-cip" bind keyword nor the
"tcp-request connection expect-netscaler-cip layer4" action had any
coverage, the notes listing them as doable now that the expect-proxy
test shows the pattern, the only obstacle being that the CIP header has
to be built by hand. It is built with sendhex here, in its standard
form, and the test follows the structure of the expect-proxy one.
The first frontend declares the magic number on its bind line, which
makes the header mandatory: a well-formed IPv4 header, then an IPv6 one,
replace the addresses of the session while fc_src keeps reporting the
real peer, whereas a wrong magic, an encapsulated protocol which is not
TCP, and a request with no header at all are all rejected.
The second frontend uses the action, which only expects the header from
the sources matching its condition. Note that the action reads the
magic number from the bind line, so without "accept-netscaler-cip"
there the expected magic is zero, which the test documents.
The action had no coverage, and the notes listed adding a fourth
backend here as the way to get it. That backend is a copy of the one
which retries on a 503, with "disable-l7-retry" in an http-request
rule: the 503 of the first server must then reach the client as it is,
with txn.conn_retries left at zero, and the second server must not be
contacted at all.
REGTESTS: http-rules: cover the rfc7239_nn and rfc7239_np converters
The test already covers rfc7239_field, rfc7239_is_valid, rfc7239_n2nn
and rfc7239_n2np, and the two converters which build a node name and a
node port were the only ones of the family left out. They are added to
the backend which already extracts the node of a "for" field, with one
IPv4, one IPv6, one string and one integer input, which covers both
branches of each: an address or a number is emitted as it is, an IPv6
is bracketed and a string becomes an obfuscated identifier prefixed
with an underscore.
REGTESTS: stick-table: cover the remaining gpc and gpt spellings
Thirteen spellings of the general purpose counters were still left out
of this test: the legacy sc1_ and sc2_ increments and clears, the sc1_
readers, sc0_inc_gpc0, sc0_clr_gpc0, sc_inc_gpc1(), sc_clr_gpc1() and
the generic sc_clr_gpc() of the array form, plus the whole src_inc_gpc*
and src_clr_gpc* family which the notes listed as a follow-up.
The first frontend cannot host them: its sc1 tracks the array table,
which the legacy fetches do not accept, and its counters already carry
the values the existing clients assert. A second frontend therefore
tracks one legacy table through its three counters, which makes the
aliasing visible since an increment through one of them is read back
through the other two, and a third frontend, whose counter tracks an
array table, is only there for the generic clear of the array form.
REGTESTS: stick-table: read the counters through sc1_ and sc2_ as well
The sc1_* and sc2_* families are pure aliases of the sc0_* ones with a
different hard-coded stick counter id, and none of their 29 entries was
covered. The notes listed extending this test as the way to close them
all at once, which is what this does.
Two more tables are added, one per extra stick counter, rather than
tracking the single existing one three times, which would have counted
every connection and every request three times and forced all the
existing values to be rewritten. Each counter therefore sees its own
copy of the same entry, and the sc1_ and sc2_ readings are compared to
the sc0_ ones rather than to hard-coded values. The generic form with
the ids 1 and 2 is read the same way.
sc0_bytes_in_rate and sc0_bytes_out_rate, which the first version had
missed, and src_updt_conn_cnt(), which creates its entry as it reads it
and therefore gets a table of its own, are covered too.
REGTESTS: ssl: test ssl_c_used, ssl_c_san and ssl_c_ca_err_depth
These three client certificate fetches were the ones the ssl_c_* test
did not report, the first two only because they were forgotten, the
last one because it needs a chain which does not verify.
The first client gets the two easy ones plus ssl_c_err and ssl_c_ca_err
which must both be zero for the certificate which does verify.
A second listener sends common.pem as a client certificate. It carries
a subjectAltName, which client1.pem does not, and its intermediate CA
is unknown to the ca-file of the bind line, so the chain breaks at
depth 1 and ssl_c_ca_err_depth reports it. Being a server certificate,
it also fails the purpose check at depth 0, hence the "ca-ignore-err
all" added next to the existing "crt-ignore-err all" so that the
handshake completes and the fetches can be read.
REGTESTS: sample_fetches: test the req.ssl_alpn fetch
req.ssl_alpn was the last fetch of the raw ClientHello family without a
test of its own, it only appeared as a condition in the tcp-check test.
It is also the only one of the family which returns several samples,
one per protocol name of the extension, so the test checks that a
log-format only sees the first name while an ACL matches any of them.
Five hand-crafted ClientHellos are used: one advertising "h2" and
"http/1.1" behind a server_name extension, one advertising the single
"acme-tls/1" name of the documentation example, one without any ALPN
extension, one whose ALPN extension is too short to hold a name, and
one announcing a name longer than the extension which carries it, the
last two checking that the fetch gives up instead of reading the
extension which follows.
REGTESTS: sample_fetches: test req.ssl_ver on an SSLv2 CLIENT-HELLO
req.ssl_ver is the only fetch of the family which accepts the SSLv2
record format in addition to the SSLv3 one, and that second half of
smp_fetch_req_ssl_ver() was not reached by the ClientHellos of the
first frontend. It is also the only one whose ACL parses its pattern
in dotted notation, which was not covered either.
A second frontend, which rejects everything and only reports the
fetches in its log, receives an SSLv2 CLIENT-HELLO with the short
record header announcing SSLv3.0, another one with the long record
header, its padding length, two ciphers and a 32-byte challenge,
announcing TLSv1.0, and finally a plain HTTP request which is neither.
The version is checked both as a number and through the "3.0" and
"gt 3.0" patterns of the two ACL spellings, while req.ssl_hello_type
and req.ssl_sni, which only know the SSLv3 record, return nothing.
REGTESTS: protobuf: cover all the field types of the ungrpc converter
The first message only used int32 leaves and a string, that is two of
the fifteen types the converter accepts as its second argument, and a
single gRPC message, while the converter is able to walk over as many
of them as the buffer holds.
A second frontend receives a message holding one field per type, the
field number being the same as the index of the type in the list of the
documentation, so that every branch of protobuf_smp_store_varint(),
protobuf_smp_store_32bit() and protobuf_smp_store_64bit() is visited,
including the zigzag decoding of sint32/sint64 and the little-endian
decoding of the fixed size types. Two fields are also extracted without
a type to check the binary form, and three others with a type which
does not match their wire type, which must return nothing.
The test records that a "float" or a "double" field is truncated to an
integer, which is what the documentation announces ("as an integer
sample") but is easy to overlook.
A third frontend receives a body made of two gRPC messages so that the
loop searching the field in the messages which follow is covered.
REGTESTS: http-messaging: test the options dropping the HTTP trailers
"option http-drop-request-trailers" and
"option http-drop-response-trailers" had no coverage.
vtest can send HTTP/1 trailers but cannot parse them, so neither end of
the chain may be a vtest HTTP peer when the trailers are expected to be
forwarded: the request side origin is a small haproxy instance, and the
client of the forwarded response side only reads the headers and then
the exact number of remaining bytes.
What actually leaves the proxy is observed at the byte level by two
extra haproxy instances running in TCP mode, one on each side, which
report through their log whether the trailer field name appears in what
they relayed. This gives a positive assertion for both the dropped and
the forwarded case, and the "no option" form is covered as well.
REGTESTS: protobuf: add a test for the ungrpc converter
ungrpc had no coverage while the protobuf converter is exercised by
nested_and_flat.vtc. It differs from protobuf by the 5-byte gRPC header
it skips first, and by the rule that only the first converter of a
chain may be an ungrpc, the following ones having to be protobuf.
The body is the "Rectangle" message of the documentation example, built
by hand with sendhex: a string field, and two nested messages holding
two varint leaves each. The test extracts the length-delimited field
without a type, the four leaves through their dotted paths, an
intermediary field as a binary sample, and checks that the three
documented equivalent spellings mixing ungrpc and protobuf all return
the same value. A field which is not in the message must yield nothing.
REGTESTS: ssl: test the ssl_fc_* and ssl_bc_* connection fetches
The existing SSL tests cover the certificate fetches (ssl_f_*, ssl_c_*
and ssl_s_*) but almost none of the ones describing the connection
itself. This test chains a clear frontend to an SSL backend and an SSL
frontend so that both families are read on the same exchange: the
ssl_bc_* ones from the client side of the SSL connection and the
ssl_fc_* ones from its server side, which lets the two be compared.
It covers ssl_bc, ssl_bc_alpn, ssl_bc_cipher, ssl_bc_protocol,
ssl_bc_alg_keysize, ssl_bc_use_keysize, ssl_bc_sni, ssl_bc_is_resumed,
ssl_bc_session_id, ssl_bc_unique_id, ssl_bc_client_random,
ssl_bc_server_random, ssl_bc_session_key, and on the frontend side
ssl_fc_protocol, ssl_fc_protocol_hello_id, ssl_fc_alg_keysize,
ssl_fc_use_keysize, ssl_fc_has_sni, ssl_fc_has_crt, ssl_fc_crtname,
ssl_fc_has_early, ssl_fc_session_id, ssl_fc_unique_id,
ssl_fc_client_random, ssl_fc_server_random, ssl_fc_session_key, plus
the captured ClientHello fetches ssl_fc_cipherlist_str, _hex, _bin,
_xxh, ssl_fc_eclist_bin, ssl_fc_ecformats_bin, ssl_fc_extlist_bin and
ssl_fc_sigalgs_bin.
A second pair of listeners pins TLSv1.3 to reach what only exists
there: ssl_fc_supported_versions_bin and the four traffic secrets plus
the exporter secret, on both ends.
The capture buffer is sized so that the whole ClientHello fits,
otherwise the extension lists which follow the cipher list are
silently reported as empty.
REGTESTS: connection: do not race with the reset in the expect-proxy test
The client which sends a malformed PROXY header also sent a request
behind it. The connection is reset as soon as the header line is
complete, so on a build where the reset comes back fast enough the
write of the request fails with ECONNRESET instead of succeeding, and
the test fails. Nothing needs to be sent after the bad header.
REGTESTS: sample_fetches: test the raw TLS hello fetches
req.ssl_hello_type, req.ssl_ver, req.ssl_ec_ext, req.ssl_st_ext and
res.ssl_hello_type had no coverage, nor had the deprecated
req_ssl_hello_type, req_ssl_ver, req_ssl_sni and rep_ssl_hello_type
aliases. They parse the raw buffer of a TCP proxy without any SSL data
layer, so the handshake messages are crafted by hand with sendhex the
same way the other ClientHello tests do, and a server answers each of
them with a ServerHello so that the response side is covered too.
Three ClientHellos are used: one carrying server_name, supported_groups
and an empty session ticket, one carrying a session ticket with data
and no supported_groups, and one carrying a single unrelated extension
so that req.ssl_st_ext reports its documented zero.
REGTESTS: connection: test the expect-proxy action
"tcp-request connection expect-proxy layer4" had no coverage. Unlike
the "accept-proxy" bind option, which makes the PROXY protocol header
mandatory for everybody, this action only expects it from the
connections matching its condition.
The test announces addresses which differ from the real ones so that
src and dst show which of the two was used, checks that fc_src keeps
reporting the real peer of the connection, that a connection from a
matching source is rejected when the header is missing or malformed,
and that a source which does not match the condition is served without
any header.
REGTESTS: cli: test the privilege levels of the CLI
The "operator" and "user" commands, which drop the privileges of the
current connection, had no coverage, and "show cli level" was only
reported by the smoke test.
The test walks down the three levels and checks both what each of them
still allows and what it refuses: reading the statistics and the
process information is allowed everywhere, clearing the counters
requires operator, and changing a global setting, a server state,
an ACL or entering the expert or experimental modes requires admin. It
also verifies that the levels only go down, and that a refused command
really left the setting untouched.
Each case chains the level change and the command it affects on a
single line because the level only applies to the connection it was set
on, and vtest opens a new connection for every "send".
REGTESTS: checks: test the health adjusting from observed traffic
The "observe", "error-limit" and "on-error" server settings had no
coverage. They adjust the health of a server from the traffic it serves
rather than from the health checks, which makes them testable without
any timer: the state changes exactly when the configured number of
consecutive errors has been served.
The test checks that a 404 is not counted as a layer 7 error while a
500 is, that the server is marked down once the error limit is reached
and that the backend then answers 503, and that a failed connection is
enough in layer 4 mode. It also verifies that the resulting state
change is attributed to the health analysis and not to a check result.
The health checks are directed at a second haproxy instance which
accepts the connections and never answers, with "option httpchk" so
that no check can complete and interfere with the observation.
REGTESTS: server: test the server settings reported by the runtime
A large part of the "server" keywords had no coverage: addr, port,
agent-addr, agent-port, rise, fall, init-state, minconn, maxqueue,
backup, non-stick, pool-purge-delay, pool-max-conn, pool-low-conn,
max-reuse and the explicit identifiers.
They are all visible through "show servers state", "show stat typed"
and "show servers conn", which makes them checkable without any traffic
and without waiting for a health check to complete. The checks are
directed at a second haproxy instance which accepts the connections and
never answers, so that no check can complete and the initial states
stay observable for the whole test, and the proxies and servers are
given explicit identifiers so that the field names of "show stat typed"
are predictable. Each dump is restricted to a single server because the
full typed dump of nine servers does not fit in one CLI response.
The four documented init-states are checked against the health counter
they set up, as is the fact that a server which says nothing inherits
the default-server value.
Note that "init-state none" is documented as restoring the default
behavior when the setting was inherited from default-server, but it
currently has no effect: srv_set_init_state() returns early for
SRV_INIT_STATE_NONE and therefore does not undo what the inherited
value had already applied. The test matches the current behavior.
REGTESTS: sample_fetches: pin one thread in the proxy state test
Same reason as for the connection address test: act_conn and fe_conn
are over-counted when several threads accept in parallel on a loaded
machine, which made the connection counters of this test unpredictable
when the suite is run with vtest -j.
REGTESTS: sample_fetches: pin one thread in the connection address test
The test asserted that dst_conn reports exactly one connection, which
holds when it runs alone but not when the reg-tests are run in parallel
on a loaded machine: it was reported failing with a value of 8.
The extra connections are not real. With several threads accepting in
parallel, the process ends up reporting current connections which were
never established: after the only client has left, "show info" reports
a non-zero CurrConns while CumConns has only counted the connections
which really happened, and "show stat" shows the same discrepancy
between scur and stot for the frontend. Forcing nbthread to 1 makes the
counters exact again, which is what this test needs since it checks the
address fetches, not the connection accounting.
req.len, req.payload and their "res." counterparts, the payload_lv
family and wait_end had little or no coverage. They are only usable in
TCP mode, on the raw buffers, so the test runs a plain TCP proxy and
reports what its rules see through the log.
The client sends a 4-byte big endian length, its payload and two
trailing bytes, and the server answers with the same framing, which
gives payload_lv() something to extract on both sides, including with a
relative second offset. A second client never sends enough data, which
lets the inspection delay expire so that wait_end becomes true and the
connection is rejected.
The log format ends with %B on purpose: a format made only of var()
fetches is emitted as soon as the server connection is established,
that is before the response side rules have run.
REGTESTS: http-rules: test the declared capture slots
The "declare capture" directive, the "id" form of the capture action
and the capture-req()/capture-res() converters had no coverage; only
"capture request header" and capture.req.hdr() were used elsewhere.
The test declares three request slots and two response slots in the
frontend, fills them from the frontend and from the backend, with the
action and with the converter, and reads them back with
capture.req.hdr() and capture.res.hdr(). It also checks that the
converters return their input unchanged, that an undeclared slot yields
nothing and that a slot whose input is missing stays empty.
REGTESTS: cli: test the commands which change the run-time settings
None of the CLI commands changing a run-time setting was covered: set
maxconn for the process, a frontend and a server, set rate-limit for
the connections and the sessions, set weight in absolute value and as a
percentage, get weight, set server state, disable/enable/shutdown
frontend, set profiling, set anon, set severity-output, set timeout,
set table, clear table and echo.
Each change is read back with the command which reports it, so that
both the setter and the reporter are exercised: "show info" for the
global limits, the "slim" and status columns of "show stat" for the
proxy and server limits and states, "get weight", "show profiling",
"show anon" and "show table".
REGTESTS: cli: add a smoke test for the read-only CLI commands
Most of the "show" commands of the CLI had no coverage at all, so a
crash or a broken output in one of their dump functions would go
unnoticed. This test issues show version, cli level, cli sockets, env,
startup-logs, dev, backend, defaults, default-server, servers state,
servers conn, stat, info, schema json, sess, errors, fd, pools,
activity, threads, tasks, profiling, anon, table, map and acl once on a
small but non-empty configuration.
The patterns are deliberately loose: the point is to verify that each
command is accepted and that its dump reaches its first line, not to
freeze output formats which are explicitly allowed to change.
REGTESTS: proxy: test monitor-uri and the monitor fail condition
"monitor fail" had no coverage and "monitor-uri" was only used as a
side setting of a health check test.
The test checks that the monitor URI is answered by the frontend
without reaching the backend while the other URIs are still forwarded,
that the status switches from 200 to 503 when the condition is met,
that the "unless" form inverts that, and that the monitor response
goes through the error files of the proxy as documented.
REGTESTS: log: test the log filtering options of a frontend
"option dontlognull", "option dontlog-normal" and "option logasap" had
no coverage, and "option log-separate-errors" was only used as a side
setting of an SSL test.
Each option gets its own frontend: a connection on which nothing is
sent must not be logged while the next real request is, a successful
request must not be logged while the one ending on a 503 is, the same
pair must both be logged but at different syslog levels, and the
message emitted early must carry the '+' prefix on the byte count and
on the total time.
Two syslog servers are needed since vtest only matches the messages
emitted at the level its server was declared with, which is precisely
what log-separate-errors changes.
REGTESTS: http-rules: test the tarpit, deny and silent-drop actions
The tarpit action was only referenced by the "set-timeout" test,
silent-drop was not covered at all, and deny was always used in its
default form, never with a customized status, body, content type or
extra header.
The test checks the default and the customized status of tarpit, the
"PT" termination state it produces in the log, the customized response
of deny, and that silent-drop closes the connection since we're using
rst-ttl here for portability reasons. The pause action is exercised
too, with a short delay.
"timeout tarpit" is set to a small value so that the test stays fast.
REGTESTS: proxy: test the connection retries, retry-on and redispatch
"retry-on" and "option redispatch" had no coverage, and the
txn.conn_retries and txn.redispatched fetches which report their effect
were not covered either.
Three backends are used: one whose only server is unreachable, which
must report as many retries as configured before giving up with a 503,
one which redispatches the retry to a healthy server, and one which
retries at the HTTP layer because the first server answers with a
status listed in "retry-on".
The connect timeout of the two backends which fail to connect is
shortened since haproxy spreads the retries over it, which would
otherwise make the test last as long as the default timeout.
REGTESTS: proxy: test the "option checkcache" response filter
"option checkcache" had no coverage. The test walks through the
conditions listed in the documentation: a cacheable 200 carrying a
Set-Cookie is blocked, a response made non cacheable by "cache-control:
private", "no-store", "no-cache=\"set-cookie\"" or "pragma: no-cache"
goes through, a status which is not cacheable by default goes through
unless the server declares the response public, and the response to a
POST is not filtered.
The origin is a second haproxy instance crafting one response per path,
because a blocked response makes the proxy close the server connection,
which a single vtest server session would not survive.
The test also documents one behavior which the documentation does not
mention: a response carrying neither freshness information nor a
validator is not considered cacheable.
REGTESTS: http-errorfiles: test the errorloc directives
The errorloc, errorloc302 and errorloc303 directives had no coverage
while errorfile and http-error do. The test checks the three forms on a
503 caused by an unreachable server and on a 403 returned by a deny
rule, verifies that "errorloc" is an alias for "errorloc302", that a
relative location is accepted, and that a status code for which no
errorloc was declared still gets the default error page.
REGTESTS: converter: add a test for the when() and debug() converters
Neither converter was covered. when() only lets its input through when
the condition is met, so the test builds a string made of one bracketed
field per condition and compares it for three kinds of transactions: one
forwarded to a healthy server, one whose server cannot be reached, and
one answered by the stats applet. This covers the error, normal,
forwarded, processed, toapplet, stopping and acl conditions as well as
the negated form.
debug() is checked to be transparent for the sample it is inserted in,
and its copy is read back from the default sink with "show events".
REGTESTS: sample_fetches: add a test for the constant and process fetches
The constant fetches always_true, always_false, str, int, bin, bool,
ipv4, ipv6 and meth, the process state ones pid, thread, tgroup, nbproc,
quic_enabled, cpu_calls, cpu_ns_tot, cpu_ns_avg, lat_ns_tot, lat_ns_avg,
cpu_usage_proc, cpu_usage_thr and cpu_usage_grp, plus date_us, env(),
rand() and dump_all_vars had little or no coverage.
The values which cannot be predicted are matched against their expected
shape, while pid is compared to the process identifier reported by
vtest, rand(10) must stay within its range, env() is looked up on the
HAPROXY_LOCALPEER variable which the process always exports, and
dump_all_vars must mention a variable set by an earlier rule.
REGTESTS: tcp-rules: test the set-src, set-dst and port rewriting actions
set-src-port and set-dst-port had no coverage at all, and set-src and
set-dst were only used incidentally by other tests.
The test applies them from the "tcp-request connection", "tcp-request
session", "tcp-request content" and "http-request" rule sets and checks
the documented behaviors: set-src and set-dst preserve the port while
set-src-port and set-dst-port preserve the address, and a later rule set
overrides what an earlier one did.
It also shows that a rewrite performed from a "tcp-request connection"
rule alters the connection itself, so that fc_src and fc_dst report the
new values, while a rewrite performed at the session level leaves them
untouched.
REGTESTS: http-rules: test replace-uri and the header replacement actions
replace-uri had no coverage at all, and replace-header and replace-value
were only used by a test checking their size limits, not their
semantics.
The test rewrites a request target in the origin form and in the
absolute form to show that replace-uri also sees the scheme and the
authority, rewrites the scheme of an absolute URI, checks that a non
matching regex leaves the target untouched, and compares the result with
replace-path and replace-pathq on the same target.
It also checks that replace-header replaces the whole header line while
replace-value only replaces the matching comma-delimited element, and
that back-references work in the replacement format.
REGTESTS: http-rules: test the binary header manipulation actions
The add-headers-bin, set-headers-bin and del-headers-bin actions had no
coverage at all. The test saves the incoming header block with
req.hdrs_bin, drops a few headers and restores them from the saved
block, with and without the "prefix" filter, and checks on the server
side which headers made it through.
The lists passed to del-headers-bin are built by hand with the bin()
fetch so that the varint encoding of the header names is exercised too,
with the default exact matching and with the "beg" one.
REGTESTS: log: test the transaction state fetches and the logging actions
The txn.sess_term_state, term_events, last_entity, waiting_entity,
txn.conn_retries, txn.redispatched, txn.id32, txn.timer.total,
txn.timer.user, req.timer.queue, res.timer.data, prio_class, prio_offset,
bc_be_queue, bc_srv_queue, fc_err_name and bc_err_name fetches were not
covered, and neither were the set-priority-class, set-priority-offset,
set-nice, set-log-level and do-log actions.
They are checked from a custom log format since most of them only reach
their final value once the transaction is over. The test verifies that
"set-log-level err" raises the syslog priority of the message, that
"set-log-level silent" suppresses it entirely, that "do-log" emits an
extra message before the response exists, and that a denied request is
reported with the "PR" termination state and the rule which took the
decision.
Two syslog servers are needed because vtest only matches the messages
emitted at the level its server was declared with.
Note that path, url and the req.timer.hdr/idle/tq fetches are not usable
at log time, hence the use of capture.req.uri here.
REGTESTS: http-rules: test the HTTP authentication action, fetches and ACL
Neither the "auth" action nor the http_auth_user, http_auth_pass,
http_auth_type and http_auth_group fetches were covered, and the
userlist section was only exercised through the stats.
The test declares a userlist with two users, one of them in a group,
and checks that the credentials are readable before being validated,
that http_auth() only matches with the right password, that
http_auth_group() only matches for the member of the group, and that
the "auth" action emits a 401 carrying the requested realm until valid
credentials are presented.
REGTESTS: stick-table: test the traffic counters of the stick counters
The connection, session, request, error, failure and byte counters of
the stick tables had no coverage, nor had the sc_*, sc0_*, src_* and
table_* families giving access to them, nor sc_key, sc_tracked,
sc_trackers, table_trackers, table_server_id, table_expire, table_idle,
in_table, table_avl and table_cnt.
A single client connection issues a normal request, a request denied
with a 403 and a request answered with a 500 by the server so that the
request, error and failure counters all move, and the three access
families are compared against each other at each step.
The values are grouped into a few headers because vtest only parses a
limited number of them.
REGTESTS: stick-table: test the general purpose counters and tags
The sc-add-gpc, sc-inc-gpc, sc-inc-gpc0, sc-inc-gpc1, sc-set-gpt and
sc-set-gpt0 actions had no coverage, and neither had the sc_*_gpc*,
sc_*_gpt*, sc0_*, sc1_*, sc2_* and src_* fetches nor the table_*gpc*
and table_*gpt* converters.
The array form of the general purpose storage (gpc, gpc_rate and gpt)
and the legacy scalar one (gpc0, gpc1 and gpt0) may not coexist in a
same table, so the test declares one table of each kind. The legacy one
is tracked by two stick counters at once, which lets the sc0_ and sc2_
shorthands be checked against each other and against the generic
sc_get_*(<sc-id>) form.
The three ways of reaching an entry are compared on each step: the stick
counters, the src_* fetches and the table_* converters, and the
increment, addition, reset and read variants are all exercised.
Note that among the shorthands of the array form, only sc1_clr_gpc() is
registered, sc0_clr_gpc() and sc2_clr_gpc() do not exist.
REGTESTS: sample_fetches: add a test for the proxy and server state fetches
None of the fetches reporting the internal state of the proxies and of
their servers was covered. This test checks fe_id, fe_name, fe_defbe,
be_id, be_name, srv_id, srv_name, nbsrv with and without an argument,
srv_is_up, srv_weight, srv_iweight, srv_uweight, act_conn, fe_conn,
be_conn, be_conn_free, srv_conn, srv_conn_free, connslots, queue,
avg_queue, srv_queue, fe_sess_rate, fe_req_rate, be_sess_rate,
srv_sess_rate, stopping, uptime and fe_client_timeout, plus the nbsrv,
srv_is_up, srv_queue and fe_exists converters.
The backend holds a usable server and a disabled one so that nbsrv() and
srv_is_up() have something to distinguish, and both servers have a
maxconn and a maxqueue so that connslots() returns a computable value
instead of the -1 it reports on an unbounded configuration.
The fetches whose implicit argument is the current proxy are placed in
the backend since they require the backend capability.
REGTESTS: sample_fetches: add a test for the connection address fetches
None of the address fetches of the frontend and backend connections was
covered. This test checks src, src_port, src_is_local, dst, dst_port,
dst_is_local, dst_conn, their fc_* equivalents, so_id, so_name,
fc_http_major, fc_nb_streams, fc_rcvd_proxy, fc_err_name, and on the
backend side bc_src, bc_src_port, bc_dst, bc_dst_port, bc_http_major,
bc_nb_streams, bc_be_queue, bc_srv_queue and bc_err_name.
Everything runs over the loopback so that both endpoints are known: the
port of the frontend and the address and port of the server are compared
against the macros exported by vtest, and the src/fc_src and dst/fc_dst
pairs are compared against each other.
However the _local tests fail on macos, and this reveals some weakenesses
in the addr_is_local() function (error checking might be too broad) and
its caller (since that function may return -1 which is taken as a bool).
So for now the local tests are disabled.
REGTESTS: sample_fetches: add a test for the HTTP request fetches
The request side header, URI and body fetches were largely uncovered.
This test checks req.hdr_val, req.hdr_ip, req.hdr_cnt with an occurrence
index, req.hdr_names with and without a custom delimiter, req.fhdr and
req.fhdr_cnt against req.hdr on a comma-delimited list, req.hdrs,
req.hdrs_bin, base, baseq, base32, base32+src, url32, url32+src, url_ip,
url_port, query, urlp_val, req.body_len, req.body_size, req.body_param
with and without the case-insensitive flag, req.timer.hdr,
req.timer.idle, req.ver, req.proto_http, the deprecated req_ver and
req_proto_http aliases, and the capture.req.method, capture.req.ver,
capture.req.uri and capture.req.hdr fetches.
The fetches are collected into variables from the http-request rule set
since most of them may not be used from the http-response one, and they
are reported in the response headers so that a single exchange suffices.
An absolute URI is used so that base, baseq, url_ip and url_port have
something to extract, which requires the Host header to match the
authority. A second request uses an address as the authority since
url_ip and url_port do not resolve host names.
REGTESTS: sample_fetches: add a test for the HTTP response fetches
Almost none of the res.* sample fetches was covered. This test checks
status, txn.status, res.ver, res.hdr, res.hdr_val, res.hdr_ip,
res.hdr_cnt with an occurrence index, res.hdr_names with and without a
custom delimiter, res.fhdr and res.fhdr_cnt against res.hdr on a
comma-delimited list, res.cook, res.cook_cnt, res.cook_val,
res.cook_names, res.body_size, res.hdrs, res.hdrs_bin, res.comp,
res.comp_algo and res.timer.hdr, as well as the deprecated shdr, shdr_val,
shdr_ip, shdr_cnt, scook, scook_cnt, scook_val and resp_ver aliases, and
the capture.res.hdr and capture.res.ver fetches fed by a response header
capture.
REGTESTS: http-rules: test the ACL and map management commands of the CLI
Out of the 13 CLI commands dealing with ACLs and maps, only "show map"
and "clear map" were used by another reg-test. This one covers them all
except the "#<ref>" form of "del" and "set" which requires capturing a
pointer from a previous dump: add acl, add map, del acl, del map, clear
acl, clear map, get acl, get map, set map, show acl, show map, and the
prepare/commit sequence for both.
The atomic replacement is verified from the data plane as well: the
entries added to the prepared version are invisible until the commit,
after which they have replaced the previous ones rather than being
merged with them.
Virtual lists are used so that the test needs no data file.
REGTESTS: http-rules: test the run-time ACL and map update actions
The add-acl, del-acl, set-map and del-map actions were not covered at
all, nor were the "file@", "opt@" and "virt@" name formats described in
section 2.7.
The test adds and removes entries from one request and observes them
from the next one, on an ACL loaded from a regular file and on three
maps designated respectively by a plain file name, by the same name
prefixed with "file@" to verify that both reference the same list, by
"virt@" which has no file at all, and by "opt@" pointing at a file which
does not exist. It also checks that updating an existing key replaces
its value instead of duplicating the entry, that the set-map action is
usable from the http-after-response rule set, and that the resulting
lists are visible from "show acl" and "show map".
REGTESTS: converter: cover all the map match methods and output types
Out of the 38 registered map converters, only map, map_str, map_dom,
map_beg and map_regm were exercised by the existing reg-tests, which
left the wiring of most of the entries of the keyword table unverified.
This test looks up every one of them at least once: the str, beg, sub,
dir, dom, end, int, ip, reg and regm match methods combined with the
default (string), _int, _ip and _key output types. It also covers an
integer range key, an IPv6 key, a network key, the difference between
map_reg and map_regm on a value holding a back-reference, the default
value argument and the failure of the converter when nothing matches
and no default is provided.
The output type is applied to the whole map at load time, so the same
key set is duplicated in three files holding string, integer and address
values, plus two smaller files for the integer and address keyed maps.
REGTESTS: converter: add a test for the date formatting converters
utime, ltime, ms_utime, ms_ltime, us_utime, us_ltime and http_date were
not covered at all. They are checked here against fixed input dates, so
that the expected output is constant, including the offset argument, the
%N specifier with its various widths and the unit argument of http_date.
The local time variants depend on the machine's timezone, so their
output is only matched loosely, which is still enough to verify that the
date and the sub-second part are correct.
The test also verifies that accept_date() and date() remain consistent
with each other when expressed in seconds, milliseconds and
microseconds. A test also checks that the ms_*/us_* converters apply
their offset in the right unit.
REGTESTS: converter: add a test for the string trimming and parsing converters
The ltrim, rtrim, cut_crlf, base2, port_only and language converters had
no coverage at all, and host_only was only used incidentally by other
tests. They are all checked here, including the corner cases of a string
entirely made of trimmed characters, of a host value without a port, of
an IPv6 host, and of an accept-language header matching nothing with and
without a default value. The "q=" test is also improved to test "q=0".
The input of cut_crlf comes from an URL-encoded query string parameter
since a header field may carry neither a CR nor a LF.
REGTESTS: converter: add a test for the arithmetic and bitwise converters
None of the integer converters was covered by a reg-test so far. This
test checks add, sub, mul, div, mod, neg, cpl, and, or, xor, not, bool,
odd, even, htonl and hex2i at once, using the "int()" fetch as an input
so that no server nor client data is needed.
The operand of add, sub and div is also passed as a variable so that
both the constant and the variable forms are covered, and the documented
behavior of a null divisor is verified for div() and mod().
Note that hex2i() currently fails on a non-hexadecimal input instead of
returning zero as documented, the test matches the implementation.
DOC: config: mention that url_ip and url_port fail when passed a name
url_ip and url_port both rely on a call to url2sa() which tries to parse
digits in the authority part. This means that neither is usable with a
host name. For example www.example.org:443 will not return an IP address
nor even port 443 since the IP address parsing fails early. It's worth
being said since it's not obvious.
DOC: config: mention other responses not blocked by option checkcache
The doc for "option checkcache" forgot to mention that responses with
no validator nor object freshness info are not considered and can pass
unfiltered to the client (it's because these info were developed with
the cache that didn't exist when the option was developed).
BUG/MINOR: sample: return the decoded JWT part when no path is given
jwt_header_query() and jwt_payload_query() are documented to return the
decoded header or payload of the token when they are called without any
argument. sample_conv_jwt_member_query() does fill the sample in that
case, but it jumps to its exit label while <retval> still holds the
zero it was initialized with, so the converter reports a failure and
the expression yields nothing at all.
Only the form taking a JSON path worked, which is the one the reg-test
used, hence the bug going unnoticed. The test now reads both parts of
the token without any argument as well.
The issue arrived in 2.5 with the original commit 0a72f5ee7c ("MINOR:
jwt: jwt_header_query and jwt_payload_query converters") so this fix
can be backported to all stable branches.
BUG/MEDIUM: tcpcheck: do not run a regex on an unallocated buffer
A "tcp-check expect rstring" rule can be evaluated while the input
buffer of the check is not allocated, in which case b_head() returns
NULL, which is not compatible with regex_exec2() that wants a writable
buffer to write a trailing zero, and dies.
By luck, the surrounding cases work by chance because my_memmem()
returns NULL on NULL, and the binary regex works on a trash buffer
(hence always allocated). When using PCRE/PCRE2 it doesn't crash
either because both return an error for a NULL subject, so only a
pure build with libc regex fails.
It's not easy to reproduce, it requires to run many (~2*nproc) parallel
instances of reg-tests/checks/tcp-check_min-recv.vtc several times
(~200) to see it, but it does happend.
The rule now reports no match when no buffer is allocated, which is
what the string and binary cases already do in the same situation
BUG/MINOR: http-fetch: do not count Set-Cookie attributes as cookies
A Set-Cookie header contains a single cookie, and other elements on
the same line are not cookies but attributes (which can easily be
mistaken for them by a parser). That's what happens for res.cook_cnt()
which uses the same parser as req.cook_cnt() and counts apparent cookie
values without figuring it's parsing attributes. Let's just do like for
res.cookie_names() and check for a request or response before looping,
so that we can now count a single cookie per set-cookie response.
BUG/MINOR: payload: always report a boolean from req.ssl_ec_ext
req.ssl_ec_ext is documented to return "a boolean identifying if client
sent the Supported Elliptic Curves Extension", but like a few other such
sample fetch functions, it fails to return false and incorrectly returns
a fetch failure instead. It must properly return false when the hello
parses correctly but doesn't have the EC extension instead of making the
sample fetch fail as if the hello was unparsable.
The bug was in the initial version in 1.6-dev3 with commit 5fc7d7e8ce
("MINOR: Add sample fetch to detect Supported Elliptic Curves Extension"),
so this fix can be backported to all stable branches.
BUG/MINOR: stick-table: do not count the lookup itself in table_trackers()
In 1.8 when adding multi-threading support with commit 819fc6f563
("MEDIUM: threads/stick-tables: handle multithreads on stick tables"),
the entry's ref_cnt was incremented by one on all trackers and accesses,
and smp_fetch_sc_trackers() learned to return one less than the visible
value so as not to count itself in it.
One of them was missed: table_trackers() returns the number of trackers
without subtracting one so it's always one above the real value. Let's
adjust it like other places do.
BUG/MINOR: http-ana: return a 502 when checkcache blocks a response
The documentation of "option checkcache" states that a response which
does not respect its requirements "will be blocked just as if it was
from an http-response deny rule, with an HTTP 502 bad gateway".
The code jumps to the "deny" label without touching txn->status, which
at that point still holds the status the server had returned. The
response delivered to the client is thus a 200 (or anything else that
the server returned), and the error files are not even used in this
case. Let's just properly set the status to 502 just like the other
rules such as "http-response deny" do. It now does what the doc says.
This has been there forever so it can be backported to all stable
branches.
BUG/MINOR: sample: make quic_enabled() always succeed
smp_fetch_quic_enabled() ends with "return smp->data.u.sint" instead of
"return 1", so it returns its boolean result as the success status of
the fetch as well. On a build without QUIC, or when the QUIC listeners
are disabled, the fetch therefore fails instead of reporting a false
boolean, and any expression using it fails to evaluate.
The issue was introduced in 2.8-dev2 with commit 33d11c464f ("MINOR:
sample: Add "quic_enabled" sample fetch") so this fix should be
backported to 2.8.
BUG/MINOR: sample: zero-pad the fractional part emitted by http_date()
When http_date() is given the "ms" or "us" unit, it appends the
sub-second part of the date with a plain "%d", so it is printed without
any padding: 123 ms gives ".123" but 23 ms gives ".23", which stands
for a completely different instant, and 23 us gives ".23" as well
although the two units must not produce the same string.
The fraction is now printed over the number of digits its unit
requires, three for milliseconds and six for microseconds. The test
which selects the format is also made to depend on that width rather
than on the unit argument, which is equivalent but clearer.
The bug was introduced in 2.1-dev4 with commit ae6f125c7b ("MINOR:
sample: add us/ms support to date/http_date"), so this fix should
be backported to all stable branches.
BUG/MINOR: sample: apply the ms_/us_ time offsets in the input unit
The documentation of ms_ltime(), ms_utime(), us_ltime() and us_utime()
states that their optional <offset> is expressed in milliseconds or in
microseconds, that is in the same unit as their input. The four of them
however split the input into a number of seconds and a sub-second part
first, and then add the offset to the number of seconds, so the offset
was really applied in seconds and could never shift the sub-second part.
With int(1234567890123),ms_utime("%H:%M:%S.%3N",877) the result was
23:46:07.123, that is 877 seconds later, instead of the 23:31:31.000
the documentation describes.
The offset is now added to the input before it is split, which also
lets it carry the sub-second part as intended. The plain ltime() and
utime() take their offset in seconds and are unaffected.
The bug was introduced with the converters in 2.9-dev3 with commit 5e63e1636a ("MEDIUM: sample: implement us and ms variant of utime and
ltime"), so the fix must be backported as far as 3.0.
BUG/MINOR: http: do not consume the delimiter of a truncated q-factor
http_parse_qvalue() increments its cursor before checking that the
character it just read is the '.' which must follow the integer part:
if (*qvalue++ != '.')
goto out;
so when the q-factor has no decimal part, as in "q=0", the cursor
reported through <end> points one byte past the character which
terminated it, that is past the ',' separating it from the next value.
sample_conv_q_preferred(), which implements the "language" converter,
resumes its scan at that position and then looks for the next ',' from
there, so it silently skips everything up to the following separator.
With "accept-language: fr;q=0, en;q=0.1" the "en" value is never
examined and the converter reports no match at all, while "q=0.0"
works as expected.
The other caller, the accept-encoding parsing of the compression
filter, passes NULL as <end> and is unaffected. Note that this bug
has been there since the introduction of the function in 1.5-dev23
via commit 0e9b1b4d1f ("MEDIUM: compression: consider the "q="
attribute in Accept-Encoding").
BUG/MINOR: startup: don't chroot by default when set-dumpable is set
In 3.5-dev7, the automatic chroot by default was applied with commit 79082c7c2b ("MAJOR: startup: use chroot auto by default when available"),
but it has the undesired effect to prevent us from getting a core dump
by default, even when set-dumpable is set.
Let's make sure set-dumpable disables this mechanism and document it.
This is cleaner than entering a "chroot /" because set-dumpable is
clearly identified as a debugging option that should be left commented
out after use.
Note: maybe we'll need to add a "chroot /" to all reg-tests now, as it
is no longer possible to get a core dumped by default, or we may also
use a command-line option to enforce set-dumpable.
BUG/MINOR: jwe: Avoid buffer overflow in fake cek (RSA1.5)
In commit 1a5a333 "BUG/MEDIUM: jwe: substitute random CEK on RSA1_5
decryption failure per RFC 7516 #11.5" we fill a fake CEK with random
data to avoid timing attacks but the size of the buffer was never
checked. If we happened to have a too little bufsize (less than 64
bytes) we could have out of bounds accesses.