summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:20:34 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:20:34 +0000
commitf3bb08bb1d94c77704371f8546a739119f0a05b4 (patch)
tree5dfb47fa424ecde655f8950098411a311e9296e6
parentReleasing progress-linux version 2.9.6-1~progress7.99u1. (diff)
downloadhaproxy-f3bb08bb1d94c77704371f8546a739119f0a05b4.tar.xz
haproxy-f3bb08bb1d94c77704371f8546a739119f0a05b4.zip
Merging upstream version 2.9.7.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rwxr-xr-x.github/matrix.py1
-rw-r--r--.github/workflows/cross-zoo.yml1
-rw-r--r--.github/workflows/fedora-rawhide.yml1
-rw-r--r--.github/workflows/vtest.yml11
-rw-r--r--CHANGELOG88
-rw-r--r--SUBVERS2
-rw-r--r--VERDATE4
-rw-r--r--VERSION2
-rw-r--r--doc/configuration.txt111
-rw-r--r--doc/lua-api/index.rst13
-rw-r--r--doc/management.txt1
-rw-r--r--include/haproxy/cli-t.h1
-rw-r--r--include/haproxy/compat.h6
-rw-r--r--include/haproxy/connection-t.h1
-rw-r--r--include/haproxy/hlua-t.h1
-rw-r--r--include/haproxy/hlua.h3
-rw-r--r--include/haproxy/quic_tls-t.h2
-rw-r--r--include/haproxy/session.h6
-rw-r--r--include/haproxy/stream-t.h2
-rw-r--r--include/import/ist.h11
-rw-r--r--reg-tests/connection/h2_glitches.vtc108
-rw-r--r--reg-tests/http-rules/forwarded-header-7239.vtc6
-rw-r--r--src/applet.c2
-rw-r--r--src/cfgparse-listen.c4
-rw-r--r--src/cfgparse.c24
-rw-r--r--src/cli.c41
-rw-r--r--src/connection.c36
-rw-r--r--src/debug.c14
-rw-r--r--src/event_hdl.c16
-rw-r--r--src/flt_spoe.c17
-rw-r--r--src/haproxy.c43
-rw-r--r--src/hlua.c578
-rw-r--r--src/http_ext.c32
-rw-r--r--src/linuxcap.c10
-rw-r--r--src/listener.c30
-rw-r--r--src/log.c8
-rw-r--r--src/mux_fcgi.c9
-rw-r--r--src/mux_h1.c5
-rw-r--r--src/mux_h2.c165
-rw-r--r--src/mux_quic.c14
-rw-r--r--src/peers.c18
-rw-r--r--src/proxy.c2
-rw-r--r--src/qpack-dec.c25
-rw-r--r--src/quic_cli.c2
-rw-r--r--src/quic_frame.c8
-rw-r--r--src/quic_rx.c32
-rw-r--r--src/quic_sock.c8
-rw-r--r--src/quic_ssl.c21
-rw-r--r--src/quic_tls.c7
-rw-r--r--src/server.c21
-rw-r--r--src/sink.c14
-rw-r--r--src/ssl_ckch.c2
-rw-r--r--src/ssl_crtlist.c7
-rw-r--r--src/ssl_ocsp.c45
-rw-r--r--src/ssl_sample.c8
-rw-r--r--src/ssl_sock.c8
-rw-r--r--src/stats.c20
-rw-r--r--src/stconn.c4
-rw-r--r--src/stick_table.c4
-rw-r--r--src/stream.c26
-rw-r--r--src/tools.c2
61 files changed, 1297 insertions, 417 deletions
diff --git a/.github/matrix.py b/.github/matrix.py
index 856704d..53279c4 100755
--- a/.github/matrix.py
+++ b/.github/matrix.py
@@ -125,6 +125,7 @@ def main(ref_name):
"TARGET": TARGET,
"CC": CC,
"FLAGS": [
+ 'DEBUG_CFLAGS="-DDEBUG_LIST"',
"USE_ZLIB=1",
"USE_OT=1",
"OT_INC=${HOME}/opt-ot/include",
diff --git a/.github/workflows/cross-zoo.yml b/.github/workflows/cross-zoo.yml
index d9864e2..5abd9cb 100644
--- a/.github/workflows/cross-zoo.yml
+++ b/.github/workflows/cross-zoo.yml
@@ -90,6 +90,7 @@ jobs:
}
]
runs-on: ubuntu-latest
+ if: ${{ github.repository_owner == 'haproxy' }}
steps:
- name: install packages
run: |
diff --git a/.github/workflows/fedora-rawhide.yml b/.github/workflows/fedora-rawhide.yml
index 8f25781..1bb2745 100644
--- a/.github/workflows/fedora-rawhide.yml
+++ b/.github/workflows/fedora-rawhide.yml
@@ -14,6 +14,7 @@ jobs:
cc: [ gcc, clang ]
name: ${{ matrix.cc }}
runs-on: ubuntu-latest
+ if: ${{ github.repository_owner == 'haproxy' }}
container:
image: fedora:rawhide
steps:
diff --git a/.github/workflows/vtest.yml b/.github/workflows/vtest.yml
index 7b5254b..d0e4ec3 100644
--- a/.github/workflows/vtest.yml
+++ b/.github/workflows/vtest.yml
@@ -57,6 +57,17 @@ jobs:
run: |
echo "key=$(echo ${{ matrix.name }} | sha256sum | awk '{print $1}')" >> $GITHUB_OUTPUT
+
+#
+# temporary hack
+# should be revisited after https://github.com/actions/runner-images/issues/9491 is resolved
+#
+
+ - name: Setup enthropy
+ if: ${{ startsWith(matrix.os, 'ubuntu-') }}
+ run: |
+ sudo sysctl vm.mmap_rnd_bits=28
+
- name: Cache SSL libs
if: ${{ matrix.ssl && matrix.ssl != 'stock' && matrix.ssl != 'BORINGSSL=yes' && matrix.ssl != 'QUICTLS=yes' }}
id: cache_ssl
diff --git a/CHANGELOG b/CHANGELOG
index 427836c..801a344 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,94 @@
ChangeLog :
===========
+2024/04/05 : 2.9.7
+ - MINOR: mux-h2: add a counter of "glitches" on a connection
+ - BUG/MINOR: mux-h2: count rejected DATA frames against the connection's flow control
+ - MINOR: mux-h2: count excess of CONTINUATION frames as a glitch
+ - MINOR: mux-h2: count late reduction of INITIAL_WINDOW_SIZE as a glitch
+ - MINOR: mux-h2: always use h2c_report_glitch()
+ - MEDIUM: mux-h2: allow to set the glitches threshold to kill a connection
+ - MINOR: connection: add a new mux_ctl to report number of connection glitches
+ - MINOR: mux-h2: implement MUX_CTL_GET_GLITCHES
+ - MINOR: connection: add sample fetches to report per-connection glitches
+ - BUG/MINOR: quic: reject unknown frame type
+ - BUG/MINOR: quic: reject HANDSHAKE_DONE as server
+ - BUG/MINOR: qpack: reject invalid increment count decoding
+ - BUG/MINOR: qpack: reject invalid dynamic table capacity
+ - DOC: quic: Missing tuning setting in "Global parameters"
+ - BUG/MEDIUM: applet: Immediately free appctx on early error
+ - BUG/MEDIUM: hlua: Be able to garbage collect uninitialized lua sockets
+ - BUG/MEDIUM: hlua: Don't loop if a lua socket does not consume received data
+ - BUG/MEDIUM: quic: fix transient send error with listener socket
+ - DOC: quic: fix recommandation for bind on multiple address
+ - MINOR: quic: warn on bind on multiple addresses if no IP_PKTINFO support
+ - BUG/MINOR: ist: allocate nul byte on istdup
+ - BUG/MINOR: stats: drop srv refcount on early release
+ - BUG/MAJOR: server: fix stream crash due to deleted server
+ - MINOR: cli: Remove useless loop on commands to find unescaped semi-colon
+ - BUG/MEDIUM: cli: Warn if pipelined commands are delimited by a \n
+ - BUG/MINOR: quic: fix output of show quic
+ - BUG/MINOR: ist: only store NUL byte on succeeded alloc
+ - BUG/MINOR: ssl/cli: duplicate cleaning code in cli_parse_del_crtlist
+ - LICENSE: event_hdl: fix GPL license version
+ - LICENSE: http_ext: fix GPL license version
+ - BUG/MINOR: mux-h1: Properly report when mux is blocked during a nego
+ - DOC: configuration: clarify ciphersuites usage
+ - BUG/MINOR: config/quic: Alert about PROXY protocol use on a QUIC listener
+ - BUG/MINOR: hlua: Fix log level to the right value when set via TXN:set_loglevel
+ - MINOR: hlua: Be able to disable logging from lua
+ - BUG/MINOR: tools: seed the statistical PRNG slightly better
+ - BUG/MINOR: hlua: fix unsafe lua_tostring() usage with empty stack
+ - BUG/MINOR: hlua: don't use lua_tostring() from unprotected contexts
+ - BUG/MINOR: hlua: fix possible crash in hlua_filter_new() under load
+ - BUG/MINOR: hlua: improper lock usage in hlua_filter_callback()
+ - BUG/MINOR: hlua: improper lock usage in hlua_filter_new()
+ - BUG/MEDIUM: hlua: improper lock usage with SET_SAFE_LJMP()
+ - BUG/MAJOR: hlua: improper lock usage with hlua_ctx_resume()
+ - BUG/MINOR: hlua: don't call ha_alert() in hlua_event_subscribe()
+ - BUG/MINOR: sink: fix a race condition in the TCP log forwarding code
+ - CI: skip scheduled builds on forks
+ - BUG/MINOR: ssl/cli: typo in new ssl crl-file CLI description
+ - BUG/MINOR: cfgparse: report proper location for log-format-sd errors
+ - BUG/MEDIUM: quic: fix handshake freeze under high traffic
+ - MINOR: quic: always use ncbuf for rx CRYPTO
+ - BUILD: solaris: fix compilation errors
+ - DOC: configuration: clarify ciphersuites usage (V2)
+ - BUG/MINOR: ssl: fix possible ctx memory leak in sample_conv_aes_gcm()
+ - CI: github: add -DDEBUG_LIST to the default builds
+ - BUG/MINOR: hlua: segfault when loading the same filter from different contexts
+ - BUG/MINOR: hlua: missing lock in hlua_filter_new()
+ - BUG/MINOR: hlua: fix missing lock in hlua_filter_delete()
+ - DEBUG: lua: precisely identify if stream is stuck inside lua or not
+ - MINOR: hlua: use accessors for stream hlua ctx
+ - BUG/MEDIUM: hlua: streams don't support mixing lua-load with lua-load-per-thread (2nd try)
+ - BUG/MINOR: listener: Wake proxy's mngmt task up if necessary on session release
+ - BUG/MINOR: listener: Don't schedule frontend without task in listener_release()
+ - BUG/MEDIUM: spoe: Don't rely on stream's expiration to detect processing timeout
+ - BUG/MINOR: spoe: Be sure to be able to quickly close IDLE applets on soft-stop
+ - CI: temporarily adjust kernel entropy to work with ASAN/clang
+ - BUG/MEDIUM: spoe: Return an invalid frame on recv if size is too small
+ - BUG/MINOR: session: ensure conn owner is set after insert into session
+ - BUG/MEDIUM: ssl: Fix crash in ocsp-update log function
+ - BUG/MINOR: mux-quic: close all QCS before freeing QCC tasklet
+ - BUG/MEDIUM: mux-fcgi: Properly handle EOM flag on end-of-trailers HTX block
+ - OPTIM: http_ext: avoid useless copy in http_7239_extract_{ipv4,ipv6}
+ - BUG/MINOR: server: 'source' interface ignored from 'default-server' directive
+ - BUILD: ssl: fix build error on older compilers with openssl-3.2
+ - BUG/MINOR: ssl: Wrong ocsp-update "incompatibility" error message
+ - BUG/MINOR: ssl: Detect more 'ocsp-update' incompatibilities
+ - BUG/MINOR: server: fix persistence cookie for dynamic servers
+ - MINOR: server: allow cookie for dynamic servers
+ - BUG/MINOR: server: ignore 'enabled' for dynamic servers
+ - DOC: config: balance 'first' not usable in LOG mode
+ - BUG/MINOR: log/balance: detect if user tries to use unsupported algo
+ - BUG/MEDIUM: stick-tables: fix a small remaining race in expiration task
+ - BUG/MINOR: backend: properly handle redispatch 0
+ - BUG/MEDIUM: stconn: Don't forward shutdown to SE if iobuf is not empty
+ - BUG/MEDIUM: stick-table: use the update lock when reading tables from peers
+ - BUG/MINOR: proxy: fix logformat expression leak in use_backend rules
+ - BUG/MINOR: init: relax LSTCHK_NETADM checks for non root
+
2024/02/26 : 2.9.6
- BUG/MAJOR: promex: fix crash on deleted server
- BUG/MAJOR: ssl/ocsp: crash with ocsp when old process exit or using ocsp CLI
diff --git a/SUBVERS b/SUBVERS
index 3fc4456..7456baf 100644
--- a/SUBVERS
+++ b/SUBVERS
@@ -1,2 +1,2 @@
--9eafce5
+-5742051
diff --git a/VERDATE b/VERDATE
index 77054ec..92fb954 100644
--- a/VERDATE
+++ b/VERDATE
@@ -1,2 +1,2 @@
-2024-02-26 18:42:42 +0100
-2024/02/26
+2024-04-05 20:18:55 +0200
+2024/04/05
diff --git a/VERSION b/VERSION
index 23ae1b5..1020118 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.9.6
+2.9.7
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 978d655..e1c5034 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -3,7 +3,7 @@
Configuration Manual
----------------------
version 2.9
- 2024/02/26
+ 2024/04/05
This document covers the configuration language as implemented in the version
@@ -1325,8 +1325,10 @@ The following keywords are supported in the "global" section :
- tune.fd.edge-triggered
- tune.h1.zero-copy-fwd-recv
- tune.h1.zero-copy-fwd-send
+ - tune.h2.be.glitches-threshold
- tune.h2.be.initial-window-size
- tune.h2.be.max-concurrent-streams
+ - tune.h2.fe.glitches-threshold
- tune.h2.fe.initial-window-size
- tune.h2.fe.max-concurrent-streams
- tune.h2.fe.max-total-streams
@@ -1362,6 +1364,7 @@ The following keywords are supported in the "global" section :
- tune.quic.frontend.max-idle-timeout
- tune.quic.frontend.max-streams-bidi
- tune.quic.max-frame-loss
+ - tune.quic.reorder-ratio
- tune.quic.retry-threshold
- tune.quic.socket-owner
- tune.quic.zero-copy-fwd-send
@@ -2228,8 +2231,26 @@ ssl-default-bind-ciphersuites <ciphersuites>
theirs. The format of the string is defined in
"man 1 ciphers" from OpenSSL man pages under the section "ciphersuites". For
cipher configuration for TLSv1.2 and earlier, please check the
- "ssl-default-bind-ciphers" keyword. Please check the "bind" keyword for more
- information.
+ "ssl-default-bind-ciphers" keyword. This setting might accept TLSv1.2
+ ciphersuites however this is an undocumented behavior and not recommended as
+ it could be inconsistent or buggy.
+ The default TLSv1.3 ciphersuites of OpenSSL are:
+ "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256"
+
+ TLSv1.3 only supports 5 ciphersuites:
+
+ - TLS_AES_128_GCM_SHA256
+ - TLS_AES_256_GCM_SHA384
+ - TLS_CHACHA20_POLY1305_SHA256
+ - TLS_AES_128_CCM_SHA256
+ - TLS_AES_128_CCM_8_SHA256
+
+ Please check the "bind" keyword for more information.
+
+ Example:
+ global
+ ssl-default-bind-ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256
+ ssl-default-bind-ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
ssl-default-bind-client-sigalgs <sigalgs>
This setting is only available when support for OpenSSL was built in. It sets
@@ -3056,6 +3077,18 @@ tune.h1.zero-copy-fwd-send { on | off }
See also: tune.disable-zero-copy-forwarding, tune.h1.zero-copy-fwd-recv
+tune.h2.be.glitches-threshold <number>
+ Sets the threshold for the number of glitches on a backend connection, where
+ that connection will automatically be killed. This allows to automatically
+ kill misbehaving connections without having to write explicit rules for them.
+ The default value is zero, indicating that no threshold is set so that no
+ event will cause a connection to be closed. Beware that some H2 servers may
+ occasionally cause a few glitches over long lasting connection, so any non-
+ zero value here should probably be in the hundreds or thousands to be
+ effective without affecting slightly bogus servers.
+
+ See also: tune.h2.fe.glitches-threshold, bc_glitches
+
tune.h2.be.initial-window-size <number>
Sets the HTTP/2 initial window size for outgoing connections, which is the
number of bytes the server can respond before waiting for an acknowledgment
@@ -3081,6 +3114,18 @@ tune.h2.be.max-concurrent-streams <number>
case). It is highly recommended not to increase this value; some might find
it optimal to run at low values (1..5 typically).
+tune.h2.fe.glitches-threshold <number>
+ Sets the threshold for the number of glitches on a frontend connection, where
+ that connection will automatically be killed. This allows to automatically
+ kill misbehaving connections without having to write explicit rules for them.
+ The default value is zero, indicating that no threshold is set so that no
+ event will cause a connection to be closed. Beware that some H2 clientss may
+ occasionally cause a few glitches over long lasting connection, so any non-
+ zero value here should probably be in the hundreds or thousands to be
+ effective without affecting slightly bogus clients.
+
+ See also: tune.h2.be.glitches-threshold, fc_glitches
+
tune.h2.fe.initial-window-size <number>
Sets the HTTP/2 initial window size for incoming connections, which is the
number of bytes the client can upload before waiting for an acknowledgment
@@ -5014,6 +5059,7 @@ balance url_param <param> [check_post]
them off when unused, and regularly checks backend queue to
turn new servers on when the queue inflates. Alternatively,
using "http-check send-state" may inform servers on the load.
+ This algorithm is not usable in LOG mode.
hash Takes a regular sample expression in argument. The expression
is evaluated for each request and hashed according to the
@@ -5296,10 +5342,12 @@ bind /<path> [, ...] [param*]
listen on. If unset, all IPv4 addresses of the system will be
listened on. The same will apply for '*' or the system's
special address "0.0.0.0". The IPv6 equivalent is '::'. Note
- that if you bind a frontend to multiple UDP addresses you have
- no guarantee about the address which will be used to respond.
- This is why "0.0.0.0" addresses and lists of comma-separated
- IP addresses have been forbidden to bind QUIC addresses.
+ that for UDP, specific OS features are required when binding
+ on multiple addresses to ensure the correct network interface
+ and source address will be used on response. In other way,
+ for QUIC listeners only bind on multiple addresses if running
+ with a modern enough systems.
+
Optionally, an address family prefix may be used before the
address to force the family regardless of the address format,
which can be useful to specify a path to a unix socket with
@@ -15370,6 +15418,22 @@ ciphersuites <ciphersuites>
TLSv1.3 handshake. The format of the string is defined in "man 1 ciphers" from
OpenSSL man pages under the "ciphersuites" section. For cipher configuration
for TLSv1.2 and earlier, please check the "ciphers" keyword.
+ This setting might accept TLSv1.2 ciphersuites however this is an
+ undocumented behavior and not recommended as it could be inconsistent or buggy.
+ The default TLSv1.3 ciphersuites of OpenSSL are:
+ "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256"
+
+ TLSv1.3 only supports 5 ciphersuites:
+
+ - TLS_AES_128_GCM_SHA256
+ - TLS_AES_256_GCM_SHA384
+ - TLS_CHACHA20_POLY1305_SHA256
+ - TLS_AES_128_CCM_SHA256
+ - TLS_AES_128_CCM_8_SHA256
+
+ Example:
+ ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256
+ ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
client-sigalgs <sigalgs>
This setting is only available when support for OpenSSL was built in. It sets
@@ -20670,6 +20734,7 @@ bc_dst ip
bc_dst_port integer
bc_err integer
bc_err_str string
+bc_glitches integer
bc_http_major integer
bc_src ip
bc_src_port integer
@@ -20696,6 +20761,7 @@ fc_dst_port integer
fc_err integer
fc_err_str string
fc_fackets integer
+fc_glitches integer
fc_http_major integer
fc_lost integer
fc_pp_authority string
@@ -20910,6 +20976,20 @@ bc_err_str : string
"fc_err_str" fetch for a full list of error codes and their
corresponding error message.
+bc_glitches : integer
+ Returns the number of protocol glitches counted on the backend connection.
+ These generally cover protocol violations as well as small anomalies that
+ generally indicate a bogus or misbehaving server that may cause trouble in
+ the infrastructure (e.g. cause connections to be aborted early, inducing
+ frequent TLS renegotiations). These may also be caused by too large responses
+ that cannot fit into a single buffer, explaining HTTP 502 errors. Ideally
+ this number should remain zero, though it's generally fine if it remains very
+ low compared to the total number of requests. These values should normally
+ not be considered as alarming (especially small ones), though a sudden jump
+ may indicate an anomaly somewhere. Not all protocol multiplexers measure this
+ metric and the only way to get more details about the events is to enable
+ traces to capture all exchanges.
+
bc_http_major : integer
Returns the backend connection's HTTP major version encoding, which may be 1
for HTTP/0.9 to HTTP/1.1 or 2 for HTTP/2. Note, this is based on the on-wire
@@ -21121,6 +21201,23 @@ fc_fackets : integer
not TCP or if the operating system does not support TCP_INFO, for example
Linux kernels before 2.4, the sample fetch fails.
+fc_glitches : integer
+ Returns the number of protocol glitches counted on the frontend connection.
+ These generally cover protocol violations as well as small anomalies that
+ generally indicate a bogus or misbehaving client that may cause trouble in
+ the infrastructure, such as excess of errors in the logs, or many connections
+ being aborted early, inducing frequent TLS renegotiations. These may also be
+ caused by too large requests that cannot fit into a single buffer, explaining
+ HTTP 400 errors. Ideally this number should remain zero, though it may be
+ possible that some browsers playing with the protocol boundaries trigger it
+ once in a while. These values should normally not be considered as alarming
+ (especially small ones), though a sudden jump may indicate an anomaly
+ somewhere. Large values (i.e. hundreds to thousands per connection, or as
+ many as the requests) may indicate a purposely built client that is trying to
+ fingerprint or attack the protocol stack. Not all protocol multiplexers
+ measure this metric, and the only way to get more details about the events is
+ to enable traces to capture all exchanges.
+
fc_http_major : integer
Reports the front connection's HTTP major version encoding, which may be 1
for HTTP/0.9 to HTTP/1.1 or 2 for HTTP/2. Note, this is based on the on-wire
diff --git a/doc/lua-api/index.rst b/doc/lua-api/index.rst
index e8df63e..17927f3 100644
--- a/doc/lua-api/index.rst
+++ b/doc/lua-api/index.rst
@@ -159,6 +159,13 @@ Core class
The "core" class is static, it is not possible to create a new object of this
type.
+.. js:attribute:: core.silent
+
+ :returns: integer
+
+ This attribute is an integer, it contains the value -1. It is a special value
+ used to disable logging.
+
.. js:attribute:: core.emerg
:returns: integer
@@ -2873,12 +2880,12 @@ TXN class
.. js:function:: TXN.set_loglevel(txn, loglevel)
Is used to change the log level of the current request. The "loglevel" must
- be an integer between 0 and 7.
+ be an integer between 0 and 7 or the special value -1 to disable logging.
:param class_txn txn: The class txn object containing the data.
:param integer loglevel: The required log level. This variable can be one of
- :see: :js:attr:`core.emerg`, :js:attr:`core.alert`, :js:attr:`core.crit`,
- :js:attr:`core.err`, :js:attr:`core.warning`, :js:attr:`core.notice`,
+ :see: :js:attr:`core.silent`, :js:attr:`core.emerg`, :js:attr:`core.alert`,
+ :js:attr:`core.crit`, :js:attr:`core.err`, :js:attr:`core.warning`, :js:attr:`core.notice`,
:js:attr:`core.info`, :js:attr:`core.debug` (log level definitions)
.. js:function:: TXN.set_tos(txn, tos)
diff --git a/doc/management.txt b/doc/management.txt
index b1789db..9cbc772 100644
--- a/doc/management.txt
+++ b/doc/management.txt
@@ -1687,6 +1687,7 @@ add server <backend>/<server> [args]*
- check-via-socks4
- ciphers
- ciphersuites
+ - cookie
- crl-file
- crt
- disabled
diff --git a/include/haproxy/cli-t.h b/include/haproxy/cli-t.h
index c155df3..cad6728 100644
--- a/include/haproxy/cli-t.h
+++ b/include/haproxy/cli-t.h
@@ -45,6 +45,7 @@
#define APPCTX_CLI_ST1_PAYLOAD (1 << 1)
#define APPCTX_CLI_ST1_NOLF (1 << 2)
#define APPCTX_CLI_ST1_TIMED (1 << 3)
+#define APPCTX_CLI_ST1_SHUT_EXPECTED (1 << 4)
#define CLI_PREFIX_KW_NB 5
#define CLI_MAX_MATCHES 5
diff --git a/include/haproxy/compat.h b/include/haproxy/compat.h
index aa4f952..0fe5a0b 100644
--- a/include/haproxy/compat.h
+++ b/include/haproxy/compat.h
@@ -303,6 +303,12 @@ typedef struct { } empty_t;
#define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC_FAST
#endif
+/* On Solaris, `queue` is a reserved name, so we redefine it here for now.
+ */
+#if defined(sun)
+#define queue _queue
+#endif
+
#endif /* _HAPROXY_COMPAT_H */
/*
diff --git a/include/haproxy/connection-t.h b/include/haproxy/connection-t.h
index 2619fd6..71269c6 100644
--- a/include/haproxy/connection-t.h
+++ b/include/haproxy/connection-t.h
@@ -337,6 +337,7 @@ enum mux_ctl_type {
MUX_CTL_EXIT_STATUS, /* Expects an int as output, sets the mux exist/error/http status, if known or 0 */
MUX_CTL_REVERSE_CONN, /* Notify about an active reverse connection accepted. */
MUX_CTL_SUBS_RECV, /* Notify the mux it must wait for read events again */
+ MUX_CTL_GET_GLITCHES, /* returns number of glitches on the connection */
};
/* sctl command used by mux->sctl() */
diff --git a/include/haproxy/hlua-t.h b/include/haproxy/hlua-t.h
index 2672ffd..af54d86 100644
--- a/include/haproxy/hlua-t.h
+++ b/include/haproxy/hlua-t.h
@@ -67,6 +67,7 @@ struct stream;
#define HLUA_WAKEREQWR 0x00000008
#define HLUA_EXIT 0x00000010
#define HLUA_NOYIELD 0x00000020
+#define HLUA_BUSY 0x00000040
#define HLUA_F_AS_STRING 0x01
#define HLUA_F_MAY_USE_HTTP 0x02
diff --git a/include/haproxy/hlua.h b/include/haproxy/hlua.h
index 3c67cce..18fad9f 100644
--- a/include/haproxy/hlua.h
+++ b/include/haproxy/hlua.h
@@ -30,6 +30,9 @@
#define HLUA_SET_RUN(__hlua) do {(__hlua)->flags |= HLUA_RUN;} while(0)
#define HLUA_CLR_RUN(__hlua) do {(__hlua)->flags &= ~HLUA_RUN;} while(0)
#define HLUA_IS_RUNNING(__hlua) ((__hlua)->flags & HLUA_RUN)
+#define HLUA_SET_BUSY(__hlua) do {(__hlua)->flags |= HLUA_BUSY;} while(0)
+#define HLUA_CLR_BUSY(__hlua) do {(__hlua)->flags &= ~HLUA_BUSY;} while(0)
+#define HLUA_IS_BUSY(__hlua) ((__hlua)->flags & HLUA_BUSY)
#define HLUA_SET_CTRLYIELD(__hlua) do {(__hlua)->flags |= HLUA_CTRLYIELD;} while(0)
#define HLUA_CLR_CTRLYIELD(__hlua) do {(__hlua)->flags &= ~HLUA_CTRLYIELD;} while(0)
#define HLUA_IS_CTRLYIELDING(__hlua) ((__hlua)->flags & HLUA_CTRLYIELD)
diff --git a/include/haproxy/quic_tls-t.h b/include/haproxy/quic_tls-t.h
index ae65149..326e01b 100644
--- a/include/haproxy/quic_tls-t.h
+++ b/include/haproxy/quic_tls-t.h
@@ -254,8 +254,6 @@ struct quic_enc_level {
struct eb_root pkts;
/* List of QUIC packets with protected header. */
struct list pqpkts;
- /* List of crypto frames received in order. */
- struct list crypto_frms;
} rx;
/* TX part */
diff --git a/include/haproxy/session.h b/include/haproxy/session.h
index 38335e4..8a62805 100644
--- a/include/haproxy/session.h
+++ b/include/haproxy/session.h
@@ -188,6 +188,12 @@ static inline int session_add_conn(struct session *sess, struct connection *conn
LIST_APPEND(&sess->srv_list, &srv_list->srv_list);
}
LIST_APPEND(&srv_list->conn_list, &conn->session_list);
+
+ /* Ensure owner is set for connection. It could have been resetted
+ * prior on after a session_add_conn() failure.
+ */
+ conn->owner = sess;
+
return 1;
}
diff --git a/include/haproxy/stream-t.h b/include/haproxy/stream-t.h
index 7e79b96..4280692 100644
--- a/include/haproxy/stream-t.h
+++ b/include/haproxy/stream-t.h
@@ -279,7 +279,7 @@ struct stream {
int last_rule_line; /* last evaluated final rule's line (def: 0) */
unsigned int stream_epoch; /* copy of stream_epoch when the stream was created */
- struct hlua *hlua; /* lua runtime context */
+ struct hlua *hlua[2]; /* lua runtime context (0: global, 1: per-thread) */
/* Context */
struct {
diff --git a/include/import/ist.h b/include/import/ist.h
index 16b8616..e4e1425 100644
--- a/include/import/ist.h
+++ b/include/import/ist.h
@@ -939,15 +939,12 @@ static inline void istfree(struct ist *ist)
*/
static inline struct ist istdup(const struct ist src)
{
- const size_t src_size = src.len;
-
- /* Allocate at least 1 byte to allow duplicating an empty string with
- * malloc implementations that return NULL for a 0-size allocation.
- */
- struct ist dst = istalloc(src_size ? src_size : 1);
+ /* Allocate 1 extra byte to add an extra \0 delimiter. */
+ struct ist dst = istalloc(src.len + 1);
if (isttest(dst)) {
- istcpy(&dst, src, src_size);
+ istcpy(&dst, src, src.len);
+ dst.ptr[dst.len] = '\0';
}
return dst;
diff --git a/reg-tests/connection/h2_glitches.vtc b/reg-tests/connection/h2_glitches.vtc
new file mode 100644
index 0000000..39ec4d6
--- /dev/null
+++ b/reg-tests/connection/h2_glitches.vtc
@@ -0,0 +1,108 @@
+# This test verifies that H2 anomalies counted as glitches are properly detected
+# and fetched.
+
+varnishtest "h2 glitches"
+feature ignore_unknown_macro
+
+# haproxy frontend
+haproxy hap -conf {
+ defaults
+ mode http
+
+ listen fe1
+ bind "fd@${fe1}" proto h2
+ http-request return status 200 hdr x-glitches %[fc_glitches]
+} -start
+
+# valid request: no glitch
+client c1 -connect ${hap_fe1_sock} {
+ txpri
+ stream 0 {
+ txsettings
+ rxsettings
+ txsettings -ack
+ rxsettings
+ expect settings.ack == true
+ } -run
+
+ stream 1 {
+ txreq \
+ -method "GET" \
+ -scheme "http" \
+ -url "/"
+ rxresp
+ expect resp.status == 200
+ expect resp.http.x-glitches == 0
+ } -run
+
+ stream 3 {
+ txreq \
+ -method "GET" \
+ -scheme "http" \
+ -url "/"
+ rxresp
+ expect resp.status == 200
+ expect resp.http.x-glitches == 0
+ } -run
+} -run
+
+# invalid path: => req decoding error => glitch++
+client c2-path -connect ${hap_fe1_sock} {
+ txpri
+ stream 0 {
+ txsettings
+ rxsettings
+ txsettings -ack
+ rxsettings
+ expect settings.ack == true
+ } -run
+
+ stream 1 {
+ txreq \
+ -method "GET" \
+ -scheme "http" \
+ -url "hello-world"
+ rxrst
+ } -run
+
+ stream 3 {
+ txreq \
+ -method "GET" \
+ -scheme "http" \
+ -url "/"
+ rxresp
+ expect resp.status == 200
+ expect resp.http.x-glitches == 1
+ } -run
+} -run
+
+# invalid scheme: blocked at HTX layer, not counted
+client c3-scheme -connect ${hap_fe1_sock} {
+ txpri
+ stream 0 {
+ txsettings
+ rxsettings
+ txsettings -ack
+ rxsettings
+ expect settings.ack == true
+ } -run
+
+ stream 1 {
+ txreq \
+ -method "GET" \
+ -scheme "http://localhost/?" \
+ -url "/"
+ rxresp
+ expect resp.status == 400
+ } -run
+
+ stream 3 {
+ txreq \
+ -method "GET" \
+ -scheme "http" \
+ -url "/"
+ rxresp
+ expect resp.status == 200
+ expect resp.http.x-glitches == 0
+ } -run
+} -run
diff --git a/reg-tests/http-rules/forwarded-header-7239.vtc b/reg-tests/http-rules/forwarded-header-7239.vtc
index a894113..dd2c6e3 100644
--- a/reg-tests/http-rules/forwarded-header-7239.vtc
+++ b/reg-tests/http-rules/forwarded-header-7239.vtc
@@ -151,6 +151,12 @@ client c1 -connect ${h1_fe1_sock} {
expect resp.status == 200
expect resp.http.nodename == "::1"
expect resp.http.nodeport == "_id"
+
+ txreq -req GET -url /req4 \
+ -hdr "forwarded: for=127.9.0.1"
+ rxresp
+ expect resp.status == 200
+ expect resp.http.nodename == "127.9.0.1"
} -run
client c2 -connect ${h2_fe1h2_sock} {
diff --git a/src/applet.c b/src/applet.c
index a5b0946..b695a9f 100644
--- a/src/applet.c
+++ b/src/applet.c
@@ -298,7 +298,7 @@ void appctx_free_on_early_error(struct appctx *appctx)
stream_free(appctx_strm(appctx));
return;
}
- appctx_free(appctx);
+ __appctx_free(appctx);
}
void appctx_free(struct appctx *appctx)
diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c
index 4f88b77..a97b1e5 100644
--- a/src/cfgparse-listen.c
+++ b/src/cfgparse-listen.c
@@ -2050,8 +2050,10 @@ stats_error_parsing:
case KWM_STD:
curproxy->options |= PR_O_REDISP;
curproxy->redispatch_after = -1;
- if(*args[2]) {
+ if (*args[2]) {
curproxy->redispatch_after = atol(args[2]);
+ if (!curproxy->redispatch_after)
+ curproxy->options &= ~PR_O_REDISP;
}
break;
case KWM_NO:
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 2744f97..bee3040 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -3434,12 +3434,12 @@ out_uri_auth_compat:
LOG_OPT_MANDATORY|LOG_OPT_MERGE_SPACES,
SMP_VAL_FE_LOG_END, &err)) {
ha_alert("Parsing [%s:%d]: failed to parse log-format-sd : %s.\n",
- curproxy->conf.lfs_file, curproxy->conf.lfs_line, err);
+ curproxy->conf.lfsd_file, curproxy->conf.lfsd_line, err);
free(err);
cfgerr++;
} else if (!add_to_logformat_list(NULL, NULL, LF_SEPARATOR, &curproxy->logformat_sd, &err)) {
ha_alert("Parsing [%s:%d]: failed to parse log-format-sd : %s.\n",
- curproxy->conf.lfs_file, curproxy->conf.lfs_line, err);
+ curproxy->conf.lfsd_file, curproxy->conf.lfsd_line, err);
free(err);
cfgerr++;
}
@@ -3964,13 +3964,21 @@ out_uri_auth_compat:
int mode = conn_pr_mode_to_proto_mode(curproxy->mode);
const struct mux_proto_list *mux_ent;
- if (!bind_conf->mux_proto) {
- /* No protocol was specified. If we're using QUIC at the transport
- * layer, we'll instantiate it as a mux as well. If QUIC is not
- * compiled in, this will remain NULL.
- */
- if (bind_conf->xprt && bind_conf->xprt == xprt_get(XPRT_QUIC))
+ if (bind_conf->xprt && bind_conf->xprt == xprt_get(XPRT_QUIC)) {
+ if (!bind_conf->mux_proto) {
+ /* No protocol was specified. If we're using QUIC at the transport
+ * layer, we'll instantiate it as a mux as well. If QUIC is not
+ * compiled in, this will remain NULL.
+ */
bind_conf->mux_proto = get_mux_proto(ist("quic"));
+ }
+ if (bind_conf->options & BC_O_ACC_PROXY) {
+ ha_alert("Binding [%s:%d] for %s %s: QUIC protocol does not support PROXY protocol yet."
+ " 'accept-proxy' option cannot be used with a QUIC listener.\n",
+ bind_conf->file, bind_conf->line,
+ proxy_type_str(curproxy), curproxy->id);
+ cfgerr++;
+ }
}
if (!bind_conf->mux_proto)
diff --git a/src/cli.c b/src/cli.c
index d0435f7..51f3d77 100644
--- a/src/cli.c
+++ b/src/cli.c
@@ -813,6 +813,22 @@ static int cli_parse_request(struct appctx *appctx)
if (!**args)
return 0;
+ if (appctx->st1 & APPCTX_CLI_ST1_SHUT_EXPECTED) {
+ /* The previous command line was finished by a \n in non-interactive mode.
+ * It should not be followed by another command line. In non-interactive mode,
+ * only one line should be processed. Because of a bug, it is not respected.
+ * So emit a warning, only once in the process life, to warn users their script
+ * must be updated.
+ */
+ appctx->st1 &= ~APPCTX_CLI_ST1_SHUT_EXPECTED;
+ if (ONLY_ONCE()) {
+ ha_warning("Commands sent to the CLI were chained using a new line character while in non-interactive mode."
+ " This is not reliable, not officially supported and will not be supported anymore in future versions. "
+ "Please use ';' to delimit commands instead.");
+ }
+ }
+
+
kw = cli_find_kw(args);
if (!kw ||
(kw->level & ~appctx->cli_level & ACCESS_MASTER_ONLY) ||
@@ -916,6 +932,7 @@ static void cli_io_handler(struct appctx *appctx)
struct bind_conf *bind_conf = strm_li(__sc_strm(sc))->bind_conf;
int reql;
int len;
+ int lf = 0;
if (unlikely(se_fl_test(appctx->sedesc, (SE_FL_EOS|SE_FL_ERROR|SE_FL_SHR|SE_FL_SHW)))) {
co_skip(sc_oc(sc), co_data(sc_oc(sc)));
@@ -987,29 +1004,15 @@ static void cli_io_handler(struct appctx *appctx)
continue;
}
- if (!(appctx->st1 & APPCTX_CLI_ST1_PAYLOAD)) {
- /* seek for a possible unescaped semi-colon. If we find
- * one, we replace it with an LF and skip only this part.
- */
- for (len = 0; len < reql; len++) {
- if (str[len] == '\\') {
- len++;
- continue;
- }
- if (str[len] == ';') {
- str[len] = '\n';
- reql = len + 1;
- break;
- }
- }
- }
+ if (str[reql-1] == '\n')
+ lf = 1;
/* now it is time to check that we have a full line,
* remove the trailing \n and possibly \r, then cut the
* line.
*/
len = reql - 1;
- if (str[len] != '\n') {
+ if (str[len] != '\n' && str[len] != ';') {
se_fl_set(appctx->sedesc, SE_FL_ERROR);
appctx->st0 = CLI_ST_END;
continue;
@@ -1044,6 +1047,8 @@ static void cli_io_handler(struct appctx *appctx)
*/
appctx->st1 &= ~APPCTX_CLI_ST1_PAYLOAD;
+ if (!(appctx->st1 & APPCTX_CLI_ST1_PROMPT) && lf)
+ appctx->st1 |= APPCTX_CLI_ST1_SHUT_EXPECTED;
}
}
}
@@ -1082,6 +1087,8 @@ static void cli_io_handler(struct appctx *appctx)
/* no payload, the command is complete: parse the request */
cli_parse_request(appctx);
chunk_reset(appctx->chunk);
+ if (!(appctx->st1 & APPCTX_CLI_ST1_PROMPT) && lf)
+ appctx->st1 |= APPCTX_CLI_ST1_SHUT_EXPECTED;
}
}
diff --git a/src/connection.c b/src/connection.c
index 7930cc4..ed6beb7 100644
--- a/src/connection.c
+++ b/src/connection.c
@@ -2242,6 +2242,40 @@ int conn_append_debug_info(struct buffer *buf, const struct connection *conn, co
return buf->data - old_len;
}
+/* return the number of glitches experienced on the mux connection. */
+static int
+smp_fetch_fc_glitches(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+ struct connection *conn = NULL;
+ int ret;
+
+ if (obj_type(smp->sess->origin) == OBJ_TYPE_CHECK)
+ conn = (kw[0] == 'b') ? sc_conn(__objt_check(smp->sess->origin)->sc) : NULL;
+ else
+ conn = (kw[0] != 'b') ? objt_conn(smp->sess->origin) :
+ smp->strm ? sc_conn(smp->strm->scb) : NULL;
+
+ /* No connection or a connection with an unsupported mux */
+ if (!conn || (conn->mux && !conn->mux->ctl))
+ return 0;
+
+ /* Mux not installed yet, this may change */
+ if (!conn->mux) {
+ smp->flags |= SMP_F_MAY_CHANGE;
+ return 0;
+ }
+
+ ret = conn->mux->ctl(conn, MUX_CTL_GET_GLITCHES, NULL);
+ if (ret < 0) {
+ /* not supported by the mux */
+ return 0;
+ }
+
+ smp->data.type = SMP_T_SINT;
+ smp->data.u.sint = ret;
+ return 1;
+}
+
/* return the major HTTP version as 1 or 2 depending on how the request arrived
* before being processed.
*
@@ -2488,9 +2522,11 @@ int smp_fetch_fc_err_str(const struct arg *args, struct sample *smp, const char
static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
{ "bc_err", smp_fetch_fc_err, 0, NULL, SMP_T_SINT, SMP_USE_L4SRV },
{ "bc_err_str", smp_fetch_fc_err_str, 0, NULL, SMP_T_STR, SMP_USE_L4SRV },
+ { "bc_glitches", smp_fetch_fc_glitches, 0, NULL, SMP_T_SINT, SMP_USE_L4SRV },
{ "bc_http_major", smp_fetch_fc_http_major, 0, NULL, SMP_T_SINT, SMP_USE_L4SRV },
{ "fc_err", smp_fetch_fc_err, 0, NULL, SMP_T_SINT, SMP_USE_L4CLI },
{ "fc_err_str", smp_fetch_fc_err_str, 0, NULL, SMP_T_STR, SMP_USE_L4CLI },
+ { "fc_glitches", smp_fetch_fc_glitches, 0, NULL, SMP_T_SINT, SMP_USE_L4CLI },
{ "fc_http_major", smp_fetch_fc_http_major, 0, NULL, SMP_T_SINT, SMP_USE_L4CLI },
{ "fc_rcvd_proxy", smp_fetch_fc_rcvd_proxy, 0, NULL, SMP_T_BOOL, SMP_USE_L4CLI },
{ "fc_pp_authority", smp_fetch_fc_pp_authority, 0, NULL, SMP_T_STR, SMP_USE_L4CLI },
diff --git a/src/debug.c b/src/debug.c
index fbaad80..756c194 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -300,9 +300,15 @@ void ha_thread_dump_one(int thr, int from_signal)
if (th_ctx->current &&
th_ctx->current->process == process_stream && th_ctx->current->context) {
const struct stream *s = (const struct stream *)th_ctx->current->context;
- struct hlua *hlua = s ? s->hlua : NULL;
+ struct hlua *hlua = NULL;
- if (hlua && hlua->T) {
+ if (s) {
+ if (s->hlua[0] && HLUA_IS_BUSY(s->hlua[0]))
+ hlua = s->hlua[0];
+ else if (s->hlua[1] && HLUA_IS_BUSY(s->hlua[1]))
+ hlua = s->hlua[1];
+ }
+ if (hlua) {
mark_tainted(TAINTED_LUA_STUCK);
if (hlua->state_id == 0)
mark_tainted(TAINTED_LUA_STUCK_SHARED);
@@ -417,7 +423,9 @@ void ha_task_dump(struct buffer *buf, const struct task *task, const char *pfx)
#ifdef USE_LUA
hlua = NULL;
- if (s && (hlua = s->hlua)) {
+ if (s && ((s->hlua[0] && HLUA_IS_BUSY(s->hlua[0])) ||
+ (s->hlua[1] && HLUA_IS_BUSY(s->hlua[1])))) {
+ hlua = (s->hlua[0] && HLUA_IS_BUSY(s->hlua[0])) ? s->hlua[0] : s->hlua[1];
chunk_appendf(buf, "%sCurrent executing Lua from a stream analyser -- ", pfx);
}
else if (task->process == hlua_process_task && (hlua = task->context)) {
diff --git a/src/event_hdl.c b/src/event_hdl.c
index aeb4d24..f5bb5b6 100644
--- a/src/event_hdl.c
+++ b/src/event_hdl.c
@@ -3,11 +3,19 @@
*
* Copyright 2022 HAProxy Technologies
*
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version
- * 2.1 of the License, or (at your option) any later version.
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
*
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <string.h>
diff --git a/src/flt_spoe.c b/src/flt_spoe.c
index 70ea2ba..43f6bd9 100644
--- a/src/flt_spoe.c
+++ b/src/flt_spoe.c
@@ -1165,6 +1165,10 @@ spoe_recv_frame(struct appctx *appctx, char *buf, size_t framesz)
ret = co_getblk(sc_oc(sc), (char *)&netint, 4, 0);
if (ret > 0) {
framesz = ntohl(netint);
+ if (framesz < 7) {
+ SPOE_APPCTX(appctx)->status_code = SPOE_FRM_ERR_INVALID;
+ return -1;
+ }
if (framesz > SPOE_APPCTX(appctx)->max_frame_size) {
SPOE_APPCTX(appctx)->status_code = SPOE_FRM_ERR_TOO_BIG;
return -1;
@@ -1998,10 +2002,13 @@ spoe_handle_appctx(struct appctx *appctx)
__fallthrough;
case SPOE_APPCTX_ST_END:
+ co_skip(sc_oc(sc), co_data(sc_oc(sc)));
return;
}
out:
- if (SPOE_APPCTX(appctx)->task->expire != TICK_ETERNITY)
+ if (stopping && appctx->st0 == SPOE_APPCTX_ST_IDLE)
+ task_wakeup(SPOE_APPCTX(appctx)->task, TASK_WOKEN_MSG);
+ else if (SPOE_APPCTX(appctx)->task->expire != TICK_ETERNITY)
task_queue(SPOE_APPCTX(appctx)->task);
}
@@ -2625,6 +2632,8 @@ spoe_stop_processing(struct spoe_agent *agent, struct spoe_context *ctx)
/* Reset processing timer */
ctx->process_exp = TICK_ETERNITY;
+ ctx->strm->req.analyse_exp = TICK_ETERNITY;
+ ctx->strm->res.analyse_exp = TICK_ETERNITY;
spoe_release_buffer(&ctx->buffer, &ctx->buffer_wait);
@@ -2683,8 +2692,10 @@ spoe_process_messages(struct stream *s, struct spoe_context *ctx,
if (!tick_isset(ctx->process_exp)) {
ctx->process_exp = tick_add_ifset(now_ms, agent->timeout.processing);
- s->task->expire = tick_first((tick_is_expired(s->task->expire, now_ms) ? 0 : s->task->expire),
- ctx->process_exp);
+ if (dir == SMP_OPT_DIR_REQ)
+ s->req.analyse_exp = ctx->process_exp;
+ else
+ s->res.analyse_exp = ctx->process_exp;
}
ret = spoe_start_processing(agent, ctx, dir);
if (!ret)
diff --git a/src/haproxy.c b/src/haproxy.c
index 4c739f4..1659d3d 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -3571,21 +3571,6 @@ int main(int argc, char **argv)
}
}
- if ((global.last_checks & LSTCHK_NETADM) && global.uid) {
- ha_alert("[%s.main()] Some configuration options require full privileges, so global.uid cannot be changed.\n"
- "", argv[0]);
- protocol_unbind_all();
- exit(1);
- }
-
- /* If the user is not root, we'll still let them try the configuration
- * but we inform them that unexpected behaviour may occur.
- */
- if ((global.last_checks & LSTCHK_NETADM) && getuid())
- ha_warning("[%s.main()] Some options which require full privileges"
- " might not work well.\n"
- "", argv[0]);
-
if ((global.mode & (MODE_MWORKER|MODE_DAEMON)) == 0) {
/* chroot if needed */
@@ -3614,6 +3599,34 @@ int main(int argc, char **argv)
if ((global.mode & (MODE_MWORKER | MODE_DAEMON)) == 0)
set_identity(argv[0]);
+ /* set_identity() above might have dropped LSTCHK_NETADM if
+ * it changed to a new UID while preserving enough permissions
+ * to honnor LSTCHK_NETADM.
+ */
+ if ((global.last_checks & LSTCHK_NETADM) && getuid()) {
+ /* If global.uid is present in config, it is already set as euid
+ * and ruid by set_identity() call just above, so it's better to
+ * remind the user to fix uncoherent settings.
+ */
+ if (global.uid) {
+ ha_alert("[%s.main()] Some configuration options require full "
+ "privileges, so global.uid cannot be changed.\n", argv[0]);
+#if defined(USE_LINUX_CAP)
+ ha_alert("[%s.main()] Alternately, if your system supports "
+ "Linux capabilities, you may also consider using "
+ "'setcap cap_net_raw' or 'setcap cap_net_admin' in the "
+ "'global' section.\n", argv[0]);
+#endif
+ protocol_unbind_all();
+ exit(1);
+ }
+ /* If the user is not root, we'll still let them try the configuration
+ * but we inform them that unexpected behaviour may occur.
+ */
+ ha_warning("[%s.main()] Some options which require full privileges"
+ " might not work well.\n", argv[0]);
+ }
+
/* check ulimits */
limit.rlim_cur = limit.rlim_max = 0;
getrlimit(RLIMIT_NOFILE, &limit);
diff --git a/src/hlua.c b/src/hlua.c
index d1f5323..0a81e7a 100644
--- a/src/hlua.c
+++ b/src/hlua.c
@@ -208,6 +208,58 @@ static inline void hlua_unlock(struct hlua *hlua)
lua_drop_global_lock();
}
+/* below is an helper function to retrieve string on on Lua stack at <index>
+ * in a safe way (function may not LJMP). It can be useful to retrieve errors
+ * at the top of the stack from an unprotected environment.
+ *
+ * The returned string will is only valid as long as the value at <index> is
+ * not removed from the stack.
+ *
+ * It is assumed that the calling function is allowed to manipulate <L>
+ */
+__LJMP static int _hlua_tostring_safe(lua_State *L)
+{
+ const char **str = lua_touserdata(L, 1);
+ const char *cur_str = MAY_LJMP(lua_tostring(L, 2));
+
+ if (cur_str)
+ *str = cur_str;
+ return 0;
+}
+static const char *hlua_tostring_safe(lua_State *L, int index)
+{
+ const char *str = NULL;
+
+ if (!lua_checkstack(L, 4))
+ return NULL;
+
+ /* before any stack modification, save the targeted value on the top of
+ * the stack: this will allow us to use relative index to target it.
+ */
+ lua_pushvalue(L, index);
+
+ /* push our custom _hlua_tostring_safe() function on the stack, then push
+ * our own string pointer and targeted value (at <index>) as argument
+ */
+ lua_pushcfunction(L, _hlua_tostring_safe);
+ lua_pushlightuserdata(L, &str); // 1st func argument = string pointer
+ lua_pushvalue(L, -3); // 2nd func argument = targeted value
+
+ lua_remove(L, -4); // remove <index> copy as we're done using it
+
+ /* call our custom function with proper arguments using pcall() to catch
+ * exceptions (if any)
+ */
+ switch (lua_pcall(L, 2, 0, 0)) {
+ case LUA_OK:
+ break;
+ default:
+ /* error was caught */
+ return NULL;
+ }
+ return str;
+}
+
#define SET_SAFE_LJMP_L(__L, __HLUA) \
({ \
int ret; \
@@ -278,7 +330,8 @@ struct hlua_flt_config {
};
struct hlua_flt_ctx {
- int ref; /* ref to the filter lua object */
+ struct hlua *_hlua; /* main hlua context */
+ int ref; /* ref to the filter lua object (in main hlua context) */
struct hlua *hlua[2]; /* lua runtime context (0: request, 1: response) */
unsigned int cur_off[2]; /* current offset (0: request, 1: response) */
unsigned int cur_len[2]; /* current forwardable length (0: request, 1: response) */
@@ -1626,6 +1679,45 @@ static int hlua_ctx_renew(struct hlua *lua, int keep_msg)
return 1;
}
+/* Helper function to get the lua ctx for a given stream and state_id */
+static inline struct hlua *hlua_stream_ctx_get(struct stream *s, int state_id)
+{
+ /* state_id == 0 -> global runtime ctx
+ * state_id != 0 -> per-thread runtime ctx
+ */
+ return s->hlua[!!state_id];
+}
+
+/* Helper function to prepare the lua ctx for a given stream and state id
+ *
+ * It uses the global or per-thread ctx depending on the expected
+ * <state_id>.
+ *
+ * Returns hlua ctx on success and NULL on failure
+ */
+static struct hlua *hlua_stream_ctx_prepare(struct stream *s, int state_id)
+{
+ /* In the execution wrappers linked with a stream, the
+ * Lua context can be not initialized. This behavior
+ * permits to save performances because a systematic
+ * Lua initialization cause 5% performances loss.
+ */
+ if (!s->hlua[!!state_id]) {
+ struct hlua *hlua;
+
+ hlua = pool_alloc(pool_head_hlua);
+ if (!hlua)
+ return NULL;
+ HLUA_INIT(hlua);
+ if (!hlua_ctx_init(hlua, state_id, s->task)) {
+ pool_free(pool_head_hlua, hlua);
+ return NULL;
+ }
+ s->hlua[!!state_id] = hlua;
+ }
+ return s->hlua[!!state_id];
+}
+
void hlua_hook(lua_State *L, lua_Debug *ar)
{
struct hlua *hlua;
@@ -1751,6 +1843,8 @@ resume_execution:
/* start the timer as we're about to start lua processing */
hlua_timer_start(&lua->timer);
+ HLUA_SET_BUSY(lua);
+
/* Call the function. */
#if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM >= 504
ret = lua_resume(lua->T, hlua_states[lua->state_id], lua->nargs, &nres);
@@ -1758,6 +1852,8 @@ resume_execution:
ret = lua_resume(lua->T, hlua_states[lua->state_id], lua->nargs);
#endif
+ HLUA_CLR_BUSY(lua);
+
/* out of lua processing, stop the timer */
hlua_timer_stop(&lua->timer);
@@ -1820,13 +1916,16 @@ resume_execution:
ret = HLUA_E_ERR;
break;
}
- msg = lua_tostring(lua->T, -1);
- lua_settop(lua->T, 0); /* Empty the stack. */
+ msg = hlua_tostring_safe(lua->T, -1);
trace = hlua_traceback(lua->T, ", ");
if (msg)
lua_pushfstring(lua->T, "[state-id %d] runtime error: %s from %s", lua->state_id, msg, trace);
else
lua_pushfstring(lua->T, "[state-id %d] unknown runtime error from %s", lua->state_id, trace);
+
+ /* Move the error msg at the top and then empty the stack except last msg */
+ lua_insert(lua->T, -lua_gettop(lua->T));
+ lua_settop(lua->T, 1);
ret = HLUA_E_ERRMSG;
break;
@@ -1842,12 +1941,15 @@ resume_execution:
ret = HLUA_E_ERR;
break;
}
- msg = lua_tostring(lua->T, -1);
- lua_settop(lua->T, 0); /* Empty the stack. */
+ msg = hlua_tostring_safe(lua->T, -1);
if (msg)
lua_pushfstring(lua->T, "[state-id %d] message handler error: %s", lua->state_id, msg);
else
lua_pushfstring(lua->T, "[state-id %d] message handler error", lua->state_id);
+
+ /* Move the error msg at the top and then empty the stack except last msg */
+ lua_insert(lua->T, -lua_gettop(lua->T));
+ lua_settop(lua->T, 1);
ret = HLUA_E_ERRMSG;
break;
@@ -2353,8 +2455,10 @@ static void hlua_socket_handler(struct appctx *appctx)
notification_wake(&ctx->wake_on_write);
/* Wake the tasks which wants to read if the buffer contains data. */
- if (co_data(sc_oc(sc)))
+ if (co_data(sc_oc(sc))) {
notification_wake(&ctx->wake_on_read);
+ applet_wont_consume(appctx);
+ }
/* If write notifications are registered, we considers we want
* to write, so we clear the blocking flag.
@@ -2435,12 +2539,19 @@ __LJMP static int hlua_socket_gc(lua_State *L)
ctx = container_of(peer, struct hlua_csk_ctx, xref);
- /* Set the flag which destroy the session. */
- ctx->die = 1;
- appctx_wakeup(ctx->appctx);
-
/* Remove all reference between the Lua stack and the coroutine stream. */
xref_disconnect(&socket->xref, peer);
+
+ if (se_fl_test(ctx->appctx->sedesc, SE_FL_ORPHAN)) {
+ /* The applet was never initialized, just release it */
+ appctx_free(ctx->appctx);
+ }
+ else {
+ /* Otherwise, notify it that is must die and wake it up */
+ ctx->die = 1;
+ appctx_wakeup(ctx->appctx);
+ }
+
return 0;
}
@@ -2616,6 +2727,7 @@ __LJMP static int hlua_socket_receive_yield(struct lua_State *L, int status, lua
co_skip(oc, len + skip_at_end);
/* Don't wait anything. */
+ applet_will_consume(appctx);
appctx_wakeup(appctx);
/* If the pattern reclaim to read all the data
@@ -3277,8 +3389,6 @@ __LJMP static int hlua_socket_connect(struct lua_State *L)
applet_have_more_data(appctx);
appctx_wakeup(appctx);
- hlua->gc_count++;
-
if (!notification_new(&hlua->com, &csk_ctx->wake_on_write, hlua->task)) {
xref_unlock(&socket->xref, peer);
WILL_LJMP(luaL_error(L, "out of memory"));
@@ -3389,6 +3499,12 @@ __LJMP static int hlua_socket_new(lua_State *L)
struct hlua_socket *socket;
struct hlua_csk_ctx *ctx;
struct appctx *appctx;
+ struct hlua *hlua;
+
+ /* Get hlua struct, or NULL if we execute from main lua state */
+ hlua = hlua_gethlua(L);
+ if (!hlua)
+ return 0;
/* Check stack size. */
if (!lua_checkstack(L, 3)) {
@@ -3428,6 +3544,8 @@ __LJMP static int hlua_socket_new(lua_State *L)
LIST_INIT(&ctx->wake_on_write);
LIST_INIT(&ctx->wake_on_read);
+ hlua->gc_count++;
+
/* Initialise cross reference between stream and Lua socket object. */
xref_create(&socket->xref, &ctx->xref);
return 1;
@@ -4868,13 +4986,13 @@ __LJMP static int hlua_applet_tcp_get_var(lua_State *L)
__LJMP static int hlua_applet_tcp_set_priv(lua_State *L)
{
struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1));
+ struct hlua_cli_ctx *cli_ctx = luactx->appctx->svcctx;
struct stream *s = luactx->htxn.s;
- struct hlua *hlua;
+ struct hlua *hlua = hlua_stream_ctx_get(s, cli_ctx->hlua->state_id);
/* Note that this hlua struct is from the session and not from the applet. */
- if (!s->hlua)
+ if (!hlua)
return 0;
- hlua = s->hlua;
MAY_LJMP(check_args(L, 2, "set_priv"));
@@ -4891,15 +5009,15 @@ __LJMP static int hlua_applet_tcp_set_priv(lua_State *L)
__LJMP static int hlua_applet_tcp_get_priv(lua_State *L)
{
struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1));
+ struct hlua_cli_ctx *cli_ctx = luactx->appctx->svcctx;
struct stream *s = luactx->htxn.s;
- struct hlua *hlua;
+ struct hlua *hlua = hlua_stream_ctx_get(s, cli_ctx->hlua->state_id);
/* Note that this hlua struct is from the session and not from the applet. */
- if (!s->hlua) {
+ if (!hlua) {
lua_pushnil(L);
return 1;
}
- hlua = s->hlua;
/* Push configuration index in the stack. */
lua_rawgeti(L, LUA_REGISTRYINDEX, hlua->Mref);
@@ -5359,13 +5477,13 @@ __LJMP static int hlua_applet_http_get_var(lua_State *L)
__LJMP static int hlua_applet_http_set_priv(lua_State *L)
{
struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_http(L, 1));
+ struct hlua_http_ctx *http_ctx = luactx->appctx->svcctx;
struct stream *s = luactx->htxn.s;
- struct hlua *hlua;
+ struct hlua *hlua = hlua_stream_ctx_get(s, http_ctx->hlua->state_id);
/* Note that this hlua struct is from the session and not from the applet. */
- if (!s->hlua)
+ if (!hlua)
return 0;
- hlua = s->hlua;
MAY_LJMP(check_args(L, 2, "set_priv"));
@@ -5382,15 +5500,15 @@ __LJMP static int hlua_applet_http_set_priv(lua_State *L)
__LJMP static int hlua_applet_http_get_priv(lua_State *L)
{
struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_http(L, 1));
+ struct hlua_http_ctx *http_ctx = luactx->appctx->svcctx;
struct stream *s = luactx->htxn.s;
- struct hlua *hlua;
+ struct hlua *hlua = hlua_stream_ctx_get(s, http_ctx->hlua->state_id);
/* Note that this hlua struct is from the session and not from the applet. */
- if (!s->hlua) {
+ if (!hlua) {
lua_pushnil(L);
return 1;
}
- hlua = s->hlua;
/* Push configuration index in the stack. */
lua_rawgeti(L, LUA_REGISTRYINDEX, hlua->Mref);
@@ -8202,10 +8320,12 @@ __LJMP static int hlua_txn_set_loglevel(lua_State *L)
htxn = MAY_LJMP(hlua_checktxn(L, 1));
ll = MAY_LJMP(luaL_checkinteger(L, 2));
- if (ll < 0 || ll > 7)
- WILL_LJMP(luaL_argerror(L, 2, "Bad log level. It must be between 0 and 7"));
+ if (ll < -1 || ll > NB_LOG_LEVELS)
+ WILL_LJMP(luaL_argerror(L, 2, "Bad log level. It must be one of the following value:"
+ " core.silent(-1), core.emerg(0), core.alert(1), core.crit(2), core.error(3),"
+ " core.warning(4), core.notice(5), core.info(6) or core.debug(7)"));
- htxn->s->logs.level = ll;
+ htxn->s->logs.level = (ll == -1) ? ll : ll + 1;
return 0;
}
@@ -8961,7 +9081,9 @@ struct task *hlua_process_task(struct task *task, void *context, unsigned int st
SEND_ERR(NULL, "Lua task: execution timeout.\n");
goto err_task_abort;
case HLUA_E_ERRMSG:
- SEND_ERR(NULL, "Lua task: %s.\n", lua_tostring(hlua->T, -1));
+ hlua_lock(hlua);
+ SEND_ERR(NULL, "Lua task: %s.\n", hlua_tostring_safe(hlua->T, -1));
+ hlua_unlock(hlua);
goto err_task_abort;
case HLUA_E_ERR:
default:
@@ -8975,51 +9097,6 @@ struct task *hlua_process_task(struct task *task, void *context, unsigned int st
return task;
}
-/* Helper function to prepare the lua ctx for a given stream
- *
- * ctx will be enforced in <state_id> parent stack on initial creation.
- * If s->hlua->state_id differs from <state_id>, which may happen at
- * runtime since existing stream hlua ctx will be reused for other
- * "independent" (but stream-related) lua executions, hlua will be
- * recreated with the expected state id.
- *
- * Returns 1 for success and 0 for failure
- */
-static int hlua_stream_ctx_prepare(struct stream *s, int state_id)
-{
- /* In the execution wrappers linked with a stream, the
- * Lua context can be not initialized. This behavior
- * permits to save performances because a systematic
- * Lua initialization cause 5% performances loss.
- */
- ctx_renew:
- if (!s->hlua) {
- struct hlua *hlua;
-
- hlua = pool_alloc(pool_head_hlua);
- if (!hlua)
- return 0;
- HLUA_INIT(hlua);
- if (!hlua_ctx_init(hlua, state_id, s->task)) {
- pool_free(pool_head_hlua, hlua);
- return 0;
- }
- s->hlua = hlua;
- }
- else if (s->hlua->state_id != state_id) {
- /* ctx already created, but not in proper state.
- * It should only happen after the previous execution is
- * finished, otherwise it's probably a bug since we don't
- * want to abort unfinished job..
- */
- BUG_ON(HLUA_IS_RUNNING(s->hlua));
- hlua_ctx_destroy(s->hlua);
- s->hlua = NULL;
- goto ctx_renew;
- }
- return 1;
-}
-
/* This function is an LUA binding that register LUA function to be
* executed after the HAProxy configuration parsing and before the
* HAProxy scheduler starts. This function expect only one LUA
@@ -9236,7 +9313,9 @@ static void hlua_event_handler(struct hlua *hlua)
break;
case HLUA_E_ERRMSG:
- SEND_ERR(NULL, "Lua event_hdl: %s.\n", lua_tostring(hlua->T, -1));
+ hlua_lock(hlua);
+ SEND_ERR(NULL, "Lua event_hdl: %s.\n", hlua_tostring_safe(hlua->T, -1));
+ hlua_unlock(hlua);
break;
case HLUA_E_ERR:
@@ -9574,11 +9653,13 @@ static struct task *hlua_event_runner(struct task *task, void *context, unsigned
/* The following Lua calls can fail. */
if (!SET_SAFE_LJMP(hlua_sub->hlua)) {
+ hlua_lock(hlua_sub->hlua);
if (lua_type(hlua_sub->hlua->T, -1) == LUA_TSTRING)
- error = lua_tostring(hlua_sub->hlua->T, -1);
+ error = hlua_tostring_safe(hlua_sub->hlua->T, -1);
else
error = "critical error";
ha_alert("Lua event_hdl: %s.\n", error);
+ hlua_unlock(hlua_sub->hlua);
goto skip_event;
}
@@ -9652,10 +9733,8 @@ static struct event_hdl_sub *hlua_event_subscribe(event_hdl_sub_list *list, stru
hlua_sub->task = NULL;
hlua_sub->hlua = NULL;
hlua_sub->paused = 0;
- if ((task = task_new_here()) == NULL) {
- ha_alert("out of memory while allocating hlua event task");
+ if ((task = task_new_here()) == NULL)
goto mem_error;
- }
task->process = hlua_event_runner;
task->context = hlua_sub;
event_hdl_async_equeue_init(&hlua_sub->equeue);
@@ -9854,83 +9933,90 @@ static int hlua_sample_conv_wrapper(const struct arg *arg_p, struct sample *smp,
{
struct hlua_function *fcn = private;
struct stream *stream = smp->strm;
+ struct hlua *hlua = NULL;
const char *error;
if (!stream)
return 0;
- if (!hlua_stream_ctx_prepare(stream, fcn_ref_to_stack_id(fcn))) {
+ if (!(hlua = hlua_stream_ctx_prepare(stream, fcn_ref_to_stack_id(fcn)))) {
SEND_ERR(stream->be, "Lua converter '%s': can't initialize Lua context.\n", fcn->name);
return 0;
}
/* If it is the first run, initialize the data for the call. */
- if (!HLUA_IS_RUNNING(stream->hlua)) {
+ if (!HLUA_IS_RUNNING(hlua)) {
/* The following Lua calls can fail. */
- if (!SET_SAFE_LJMP(stream->hlua)) {
- if (lua_type(stream->hlua->T, -1) == LUA_TSTRING)
- error = lua_tostring(stream->hlua->T, -1);
+ if (!SET_SAFE_LJMP(hlua)) {
+ hlua_lock(hlua);
+ if (lua_type(hlua->T, -1) == LUA_TSTRING)
+ error = hlua_tostring_safe(hlua->T, -1);
else
error = "critical error";
SEND_ERR(stream->be, "Lua converter '%s': %s.\n", fcn->name, error);
+ hlua_unlock(hlua);
return 0;
}
/* Check stack available size. */
- if (!lua_checkstack(stream->hlua->T, 1)) {
+ if (!lua_checkstack(hlua->T, 1)) {
SEND_ERR(stream->be, "Lua converter '%s': full stack.\n", fcn->name);
- RESET_SAFE_LJMP(stream->hlua);
+ RESET_SAFE_LJMP(hlua);
return 0;
}
/* Restore the function in the stack. */
- hlua_pushref(stream->hlua->T, fcn->function_ref[stream->hlua->state_id]);
+ hlua_pushref(hlua->T, fcn->function_ref[hlua->state_id]);
/* convert input sample and pust-it in the stack. */
- if (!lua_checkstack(stream->hlua->T, 1)) {
+ if (!lua_checkstack(hlua->T, 1)) {
SEND_ERR(stream->be, "Lua converter '%s': full stack.\n", fcn->name);
- RESET_SAFE_LJMP(stream->hlua);
+ RESET_SAFE_LJMP(hlua);
return 0;
}
- MAY_LJMP(hlua_smp2lua(stream->hlua->T, smp));
- stream->hlua->nargs = 1;
+ MAY_LJMP(hlua_smp2lua(hlua->T, smp));
+ hlua->nargs = 1;
/* push keywords in the stack. */
if (arg_p) {
for (; arg_p->type != ARGT_STOP; arg_p++) {
- if (!lua_checkstack(stream->hlua->T, 1)) {
+ if (!lua_checkstack(hlua->T, 1)) {
SEND_ERR(stream->be, "Lua converter '%s': full stack.\n", fcn->name);
- RESET_SAFE_LJMP(stream->hlua);
+ RESET_SAFE_LJMP(hlua);
return 0;
}
- MAY_LJMP(hlua_arg2lua(stream->hlua->T, arg_p));
- stream->hlua->nargs++;
+ MAY_LJMP(hlua_arg2lua(hlua->T, arg_p));
+ hlua->nargs++;
}
}
/* We must initialize the execution timeouts. */
- hlua_timer_init(&stream->hlua->timer, hlua_timeout_session);
+ hlua_timer_init(&hlua->timer, hlua_timeout_session);
/* At this point the execution is safe. */
- RESET_SAFE_LJMP(stream->hlua);
+ RESET_SAFE_LJMP(hlua);
}
/* Execute the function. */
- switch (hlua_ctx_resume(stream->hlua, 0)) {
+ switch (hlua_ctx_resume(hlua, 0)) {
/* finished. */
case HLUA_E_OK:
+ hlua_lock(hlua);
/* If the stack is empty, the function fails. */
- if (lua_gettop(stream->hlua->T) <= 0)
+ if (lua_gettop(hlua->T) <= 0) {
+ hlua_unlock(hlua);
return 0;
+ }
/* Convert the returned value in sample. */
- hlua_lua2smp(stream->hlua->T, -1, smp);
+ hlua_lua2smp(hlua->T, -1, smp);
/* dup the smp before popping the related lua value and
* returning it to haproxy
*/
smp_dup(smp);
- lua_pop(stream->hlua->T, 1);
+ lua_pop(hlua->T, 1);
+ hlua_unlock(hlua);
return 1;
/* yield. */
@@ -9941,9 +10027,11 @@ static int hlua_sample_conv_wrapper(const struct arg *arg_p, struct sample *smp,
/* finished with error. */
case HLUA_E_ERRMSG:
/* Display log. */
+ hlua_lock(hlua);
SEND_ERR(stream->be, "Lua converter '%s': %s.\n",
- fcn->name, lua_tostring(stream->hlua->T, -1));
- lua_pop(stream->hlua->T, 1);
+ fcn->name, hlua_tostring_safe(hlua->T, -1));
+ lua_pop(hlua->T, 1);
+ hlua_unlock(hlua);
return 0;
case HLUA_E_ETMOUT:
@@ -9978,82 +10066,89 @@ static int hlua_sample_fetch_wrapper(const struct arg *arg_p, struct sample *smp
{
struct hlua_function *fcn = private;
struct stream *stream = smp->strm;
+ struct hlua *hlua = NULL;
const char *error;
unsigned int hflags = HLUA_TXN_NOTERM | HLUA_TXN_SMP_CTX;
if (!stream)
return 0;
- if (!hlua_stream_ctx_prepare(stream, fcn_ref_to_stack_id(fcn))) {
+ if (!(hlua = hlua_stream_ctx_prepare(stream, fcn_ref_to_stack_id(fcn)))) {
SEND_ERR(stream->be, "Lua sample-fetch '%s': can't initialize Lua context.\n", fcn->name);
return 0;
}
/* If it is the first run, initialize the data for the call. */
- if (!HLUA_IS_RUNNING(stream->hlua)) {
+ if (!HLUA_IS_RUNNING(hlua)) {
/* The following Lua calls can fail. */
- if (!SET_SAFE_LJMP(stream->hlua)) {
- if (lua_type(stream->hlua->T, -1) == LUA_TSTRING)
- error = lua_tostring(stream->hlua->T, -1);
+ if (!SET_SAFE_LJMP(hlua)) {
+ hlua_lock(hlua);
+ if (lua_type(hlua->T, -1) == LUA_TSTRING)
+ error = hlua_tostring_safe(hlua->T, -1);
else
error = "critical error";
SEND_ERR(smp->px, "Lua sample-fetch '%s': %s.\n", fcn->name, error);
+ hlua_unlock(hlua);
return 0;
}
/* Check stack available size. */
- if (!lua_checkstack(stream->hlua->T, 2)) {
+ if (!lua_checkstack(hlua->T, 2)) {
SEND_ERR(smp->px, "Lua sample-fetch '%s': full stack.\n", fcn->name);
- RESET_SAFE_LJMP(stream->hlua);
+ RESET_SAFE_LJMP(hlua);
return 0;
}
/* Restore the function in the stack. */
- hlua_pushref(stream->hlua->T, fcn->function_ref[stream->hlua->state_id]);
+ hlua_pushref(hlua->T, fcn->function_ref[hlua->state_id]);
/* push arguments in the stack. */
- if (!hlua_txn_new(stream->hlua->T, stream, smp->px, smp->opt & SMP_OPT_DIR, hflags)) {
+ if (!hlua_txn_new(hlua->T, stream, smp->px, smp->opt & SMP_OPT_DIR, hflags)) {
SEND_ERR(smp->px, "Lua sample-fetch '%s': full stack.\n", fcn->name);
- RESET_SAFE_LJMP(stream->hlua);
+ RESET_SAFE_LJMP(hlua);
return 0;
}
- stream->hlua->nargs = 1;
+ hlua->nargs = 1;
/* push keywords in the stack. */
for (; arg_p && arg_p->type != ARGT_STOP; arg_p++) {
/* Check stack available size. */
- if (!lua_checkstack(stream->hlua->T, 1)) {
+ if (!lua_checkstack(hlua->T, 1)) {
SEND_ERR(smp->px, "Lua sample-fetch '%s': full stack.\n", fcn->name);
- RESET_SAFE_LJMP(stream->hlua);
+ RESET_SAFE_LJMP(hlua);
return 0;
}
- MAY_LJMP(hlua_arg2lua(stream->hlua->T, arg_p));
- stream->hlua->nargs++;
+ MAY_LJMP(hlua_arg2lua(hlua->T, arg_p));
+ hlua->nargs++;
}
/* We must initialize the execution timeouts. */
- hlua_timer_init(&stream->hlua->timer, hlua_timeout_session);
+ hlua_timer_init(&hlua->timer, hlua_timeout_session);
/* At this point the execution is safe. */
- RESET_SAFE_LJMP(stream->hlua);
+ RESET_SAFE_LJMP(hlua);
}
/* Execute the function. */
- switch (hlua_ctx_resume(stream->hlua, 0)) {
+ switch (hlua_ctx_resume(hlua, 0)) {
/* finished. */
case HLUA_E_OK:
+ hlua_lock(hlua);
/* If the stack is empty, the function fails. */
- if (lua_gettop(stream->hlua->T) <= 0)
+ if (lua_gettop(hlua->T) <= 0) {
+ hlua_unlock(hlua);
return 0;
+ }
/* Convert the returned value in sample. */
- hlua_lua2smp(stream->hlua->T, -1, smp);
+ hlua_lua2smp(hlua->T, -1, smp);
/* dup the smp before popping the related lua value and
* returning it to haproxy
*/
smp_dup(smp);
- lua_pop(stream->hlua->T, 1);
+ lua_pop(hlua->T, 1);
+ hlua_unlock(hlua);
/* Set the end of execution flag. */
smp->flags &= ~SMP_F_MAY_CHANGE;
@@ -10067,9 +10162,11 @@ static int hlua_sample_fetch_wrapper(const struct arg *arg_p, struct sample *smp
/* finished with error. */
case HLUA_E_ERRMSG:
/* Display log. */
+ hlua_lock(hlua);
SEND_ERR(smp->px, "Lua sample-fetch '%s': %s.\n",
- fcn->name, lua_tostring(stream->hlua->T, -1));
- lua_pop(stream->hlua->T, 1);
+ fcn->name, hlua_tostring_safe(hlua->T, -1));
+ lua_pop(hlua->T, 1);
+ hlua_unlock(hlua);
return 0;
case HLUA_E_ETMOUT:
@@ -10302,6 +10399,7 @@ static enum act_return hlua_action(struct act_rule *rule, struct proxy *px,
unsigned int hflags = HLUA_TXN_ACT_CTX;
int dir, act_ret = ACT_RET_CONT;
const char *error;
+ struct hlua *hlua = NULL;
switch (rule->from) {
case ACT_F_TCP_REQ_CNT: dir = SMP_OPT_DIR_REQ; break;
@@ -10313,72 +10411,76 @@ static enum act_return hlua_action(struct act_rule *rule, struct proxy *px,
goto end;
}
- if (!hlua_stream_ctx_prepare(s, fcn_ref_to_stack_id(rule->arg.hlua_rule->fcn))) {
+ if (!(hlua = hlua_stream_ctx_prepare(s, fcn_ref_to_stack_id(rule->arg.hlua_rule->fcn)))) {
SEND_ERR(px, "Lua action '%s': can't initialize Lua context.\n",
rule->arg.hlua_rule->fcn->name);
goto end;
}
/* If it is the first run, initialize the data for the call. */
- if (!HLUA_IS_RUNNING(s->hlua)) {
+ if (!HLUA_IS_RUNNING(hlua)) {
/* The following Lua calls can fail. */
- if (!SET_SAFE_LJMP(s->hlua)) {
- if (lua_type(s->hlua->T, -1) == LUA_TSTRING)
- error = lua_tostring(s->hlua->T, -1);
+ if (!SET_SAFE_LJMP(hlua)) {
+ hlua_lock(hlua);
+ if (lua_type(hlua->T, -1) == LUA_TSTRING)
+ error = hlua_tostring_safe(hlua->T, -1);
else
error = "critical error";
SEND_ERR(px, "Lua function '%s': %s.\n",
rule->arg.hlua_rule->fcn->name, error);
+ hlua_unlock(hlua);
goto end;
}
/* Check stack available size. */
- if (!lua_checkstack(s->hlua->T, 1)) {
+ if (!lua_checkstack(hlua->T, 1)) {
SEND_ERR(px, "Lua function '%s': full stack.\n",
rule->arg.hlua_rule->fcn->name);
- RESET_SAFE_LJMP(s->hlua);
+ RESET_SAFE_LJMP(hlua);
goto end;
}
/* Restore the function in the stack. */
- hlua_pushref(s->hlua->T, rule->arg.hlua_rule->fcn->function_ref[s->hlua->state_id]);
+ hlua_pushref(hlua->T, rule->arg.hlua_rule->fcn->function_ref[hlua->state_id]);
/* Create and and push object stream in the stack. */
- if (!hlua_txn_new(s->hlua->T, s, px, dir, hflags)) {
+ if (!hlua_txn_new(hlua->T, s, px, dir, hflags)) {
SEND_ERR(px, "Lua function '%s': full stack.\n",
rule->arg.hlua_rule->fcn->name);
- RESET_SAFE_LJMP(s->hlua);
+ RESET_SAFE_LJMP(hlua);
goto end;
}
- s->hlua->nargs = 1;
+ hlua->nargs = 1;
/* push keywords in the stack. */
for (arg = rule->arg.hlua_rule->args; arg && *arg; arg++) {
- if (!lua_checkstack(s->hlua->T, 1)) {
+ if (!lua_checkstack(hlua->T, 1)) {
SEND_ERR(px, "Lua function '%s': full stack.\n",
rule->arg.hlua_rule->fcn->name);
- RESET_SAFE_LJMP(s->hlua);
+ RESET_SAFE_LJMP(hlua);
goto end;
}
- lua_pushstring(s->hlua->T, *arg);
- s->hlua->nargs++;
+ lua_pushstring(hlua->T, *arg);
+ hlua->nargs++;
}
/* Now the execution is safe. */
- RESET_SAFE_LJMP(s->hlua);
+ RESET_SAFE_LJMP(hlua);
/* We must initialize the execution timeouts. */
- hlua_timer_init(&s->hlua->timer, hlua_timeout_session);
+ hlua_timer_init(&hlua->timer, hlua_timeout_session);
}
/* Execute the function. */
- switch (hlua_ctx_resume(s->hlua, !(flags & ACT_OPT_FINAL))) {
+ switch (hlua_ctx_resume(hlua, !(flags & ACT_OPT_FINAL))) {
/* finished. */
case HLUA_E_OK:
/* Catch the return value */
- if (lua_gettop(s->hlua->T) > 0)
- act_ret = lua_tointeger(s->hlua->T, -1);
+ hlua_lock(hlua);
+ if (lua_gettop(hlua->T) > 0)
+ act_ret = lua_tointeger(hlua->T, -1);
+ hlua_unlock(hlua);
/* Set timeout in the required channel. */
if (act_ret == ACT_RET_YIELD) {
@@ -10387,10 +10489,10 @@ static enum act_return hlua_action(struct act_rule *rule, struct proxy *px,
if (dir == SMP_OPT_DIR_REQ)
s->req.analyse_exp = tick_first((tick_is_expired(s->req.analyse_exp, now_ms) ? 0 : s->req.analyse_exp),
- s->hlua->wake_time);
+ hlua->wake_time);
else
s->res.analyse_exp = tick_first((tick_is_expired(s->res.analyse_exp, now_ms) ? 0 : s->res.analyse_exp),
- s->hlua->wake_time);
+ hlua->wake_time);
}
goto end;
@@ -10399,18 +10501,18 @@ static enum act_return hlua_action(struct act_rule *rule, struct proxy *px,
/* Set timeout in the required channel. */
if (dir == SMP_OPT_DIR_REQ)
s->req.analyse_exp = tick_first((tick_is_expired(s->req.analyse_exp, now_ms) ? 0 : s->req.analyse_exp),
- s->hlua->wake_time);
+ hlua->wake_time);
else
s->res.analyse_exp = tick_first((tick_is_expired(s->res.analyse_exp, now_ms) ? 0 : s->res.analyse_exp),
- s->hlua->wake_time);
+ hlua->wake_time);
/* Some actions can be wake up when a "write" event
* is detected on a response channel. This is useful
* only for actions targeted on the requests.
*/
- if (HLUA_IS_WAKERESWR(s->hlua))
+ if (HLUA_IS_WAKERESWR(hlua))
s->res.flags |= CF_WAKE_WRITE;
- if (HLUA_IS_WAKEREQWR(s->hlua))
+ if (HLUA_IS_WAKEREQWR(hlua))
s->req.flags |= CF_WAKE_WRITE;
act_ret = ACT_RET_YIELD;
goto end;
@@ -10418,9 +10520,11 @@ static enum act_return hlua_action(struct act_rule *rule, struct proxy *px,
/* finished with error. */
case HLUA_E_ERRMSG:
/* Display log. */
+ hlua_lock(hlua);
SEND_ERR(px, "Lua function '%s': %s.\n",
- rule->arg.hlua_rule->fcn->name, lua_tostring(s->hlua->T, -1));
- lua_pop(s->hlua->T, 1);
+ rule->arg.hlua_rule->fcn->name, hlua_tostring_safe(hlua->T, -1));
+ lua_pop(hlua->T, 1);
+ hlua_unlock(hlua);
goto end;
case HLUA_E_ETMOUT:
@@ -10448,8 +10552,8 @@ static enum act_return hlua_action(struct act_rule *rule, struct proxy *px,
}
end:
- if (act_ret != ACT_RET_YIELD && s->hlua)
- s->hlua->wake_time = TICK_ETERNITY;
+ if (act_ret != ACT_RET_YIELD && hlua)
+ hlua->wake_time = TICK_ETERNITY;
return act_ret;
}
@@ -10510,12 +10614,14 @@ static int hlua_applet_tcp_init(struct appctx *ctx)
/* The following Lua calls can fail. */
if (!SET_SAFE_LJMP(hlua)) {
+ hlua_lock(hlua);
if (lua_type(hlua->T, -1) == LUA_TSTRING)
- error = lua_tostring(hlua->T, -1);
+ error = hlua_tostring_safe(hlua->T, -1);
else
error = "critical error";
SEND_ERR(strm->be, "Lua applet tcp '%s': %s.\n",
ctx->rule->arg.hlua_rule->fcn->name, error);
+ hlua_unlock(hlua);
return -1;
}
@@ -10593,9 +10699,11 @@ void hlua_applet_tcp_fct(struct appctx *ctx)
/* finished with error. */
case HLUA_E_ERRMSG:
/* Display log. */
+ hlua_lock(hlua);
SEND_ERR(px, "Lua applet tcp '%s': %s.\n",
- rule->arg.hlua_rule->fcn->name, lua_tostring(hlua->T, -1));
+ rule->arg.hlua_rule->fcn->name, hlua_tostring_safe(hlua->T, -1));
lua_pop(hlua->T, 1);
+ hlua_unlock(hlua);
goto error;
case HLUA_E_ETMOUT:
@@ -10701,12 +10809,14 @@ static int hlua_applet_http_init(struct appctx *ctx)
/* The following Lua calls can fail. */
if (!SET_SAFE_LJMP(hlua)) {
+ hlua_lock(hlua);
if (lua_type(hlua->T, -1) == LUA_TSTRING)
- error = lua_tostring(hlua->T, -1);
+ error = hlua_tostring_safe(hlua->T, -1);
else
error = "critical error";
SEND_ERR(strm->be, "Lua applet http '%s': %s.\n",
ctx->rule->arg.hlua_rule->fcn->name, error);
+ hlua_unlock(hlua);
return -1;
}
@@ -10802,9 +10912,11 @@ void hlua_applet_http_fct(struct appctx *ctx)
/* finished with error. */
case HLUA_E_ERRMSG:
/* Display log. */
+ hlua_lock(hlua);
SEND_ERR(px, "Lua applet http '%s': %s.\n",
- rule->arg.hlua_rule->fcn->name, lua_tostring(hlua->T, -1));
+ rule->arg.hlua_rule->fcn->name, hlua_tostring_safe(hlua->T, -1));
lua_pop(hlua->T, 1);
+ hlua_unlock(hlua);
goto error;
case HLUA_E_ETMOUT:
@@ -11341,11 +11453,13 @@ static int hlua_cli_parse_fct(char **args, char *payload, struct appctx *appctx,
/* The following Lua calls can fail. */
if (!SET_SAFE_LJMP(hlua)) {
+ hlua_lock(hlua);
if (lua_type(hlua->T, -1) == LUA_TSTRING)
- error = lua_tostring(hlua->T, -1);
+ error = hlua_tostring_safe(hlua->T, -1);
else
error = "critical error";
SEND_ERR(NULL, "Lua cli '%s': %s.\n", fcn->name, error);
+ hlua_unlock(hlua);
goto error;
}
@@ -11426,9 +11540,11 @@ static int hlua_cli_io_handler_fct(struct appctx *appctx)
/* finished with error. */
case HLUA_E_ERRMSG:
/* Display log. */
+ hlua_lock(hlua);
SEND_ERR(NULL, "Lua cli '%s': %s.\n",
- fcn->name, lua_tostring(hlua->T, -1));
+ fcn->name, hlua_tostring_safe(hlua->T, -1));
lua_pop(hlua->T, 1);
+ hlua_unlock(hlua);
return 1;
case HLUA_E_ETMOUT:
@@ -11681,21 +11797,21 @@ static int hlua_filter_init_per_thread(struct proxy *px, struct flt_conf *fconf)
conf->ref[state_id] = flt_ref;
break;
case LUA_ERRRUN:
- ha_alert("Lua filter '%s' : runtime error : %s", conf->reg->name, lua_tostring(L, -1));
+ ha_alert("Lua filter '%s' : runtime error : %s", conf->reg->name, hlua_tostring_safe(L, -1));
goto error;
case LUA_ERRMEM:
ha_alert("Lua filter '%s' : out of memory error", conf->reg->name);
goto error;
case LUA_ERRERR:
- ha_alert("Lua filter '%s' : message handler error : %s", conf->reg->name, lua_tostring(L, -1));
+ ha_alert("Lua filter '%s' : message handler error : %s", conf->reg->name, hlua_tostring_safe(L, -1));
goto error;
#if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM <= 503
case LUA_ERRGCMM:
- ha_alert("Lua filter '%s' : garbage collector error : %s", conf->reg->name, lua_tostring(L, -1));
+ ha_alert("Lua filter '%s' : garbage collector error : %s", conf->reg->name, hlua_tostring_safe(L, -1));
goto error;
#endif
default:
- ha_alert("Lua filter '%s' : unknown error : %s", conf->reg->name, lua_tostring(L, -1));
+ ha_alert("Lua filter '%s' : unknown error : %s", conf->reg->name, hlua_tostring_safe(L, -1));
goto error;
}
@@ -11757,9 +11873,10 @@ static int hlua_filter_new(struct stream *s, struct filter *filter)
{
struct hlua_flt_config *conf = FLT_CONF(filter);
struct hlua_flt_ctx *flt_ctx = NULL;
+ struct hlua *hlua = NULL;
int ret = 1;
- if (!hlua_stream_ctx_prepare(s, reg_flt_to_stack_id(conf->reg))) {
+ if (!(hlua = hlua_stream_ctx_prepare(s, reg_flt_to_stack_id(conf->reg)))) {
SEND_ERR(s->be, "Lua filter '%s': can't initialize filter Lua context.\n",
conf->reg->name);
ret = 0;
@@ -11773,16 +11890,18 @@ static int hlua_filter_new(struct stream *s, struct filter *filter)
ret = 0;
goto end;
}
- flt_ctx->hlua[0] = pool_alloc(pool_head_hlua);
- flt_ctx->hlua[1] = pool_alloc(pool_head_hlua);
+
+ if ((flt_ctx->hlua[0] = pool_alloc(pool_head_hlua)))
+ HLUA_INIT(flt_ctx->hlua[0]);
+ if ((flt_ctx->hlua[1] = pool_alloc(pool_head_hlua)))
+ HLUA_INIT(flt_ctx->hlua[1]);
if (!flt_ctx->hlua[0] || !flt_ctx->hlua[1]) {
SEND_ERR(s->be, "Lua filter '%s': can't initialize filter Lua context.\n",
conf->reg->name);
ret = 0;
goto end;
}
- HLUA_INIT(flt_ctx->hlua[0]);
- HLUA_INIT(flt_ctx->hlua[1]);
+
if (!hlua_ctx_init(flt_ctx->hlua[0], reg_flt_to_stack_id(conf->reg), s->task) ||
!hlua_ctx_init(flt_ctx->hlua[1], reg_flt_to_stack_id(conf->reg), s->task)) {
SEND_ERR(s->be, "Lua filter '%s': can't initialize filter Lua context.\n",
@@ -11791,68 +11910,95 @@ static int hlua_filter_new(struct stream *s, struct filter *filter)
goto end;
}
- if (!HLUA_IS_RUNNING(s->hlua)) {
+ if (!HLUA_IS_RUNNING(hlua)) {
/* The following Lua calls can fail. */
- if (!SET_SAFE_LJMP(s->hlua)) {
+ if (!SET_SAFE_LJMP(hlua)) {
const char *error;
- if (lua_type(s->hlua->T, -1) == LUA_TSTRING)
- error = lua_tostring(s->hlua->T, -1);
+ hlua_lock(hlua);
+ if (lua_type(hlua->T, -1) == LUA_TSTRING)
+ error = hlua_tostring_safe(hlua->T, -1);
else
error = "critical error";
SEND_ERR(s->be, "Lua filter '%s': %s.\n", conf->reg->name, error);
+ hlua_unlock(hlua);
ret = 0;
goto end;
}
/* Check stack size. */
- if (!lua_checkstack(s->hlua->T, 1)) {
+ if (!lua_checkstack(hlua->T, 1)) {
SEND_ERR(s->be, "Lua filter '%s': full stack.\n", conf->reg->name);
- RESET_SAFE_LJMP(s->hlua);
+ RESET_SAFE_LJMP(hlua);
ret = 0;
goto end;
}
- hlua_pushref(s->hlua->T, conf->ref[s->hlua->state_id]);
- if (lua_getfield(s->hlua->T, -1, "new") != LUA_TFUNCTION) {
+ hlua_pushref(hlua->T, conf->ref[hlua->state_id]);
+ if (lua_getfield(hlua->T, -1, "new") != LUA_TFUNCTION) {
SEND_ERR(s->be, "Lua filter '%s': 'new' field is not a function.\n",
conf->reg->name);
- RESET_SAFE_LJMP(s->hlua);
+ RESET_SAFE_LJMP(hlua);
ret = 0;
goto end;
}
- lua_insert(s->hlua->T, -2);
+ lua_insert(hlua->T, -2);
/* Push the copy on the stack */
- s->hlua->nargs = 1;
+ hlua->nargs = 1;
/* We must initialize the execution timeouts. */
- hlua_timer_init(&s->hlua->timer, hlua_timeout_session);
+ hlua_timer_init(&hlua->timer, hlua_timeout_session);
/* At this point the execution is safe. */
- RESET_SAFE_LJMP(s->hlua);
+ RESET_SAFE_LJMP(hlua);
}
- switch (hlua_ctx_resume(s->hlua, 0)) {
+ switch (hlua_ctx_resume(hlua, 0)) {
case HLUA_E_OK:
+ /* The following Lua calls can fail. */
+ if (!SET_SAFE_LJMP(hlua)) {
+ const char *error;
+
+ hlua_lock(hlua);
+ if (lua_type(hlua->T, -1) == LUA_TSTRING)
+ error = hlua_tostring_safe(hlua->T, -1);
+ else
+ error = "critical error";
+ SEND_ERR(s->be, "Lua filter '%s': %s.\n", conf->reg->name, error);
+ hlua_unlock(hlua);
+ ret = 0;
+ goto end;
+ }
+
/* Nothing returned or not a table, ignore the filter for current stream */
- if (!lua_gettop(s->hlua->T) || !lua_istable(s->hlua->T, 1)) {
+ if (!lua_gettop(hlua->T) || !lua_istable(hlua->T, 1)) {
ret = 0;
+ RESET_SAFE_LJMP(hlua);
goto end;
}
/* Attached the filter pointer to the ctx */
- lua_pushstring(s->hlua->T, "__filter");
- lua_pushlightuserdata(s->hlua->T, filter);
- lua_settable(s->hlua->T, -3);
+ lua_pushstring(hlua->T, "__filter");
+ lua_pushlightuserdata(hlua->T, filter);
+ lua_settable(hlua->T, -3);
/* Save a ref on the filter ctx */
- lua_pushvalue(s->hlua->T, 1);
- flt_ctx->ref = hlua_ref(s->hlua->T);
+ lua_pushvalue(hlua->T, 1);
+ flt_ctx->ref = hlua_ref(hlua->T);
+
+ /* At this point the execution is safe. */
+ RESET_SAFE_LJMP(hlua);
+
+ /* save main hlua ctx (from the stream) */
+ flt_ctx->_hlua = hlua;
+
filter->ctx = flt_ctx;
break;
case HLUA_E_ERRMSG:
- SEND_ERR(s->be, "Lua filter '%s' : %s.\n", conf->reg->name, lua_tostring(s->hlua->T, -1));
+ hlua_lock(hlua);
+ SEND_ERR(s->be, "Lua filter '%s' : %s.\n", conf->reg->name, hlua_tostring_safe(hlua->T, -1));
+ hlua_unlock(hlua);
ret = -1;
goto end;
case HLUA_E_ETMOUT:
@@ -11879,8 +12025,11 @@ static int hlua_filter_new(struct stream *s, struct filter *filter)
}
end:
- if (s->hlua)
- lua_settop(s->hlua->T, 0);
+ if (hlua) {
+ hlua_lock(hlua);
+ lua_settop(hlua->T, 0);
+ hlua_unlock(hlua);
+ }
if (ret <= 0) {
if (flt_ctx) {
hlua_ctx_destroy(flt_ctx->hlua[0]);
@@ -11894,8 +12043,11 @@ static int hlua_filter_new(struct stream *s, struct filter *filter)
static void hlua_filter_delete(struct stream *s, struct filter *filter)
{
struct hlua_flt_ctx *flt_ctx = filter->ctx;
+ struct hlua *hlua = hlua_stream_ctx_get(s, flt_ctx->_hlua->state_id);
- hlua_unref(s->hlua->T, flt_ctx->ref);
+ hlua_lock(hlua);
+ hlua_unref(hlua->T, flt_ctx->ref);
+ hlua_unlock(hlua);
hlua_ctx_destroy(flt_ctx->hlua[0]);
hlua_ctx_destroy(flt_ctx->hlua[1]);
pool_free(pool_head_hlua_flt_ctx, flt_ctx);
@@ -11923,20 +12075,24 @@ static int hlua_filter_callback(struct stream *s, struct filter *filter, const c
goto end;
if (!HLUA_IS_RUNNING(flt_hlua)) {
- int extra_idx = lua_gettop(flt_hlua->T);
+ int extra_idx;
/* The following Lua calls can fail. */
if (!SET_SAFE_LJMP(flt_hlua)) {
const char *error;
+ hlua_lock(flt_hlua);
if (lua_type(flt_hlua->T, -1) == LUA_TSTRING)
- error = lua_tostring(flt_hlua->T, -1);
+ error = hlua_tostring_safe(flt_hlua->T, -1);
else
error = "critical error";
SEND_ERR(s->be, "Lua filter '%s': %s.\n", conf->reg->name, error);
+ hlua_unlock(flt_hlua);
goto end;
}
+ extra_idx = lua_gettop(flt_hlua->T);
+
/* Check stack size. */
if (!lua_checkstack(flt_hlua->T, 3)) {
SEND_ERR(s->be, "Lua filter '%s': full stack.\n", conf->reg->name);
@@ -12006,10 +12162,12 @@ static int hlua_filter_callback(struct stream *s, struct filter *filter, const c
switch (hlua_ctx_resume(flt_hlua, !(flags & HLUA_FLT_CB_FINAL))) {
case HLUA_E_OK:
/* Catch the return value if it required */
+ hlua_lock(flt_hlua);
if ((flags & HLUA_FLT_CB_RETVAL) && lua_gettop(flt_hlua->T) > 0) {
ret = lua_tointeger(flt_hlua->T, -1);
lua_settop(flt_hlua->T, 0); /* Empty the stack. */
}
+ hlua_unlock(flt_hlua);
/* Set timeout in the required channel. */
if (flt_hlua->wake_time != TICK_ETERNITY) {
@@ -12038,7 +12196,9 @@ static int hlua_filter_callback(struct stream *s, struct filter *filter, const c
ret = 0;
goto end;
case HLUA_E_ERRMSG:
- SEND_ERR(s->be, "Lua filter '%s' : %s.\n", conf->reg->name, lua_tostring(flt_hlua->T, -1));
+ hlua_lock(flt_hlua);
+ SEND_ERR(s->be, "Lua filter '%s' : %s.\n", conf->reg->name, hlua_tostring_safe(flt_hlua->T, -1));
+ hlua_unlock(flt_hlua);
ret = -1;
goto end;
case HLUA_E_ETMOUT:
@@ -12527,7 +12687,7 @@ static int hlua_load_state(char **args, lua_State *L, char **err)
/* Just load and compile the file. */
error = luaL_loadfile(L, args[0]);
if (error) {
- memprintf(err, "error in Lua file '%s': %s", args[0], lua_tostring(L, -1));
+ memprintf(err, "error in Lua file '%s': %s", args[0], hlua_tostring_safe(L, -1));
lua_pop(L, 1);
return -1;
}
@@ -12549,24 +12709,24 @@ static int hlua_load_state(char **args, lua_State *L, char **err)
case LUA_OK:
break;
case LUA_ERRRUN:
- memprintf(err, "Lua runtime error: %s", lua_tostring(L, -1));
+ memprintf(err, "Lua runtime error: %s", hlua_tostring_safe(L, -1));
lua_pop(L, 1);
return -1;
case LUA_ERRMEM:
memprintf(err, "Lua out of memory error");
return -1;
case LUA_ERRERR:
- memprintf(err, "Lua message handler error: %s", lua_tostring(L, -1));
+ memprintf(err, "Lua message handler error: %s", hlua_tostring_safe(L, -1));
lua_pop(L, 1);
return -1;
#if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM <= 503
case LUA_ERRGCMM:
- memprintf(err, "Lua garbage collector error: %s", lua_tostring(L, -1));
+ memprintf(err, "Lua garbage collector error: %s", hlua_tostring_safe(L, -1));
lua_pop(L, 1);
return -1;
#endif
default:
- memprintf(err, "Lua unknown error: %s", lua_tostring(L, -1));
+ memprintf(err, "Lua unknown error: %s", hlua_tostring_safe(L, -1));
lua_pop(L, 1);
return -1;
}
@@ -12718,7 +12878,7 @@ static int hlua_config_prepend_path(char **args, int section_type, struct proxy
if (setjmp(safe_ljmp_env) != 0) {
lua_atpanic(L, hlua_panic_safe);
if (lua_type(L, -1) == LUA_TSTRING)
- error = lua_tostring(L, -1);
+ error = hlua_tostring_safe(L, -1);
else
error = "critical error";
fprintf(stderr, "lua-prepend-path: %s.\n", error);
@@ -12987,7 +13147,7 @@ int hlua_post_init_state(lua_State *L)
if (setjmp(safe_ljmp_env) != 0) {
lua_atpanic(L, hlua_panic_safe);
if (lua_type(L, -1) == LUA_TSTRING)
- error = lua_tostring(L, -1);
+ error = hlua_tostring_safe(L, -1);
else
error = "critical error";
fprintf(stderr, "Lua post-init: %s.\n", error);
@@ -13021,13 +13181,14 @@ int hlua_post_init_state(lua_State *L)
case LUA_ERRRUN:
if (!kind)
kind = "runtime error";
- msg = lua_tostring(L, -1);
- lua_settop(L, 0); /* Empty the stack. */
+ msg = hlua_tostring_safe(L, -1);
trace = hlua_traceback(L, ", ");
if (msg)
ha_alert("Lua init: %s: '%s' from %s\n", kind, msg, trace);
else
ha_alert("Lua init: unknown %s from %s\n", kind, trace);
+
+ lua_settop(L, 0); /* Empty the stack. */
return_status = 0;
break;
@@ -13171,7 +13332,7 @@ int hlua_post_init()
if ((reg_flt->flt_ref[0] == -1) == (ret < 0)) {
ha_alert("Lua filter '%s' is referenced both ins shared Lua context (through lua-load) "
"and per-thread Lua context (through lua-load-per-thread). these two context "
- "exclusive.\n", fcn->name);
+ "exclusive.\n", reg_flt->name);
errors++;
}
}
@@ -13281,7 +13442,7 @@ lua_State *hlua_init_state(int thread_num)
if (setjmp(safe_ljmp_env) != 0) {
lua_atpanic(L, hlua_panic_safe);
if (lua_type(L, -1) == LUA_TSTRING)
- error_msg = lua_tostring(L, -1);
+ error_msg = hlua_tostring_safe(L, -1);
else
error_msg = "critical error";
fprintf(stderr, "Lua init: %s.\n", error_msg);
@@ -13330,6 +13491,7 @@ lua_State *hlua_init_state(int thread_num)
hlua_class_const_int(L, "thread", thread_num);
/* Push the loglevel constants. */
+ hlua_class_const_int(L, "silent", -1);
for (i = 0; i < NB_LOG_LEVELS; i++)
hlua_class_const_int(L, log_levels[i], i);
diff --git a/src/http_ext.c b/src/http_ext.c
index a367519..3367e38 100644
--- a/src/http_ext.c
+++ b/src/http_ext.c
@@ -3,11 +3,19 @@
*
* Copyright 2022 HAProxy Technologies
*
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version
- * 2.1 of the License, or (at your option) any later version.
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
*
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <haproxy/sample.h>
@@ -106,8 +114,12 @@ static inline int http_7239_extract_ipv4(struct ist *input, struct in_addr *ip)
{
char ip4[INET_ADDRSTRLEN];
unsigned char buf[sizeof(struct in_addr)];
+ void *dst = buf;
int it = 0;
+ if (ip)
+ dst = ip;
+
/* extract ipv4 addr */
while (it < istlen(*input) && it < (sizeof(ip4) - 1)) {
if (!isdigit((unsigned char)istptr(*input)[it]) &&
@@ -117,11 +129,9 @@ static inline int http_7239_extract_ipv4(struct ist *input, struct in_addr *ip)
it += 1;
}
ip4[it] = 0;
- if (inet_pton(AF_INET, ip4, buf) != 1)
+ if (inet_pton(AF_INET, ip4, dst) != 1)
return 0; /* invalid ip4 addr */
/* ok */
- if (ip)
- memcpy(ip, buf, sizeof(buf));
*input = istadv(*input, it);
return 1;
}
@@ -138,8 +148,12 @@ static inline int http_7239_extract_ipv6(struct ist *input, struct in6_addr *ip)
{
char ip6[INET6_ADDRSTRLEN];
unsigned char buf[sizeof(struct in6_addr)];
+ void *dst = buf;
int it = 0;
+ if (ip)
+ dst = ip;
+
*input = istnext(*input); /* skip '[' leading char */
/* extract ipv6 addr */
while (it < istlen(*input) &&
@@ -154,11 +168,9 @@ static inline int http_7239_extract_ipv6(struct ist *input, struct in6_addr *ip)
if ((istlen(*input)-it) < 1 || istptr(*input)[it] != ']')
return 0; /* missing ending "]" char */
it += 1;
- if (inet_pton(AF_INET6, ip6, buf) != 1)
+ if (inet_pton(AF_INET6, ip6, dst) != 1)
return 0; /* invalid ip6 addr */
/* ok */
- if (ip)
- memcpy(ip, buf, sizeof(buf));
*input = istadv(*input, it);
return 1;
}
diff --git a/src/linuxcap.c b/src/linuxcap.c
index 919086c..4a2a3ab 100644
--- a/src/linuxcap.c
+++ b/src/linuxcap.c
@@ -23,6 +23,7 @@
#include <haproxy/api.h>
#include <haproxy/cfgparse.h>
#include <haproxy/errors.h>
+#include <haproxy/global.h>
#include <haproxy/tools.h>
/* supported names, zero-terminated */
@@ -59,9 +60,10 @@ static uint32_t caplist;
* - switch euid to non-zero
* - set the effective and permitted caps again
* - then the caller can safely call setuid()
+ * On success LSTCHK_NETADM is unset from global.last_checks, if CAP_NET_ADMIN
+ * or CAP_NET_RAW was found in the caplist from config.
* We don't do this if the current euid is not zero or if the target uid
- * is zero. Returns >=0 on success, negative on failure. Alerts or warnings
- * may be emitted.
+ * is zero. Returns 0 on success, negative on failure. Alerts may be emitted.
*/
int prepare_caps_for_setuid(int from_uid, int to_uid)
{
@@ -101,6 +103,10 @@ int prepare_caps_for_setuid(int from_uid, int to_uid)
ha_alert("Failed to set the final capabilities using capset(): %s\n", strerror(errno));
return -1;
}
+
+ if (caplist & ((1 << CAP_NET_ADMIN)|(1 << CAP_NET_RAW)))
+ global.last_checks &= ~LSTCHK_NETADM;
+
/* all's good */
return 0;
}
diff --git a/src/listener.c b/src/listener.c
index 86d0945..75e164a 100644
--- a/src/listener.c
+++ b/src/listener.c
@@ -1602,6 +1602,22 @@ void listener_release(struct listener *l)
if (fe && !MT_LIST_ISEMPTY(&fe->listener_queue) &&
(!fe->fe_sps_lim || freq_ctr_remain(&fe->fe_sess_per_sec, fe->fe_sps_lim, 0) > 0))
dequeue_proxy_listeners(fe);
+ else {
+ unsigned int wait;
+ int expire = TICK_ETERNITY;
+
+ if (fe->task && fe->fe_sps_lim &&
+ (wait = next_event_delay(&fe->fe_sess_per_sec,fe->fe_sps_lim, 0))) {
+ /* we're blocking because a limit was reached on the number of
+ * requests/s on the frontend. We want to re-check ASAP, which
+ * means in 1 ms before estimated expiration date, because the
+ * timer will have settled down.
+ */
+ expire = tick_first(fe->task->expire, tick_add(now_ms, wait));
+ if (tick_isset(expire))
+ task_schedule(fe->task, expire);
+ }
+ }
}
/* Initializes the listener queues. Returns 0 on success, otherwise ERR_* flags */
@@ -2195,6 +2211,9 @@ int bind_parse_args_list(struct bind_conf *bind_conf, char **args, int cur_arg,
*/
if ((bind_conf->options & (BC_O_USE_SOCK_DGRAM|BC_O_USE_XPRT_STREAM)) == (BC_O_USE_SOCK_DGRAM|BC_O_USE_XPRT_STREAM)) {
#ifdef USE_QUIC
+ struct listener *l __maybe_unused;
+ int listener_count __maybe_unused = 0;
+
bind_conf->xprt = xprt_get(XPRT_QUIC);
if (!(bind_conf->options & BC_O_USE_SSL)) {
bind_conf->options |= BC_O_USE_SSL;
@@ -2202,6 +2221,17 @@ int bind_parse_args_list(struct bind_conf *bind_conf, char **args, int cur_arg,
file, linenum, args[0], args[1], section);
}
quic_transport_params_init(&bind_conf->quic_params, 1);
+
+#if (!defined(IP_PKTINFO) && !defined(IP_RECVDSTADDR)) || !defined(IPV6_RECVPKTINFO)
+ list_for_each_entry(l, &bind_conf->listeners, by_bind) {
+ if (++listener_count > 1 || !is_inet_addr(&l->rx.addr)) {
+ ha_diag_warning("parsing [%s:%d] : '%s %s' in section '%s' : UDP binding on multiple addresses without IP_PKTINFO or equivalent support may be unreliable.\n",
+ file, linenum, args[0], args[1], section);
+ break;
+ }
+ }
+#endif /* (!IP_PKTINFO && !IP_RECVDSTADDR) || !IPV6_RECVPKTINFO */
+
#else
ha_alert("parsing [%s:%d] : '%s %s' in section '%s' : QUIC protocol selected but support not compiled in (check build options).\n",
file, linenum, args[0], args[1], section);
diff --git a/src/log.c b/src/log.c
index 010ace9..42b905c 100644
--- a/src/log.c
+++ b/src/log.c
@@ -883,6 +883,7 @@ static void log_backend_srv_down(struct server *srv)
static int _postcheck_log_backend_compat(struct proxy *be)
{
int err_code = ERR_NONE;
+ int balance_algo = (be->lbprm.algo & BE_LB_ALGO);
if (!LIST_ISEMPTY(&be->tcp_req.inspect_rules) ||
!LIST_ISEMPTY(&be->tcp_req.l4_rules) ||
@@ -940,6 +941,13 @@ static int _postcheck_log_backend_compat(struct proxy *be)
err_code |= ERR_WARN;
free_server_rules(&be->server_rules);
}
+ if (balance_algo != BE_LB_ALGO_RR &&
+ balance_algo != BE_LB_ALGO_RND &&
+ balance_algo != BE_LB_ALGO_LS &&
+ balance_algo != BE_LB_ALGO_LH) {
+ ha_alert("in %s '%s': \"balance\" only supports 'roundrobin', 'random', 'sticky' and 'log-hash'.\n", proxy_type_str(be), be->id);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ }
return err_code;
}
diff --git a/src/mux_fcgi.c b/src/mux_fcgi.c
index 0230e6b..448d8bb 100644
--- a/src/mux_fcgi.c
+++ b/src/mux_fcgi.c
@@ -4039,6 +4039,15 @@ static size_t fcgi_snd_buf(struct stconn *sc, struct buffer *buf, size_t count,
}
break;
+ case HTX_BLK_EOT:
+ if (htx_is_unique_blk(htx, blk) && (htx->flags & HTX_FL_EOM)) {
+ TRACE_PROTO("sending FCGI STDIN record", FCGI_EV_TX_RECORD|FCGI_EV_TX_STDIN, fconn->conn, fstrm, htx);
+ ret = fcgi_strm_send_empty_stdin(fconn, fstrm);
+ if (!ret)
+ goto done;
+ }
+ __fallthrough;
+
default:
remove_blk:
htx_remove_blk(htx, blk);
diff --git a/src/mux_h1.c b/src/mux_h1.c
index 455ebeb..6593661 100644
--- a/src/mux_h1.c
+++ b/src/mux_h1.c
@@ -4430,8 +4430,10 @@ static size_t h1_nego_ff(struct stconn *sc, struct buffer *input, size_t count,
}
else {
BUG_ON(h1m->state != H1_MSG_CHUNK_CRLF && h1m->state != H1_MSG_CHUNK_SIZE);
- if (!h1_make_chunk(h1s, h1m, count))
+ if (!h1_make_chunk(h1s, h1m, count)) {
+ h1s->sd->iobuf.flags |= IOBUF_FL_FF_BLOCKED;
goto out;
+ }
h1m->curr_len = count;
}
}
@@ -4458,6 +4460,7 @@ static size_t h1_nego_ff(struct stconn *sc, struct buffer *input, size_t count,
if (!h1_get_buf(h1c, &h1c->obuf)) {
h1c->flags |= H1C_F_OUT_ALLOC;
+ h1s->sd->iobuf.flags |= IOBUF_FL_FF_BLOCKED;
TRACE_STATE("waiting for opposite h1c obuf allocation", H1_EV_STRM_SEND|H1_EV_H1S_BLK, h1c->conn, h1s);
goto out;
}
diff --git a/src/mux_h2.c b/src/mux_h2.c
index 273e1f5..7ce0e6e 100644
--- a/src/mux_h2.c
+++ b/src/mux_h2.c
@@ -78,11 +78,13 @@ struct h2c {
int timeout; /* idle timeout duration in ticks */
int shut_timeout; /* idle timeout duration in ticks after GOAWAY was sent */
int idle_start; /* date of the last time the connection went idle (no stream + empty mbuf), or the start of current http req */
- /* 32-bit hole here */
+
unsigned int nb_streams; /* number of streams in the tree */
unsigned int nb_sc; /* number of attached stream connectors */
unsigned int nb_reserved; /* number of reserved streams */
unsigned int stream_cnt; /* total number of streams seen */
+ int glitches; /* total number of glitches on this connection */
+
struct proxy *proxy; /* the proxy this connection was created for */
struct task *task; /* timeout management task */
struct h2_counters *px_counters; /* h2 counters attached to proxy */
@@ -408,6 +410,8 @@ static int h2_settings_header_table_size = 4096; /* initial value */
static int h2_settings_initial_window_size = 65536; /* default initial value */
static int h2_be_settings_initial_window_size = 0; /* backend's default initial value */
static int h2_fe_settings_initial_window_size = 0; /* frontend's default initial value */
+static int h2_be_glitches_threshold = 0; /* backend's max glitches: unlimited */
+static int h2_fe_glitches_threshold = 0; /* frontend's max glitches: unlimited */
static unsigned int h2_settings_max_concurrent_streams = 100; /* default value */
static unsigned int h2_be_settings_max_concurrent_streams = 0; /* backend value */
static unsigned int h2_fe_settings_max_concurrent_streams = 0; /* frontend value */
@@ -510,6 +514,9 @@ static void h2_trace(enum trace_level level, uint64_t mask, const struct trace_s
if (h2c->errcode)
chunk_appendf(&trace_buf, " err=%s/%02x", h2_err_str(h2c->errcode), h2c->errcode);
+ if (h2c->glitches)
+ chunk_appendf(&trace_buf, " glitches=%d", h2c->glitches);
+
if (h2c->flags & H2_CF_DEM_IN_PROGRESS && // frame processing has started, type and length are valid
(mask & (H2_EV_RX_FRAME|H2_EV_RX_FHDR)) == (H2_EV_RX_FRAME|H2_EV_RX_FHDR)) {
chunk_appendf(&trace_buf, " dft=%s/%02x dfl=%d", h2_ft_str(h2c->dft), h2c->dff, h2c->dfl);
@@ -599,7 +606,6 @@ static inline int h2c_max_concurrent_streams(const struct h2c *h2c)
return ret;
}
-
/* update h2c timeout if needed */
static void h2c_update_timeout(struct h2c *h2c)
{
@@ -1046,6 +1052,7 @@ static int h2_init(struct connection *conn, struct proxy *prx, struct session *s
h2c->nb_sc = 0;
h2c->nb_reserved = 0;
h2c->stream_cnt = 0;
+ h2c->glitches = 0;
h2c->dbuf = *input;
h2c->dsi = -1;
@@ -1297,6 +1304,25 @@ static void __maybe_unused h2s_alert(struct h2s *h2s)
TRACE_LEAVE(H2_EV_H2S_WAKE, h2s->h2c->conn, h2s);
}
+/* report one or more glitches on the connection. That is any unexpected event
+ * that may occasionally happen but if repeated a bit too much, might indicate
+ * a misbehaving or completely bogus peer. It normally returns zero, unless the
+ * glitch limit was reached, in which case an error is also reported on the
+ * connection.
+ */
+static inline int h2c_report_glitch(struct h2c *h2c, int increment)
+{
+ int thres = (h2c->flags & H2_CF_IS_BACK) ?
+ h2_be_glitches_threshold : h2_fe_glitches_threshold;
+
+ h2c->glitches += increment;
+ if (thres && h2c->glitches >= thres) {
+ h2c_error(h2c, H2_ERR_ENHANCE_YOUR_CALM);
+ return 1;
+ }
+ return 0;
+}
+
/* writes the 24-bit frame size <len> at address <frame> */
static inline __maybe_unused void h2_set_frame_size(void *frame, uint32_t len)
{
@@ -1586,6 +1612,7 @@ static struct h2s *h2c_frt_stream_new(struct h2c *h2c, int id, struct buffer *in
BUG_ON(conn_reverse_in_preconnect(h2c->conn));
if (h2c->nb_streams >= h2c_max_concurrent_streams(h2c)) {
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("HEADERS frame causing MAX_CONCURRENT_STREAMS to be exceeded", H2_EV_H2S_NEW|H2_EV_RX_FRAME|H2_EV_RX_HDR, h2c->conn);
session_inc_http_req_ctr(sess);
session_inc_http_err_ctr(sess);
@@ -1835,6 +1862,7 @@ static int h2c_frt_recv_preface(struct h2c *h2c)
if (!ret1)
h2c->flags |= H2_CF_DEM_SHORT_READ;
if (ret1 < 0 || (h2c->flags & H2_CF_RCVD_SHUT)) {
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("I/O error or short read", H2_EV_RX_FRAME|H2_EV_RX_PREFACE, h2c->conn);
h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
if (b_data(&h2c->dbuf) ||
@@ -2298,12 +2326,26 @@ static int h2c_handle_settings(struct h2c *h2c)
*/
if (arg < 0) { // RFC7540#6.5.2
error = H2_ERR_FLOW_CONTROL_ERROR;
+ h2c_report_glitch(h2c, 1);
goto fail;
}
+ /* Let's count a glitch here in case of a reduction
+ * after H2_CS_SETTINGS1 because while it's not
+ * fundamentally invalid from a protocol's perspective,
+ * it's often suspicious.
+ */
+ if (h2c->st0 != H2_CS_SETTINGS1 && arg < h2c->miw)
+ if (h2c_report_glitch(h2c, 1)) {
+ error = H2_ERR_ENHANCE_YOUR_CALM;
+ TRACE_STATE("glitch limit reached on SETTINGS frame", H2_EV_RX_FRAME|H2_EV_RX_SETTINGS|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
+ goto fail;
+ }
+
h2c->miw = arg;
break;
case H2_SETTINGS_MAX_FRAME_SIZE:
if (arg < 16384 || arg > 16777215) { // RFC7540#6.5.2
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("MAX_FRAME_SIZE out of range", H2_EV_RX_FRAME|H2_EV_RX_SETTINGS, h2c->conn);
error = H2_ERR_PROTOCOL_ERROR;
HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err);
@@ -2316,6 +2358,7 @@ static int h2c_handle_settings(struct h2c *h2c)
break;
case H2_SETTINGS_ENABLE_PUSH:
if (arg < 0 || arg > 1) { // RFC7540#6.5.2
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("ENABLE_PUSH out of range", H2_EV_RX_FRAME|H2_EV_RX_SETTINGS, h2c->conn);
error = H2_ERR_PROTOCOL_ERROR;
HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err);
@@ -2578,13 +2621,22 @@ static int h2c_handle_window_update(struct h2c *h2c, struct h2s *h2s)
goto done;
if (!inc) {
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("stream WINDOW_UPDATE inc=0", H2_EV_RX_FRAME|H2_EV_RX_WU, h2c->conn, h2s);
error = H2_ERR_PROTOCOL_ERROR;
HA_ATOMIC_INC(&h2c->px_counters->strm_proto_err);
goto strm_err;
}
+ /* WT: it would be tempting to count a glitch here for very small
+ * increments (less than a few tens of bytes), but that might be
+ * perfectly valid for many short streams, so better instead
+ * count the number of WU per frame maybe. That would be better
+ * dealt with using scores per frame.
+ */
+
if (h2s_mws(h2s) >= 0 && h2s_mws(h2s) + inc < 0) {
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("stream WINDOW_UPDATE inc<0", H2_EV_RX_FRAME|H2_EV_RX_WU, h2c->conn, h2s);
error = H2_ERR_FLOW_CONTROL_ERROR;
HA_ATOMIC_INC(&h2c->px_counters->strm_proto_err);
@@ -2603,6 +2655,7 @@ static int h2c_handle_window_update(struct h2c *h2c, struct h2s *h2s)
else {
/* connection window update */
if (!inc) {
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("conn WINDOW_UPDATE inc=0", H2_EV_RX_FRAME|H2_EV_RX_WU, h2c->conn);
error = H2_ERR_PROTOCOL_ERROR;
HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err);
@@ -2610,6 +2663,7 @@ static int h2c_handle_window_update(struct h2c *h2c, struct h2s *h2s)
}
if (h2c->mws >= 0 && h2c->mws + inc < 0) {
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("conn WINDOW_UPDATE inc<0", H2_EV_RX_FRAME|H2_EV_RX_WU, h2c->conn);
error = H2_ERR_FLOW_CONTROL_ERROR;
goto conn_err;
@@ -2679,6 +2733,7 @@ static int h2c_handle_priority(struct h2c *h2c)
if (h2_get_n32(&h2c->dbuf, 0) == h2c->dsi) {
/* 7540#5.3 : can't depend on itself */
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("PRIORITY depends on itself", H2_EV_RX_FRAME|H2_EV_RX_WU, h2c->conn);
h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err);
@@ -2792,6 +2847,7 @@ static struct h2s *h2c_frt_handle_headers(struct h2c *h2c, struct h2s *h2s)
else if (h2c->dsi <= h2c->max_id || !(h2c->dsi & 1)) {
/* RFC7540#5.1.1 stream id > prev ones, and must be odd here */
error = H2_ERR_PROTOCOL_ERROR;
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("HEADERS on invalid stream ID", H2_EV_RX_FRAME|H2_EV_RX_HDR, h2c->conn);
HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err);
sess_log(h2c->conn->owner);
@@ -2809,6 +2865,7 @@ static struct h2s *h2c_frt_handle_headers(struct h2c *h2c, struct h2s *h2s)
* stop processing its requests for real.
*/
error = H2_ERR_ENHANCE_YOUR_CALM;
+ h2c_report_glitch(h2c, 1);
TRACE_STATE("Stream limit violated", H2_EV_STRM_SHUT, h2c->conn);
HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err);
sess_log(h2c->conn->owner);
@@ -2977,6 +3034,7 @@ static struct h2s *h2c_bck_handle_headers(struct h2c *h2c, struct h2s *h2s)
if (h2s->st != H2_SS_OPEN && h2s->st != H2_SS_HLOC) {
/* RFC7540#5.1 */
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("response HEADERS in invalid state", H2_EV_RX_FRAME|H2_EV_RX_HDR, h2c->conn, h2s);
h2s_error(h2s, H2_ERR_STREAM_CLOSED);
h2c->st0 = H2_CS_FRAME_E;
@@ -3067,22 +3125,24 @@ static int h2c_handle_data(struct h2c *h2c, struct h2s *h2s)
if (h2s->st != H2_SS_OPEN && h2s->st != H2_SS_HLOC) {
/* RFC7540#6.1 */
error = H2_ERR_STREAM_CLOSED;
- goto strm_err;
+ goto strm_err_wu;
}
if (!(h2s->flags & H2_SF_HEADERS_RCVD)) {
/* RFC9113#8.1: The header section must be received before the message content */
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("Unexpected DATA frame before the message headers", H2_EV_RX_FRAME|H2_EV_RX_DATA, h2c->conn, h2s);
error = H2_ERR_PROTOCOL_ERROR;
HA_ATOMIC_INC(&h2c->px_counters->strm_proto_err);
- goto strm_err;
+ goto strm_err_wu;
}
if ((h2s->flags & H2_SF_DATA_CLEN) && (h2c->dfl - h2c->dpl) > h2s->body_len) {
/* RFC7540#8.1.2 */
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("DATA frame larger than content-length", H2_EV_RX_FRAME|H2_EV_RX_DATA, h2c->conn, h2s);
error = H2_ERR_PROTOCOL_ERROR;
HA_ATOMIC_INC(&h2c->px_counters->strm_proto_err);
- goto strm_err;
+ goto strm_err_wu;
}
if (!(h2c->flags & H2_CF_IS_BACK) &&
(h2s->flags & (H2_SF_TUNNEL_ABRT|H2_SF_ES_SENT)) == (H2_SF_TUNNEL_ABRT|H2_SF_ES_SENT) &&
@@ -3095,7 +3155,7 @@ static int h2c_handle_data(struct h2c *h2c, struct h2s *h2s)
*/
TRACE_ERROR("Request DATA frame for aborted tunnel", H2_EV_RX_FRAME|H2_EV_RX_DATA, h2c->conn, h2s);
error = H2_ERR_CANCEL;
- goto strm_err;
+ goto strm_err_wu;
}
if (!h2_frt_transfer_data(h2s))
@@ -3137,6 +3197,7 @@ static int h2c_handle_data(struct h2c *h2c, struct h2s *h2s)
if (h2s->flags & H2_SF_DATA_CLEN && h2s->body_len) {
/* RFC7540#8.1.2 */
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("ES on DATA frame before content-length", H2_EV_RX_FRAME|H2_EV_RX_DATA, h2c->conn, h2s);
error = H2_ERR_PROTOCOL_ERROR;
HA_ATOMIC_INC(&h2c->px_counters->strm_proto_err);
@@ -3156,6 +3217,12 @@ static int h2c_handle_data(struct h2c *h2c, struct h2s *h2s)
TRACE_LEAVE(H2_EV_RX_FRAME|H2_EV_RX_DATA, h2c->conn, h2s);
return 1;
+ strm_err_wu:
+ /* stream error before the frame was taken into account, we're
+ * going to kill the stream but must still update the connection's
+ * window.
+ */
+ h2c->rcvd_c += h2c->dfl - h2c->dpl;
strm_err:
h2s_error(h2s, error);
h2c->st0 = H2_CS_FRAME_E;
@@ -3179,6 +3246,7 @@ static int h2_frame_check_vs_state(struct h2c *h2c, struct h2s *h2s)
/* RFC7540#5.1: any frame other than HEADERS or PRIORITY in
* this state MUST be treated as a connection error
*/
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("invalid frame type for IDLE state", H2_EV_RX_FRAME|H2_EV_RX_FHDR, h2c->conn, h2s);
h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
if (!h2c->nb_streams && !(h2c->flags & H2_CF_IS_BACK)) {
@@ -3192,6 +3260,7 @@ static int h2_frame_check_vs_state(struct h2c *h2c, struct h2s *h2s)
if (h2s->st == H2_SS_IDLE && (h2c->flags & H2_CF_IS_BACK)) {
/* only PUSH_PROMISE would be permitted here */
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("invalid frame type for IDLE state (back)", H2_EV_RX_FRAME|H2_EV_RX_FHDR, h2c->conn, h2s);
h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err);
@@ -3207,11 +3276,13 @@ static int h2_frame_check_vs_state(struct h2c *h2c, struct h2s *h2s)
* PUSH_PROMISE/CONTINUATION cause connection errors.
*/
if (h2_ft_bit(h2c->dft) & H2_FT_HDR_MASK) {
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("invalid frame type for HREM state", H2_EV_RX_FRAME|H2_EV_RX_FHDR, h2c->conn, h2s);
h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err);
}
else {
+ h2c_report_glitch(h2c, 1);
h2s_error(h2s, H2_ERR_STREAM_CLOSED);
}
TRACE_DEVEL("leaving in error (hrem&!wu&!rst&!prio)", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn, h2s);
@@ -3241,6 +3312,7 @@ static int h2_frame_check_vs_state(struct h2c *h2c, struct h2s *h2s)
* receives an unexpected stream identifier
* MUST respond with a connection error.
*/
+ h2c_report_glitch(h2c, 1);
h2c_error(h2c, H2_ERR_STREAM_CLOSED);
TRACE_DEVEL("leaving in error (closed&hdrmask)", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn, h2s);
return 0;
@@ -3267,6 +3339,14 @@ static int h2_frame_check_vs_state(struct h2c *h2c, struct h2s *h2s)
* RST_RCVD bit, we don't want to accidentally catch valid
* frames for a closed stream, i.e. RST/PRIO/WU.
*/
+ if (h2c->dft == H2_FT_DATA) {
+ /* even if we reject out-of-stream DATA, it must
+ * still count against the connection's flow control.
+ */
+ h2c->rcvd_c += h2c->dfl - h2c->dpl;
+ }
+
+ h2c_report_glitch(h2c, 1);
h2s_error(h2s, H2_ERR_STREAM_CLOSED);
h2c->st0 = H2_CS_FRAME_E;
TRACE_DEVEL("leaving in error (rst_rcvd&!hdrmask)", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn, h2s);
@@ -3291,6 +3371,7 @@ static int h2_frame_check_vs_state(struct h2c *h2c, struct h2s *h2s)
if (h2c->dft != H2_FT_RST_STREAM &&
h2c->dft != H2_FT_PRIORITY &&
h2c->dft != H2_FT_WINDOW_UPDATE) {
+ h2c_report_glitch(h2c, 1);
h2c_error(h2c, H2_ERR_STREAM_CLOSED);
TRACE_DEVEL("leaving in error (rst_sent&!rst&!prio&!wu)", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn, h2s);
return 0;
@@ -3426,6 +3507,10 @@ static void h2_process_demux(struct h2c *h2c)
if (unlikely(h2c_frt_recv_preface(h2c) <= 0)) {
/* RFC7540#3.5: a GOAWAY frame MAY be omitted */
if (h2c->st0 == H2_CS_ERROR) {
+ if (b_data(&h2c->dbuf) ||
+ !(((const struct session *)h2c->conn->owner)->fe->options & (PR_O_NULLNOLOG|PR_O_IGNORE_PRB)))
+ h2c_report_glitch(h2c, 1);
+
TRACE_PROTO("failed to receive preface", H2_EV_RX_PREFACE|H2_EV_PROTO_ERR, h2c->conn);
h2c->st0 = H2_CS_ERROR2;
if (b_data(&h2c->dbuf) ||
@@ -3450,6 +3535,7 @@ static void h2_process_demux(struct h2c *h2c)
/* RFC7540#3.5: a GOAWAY frame MAY be omitted */
h2c->flags |= H2_CF_DEM_SHORT_READ;
if (h2c->st0 == H2_CS_ERROR) {
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("failed to receive settings", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_RX_SETTINGS|H2_EV_PROTO_ERR, h2c->conn);
h2c->st0 = H2_CS_ERROR2;
if (!(h2c->flags & H2_CF_IS_BACK))
@@ -3460,6 +3546,7 @@ static void h2_process_demux(struct h2c *h2c)
if (hdr.sid || hdr.ft != H2_FT_SETTINGS || hdr.ff & H2_F_SETTINGS_ACK) {
/* RFC7540#3.5: a GOAWAY frame MAY be omitted */
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("unexpected frame type or flags", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_RX_SETTINGS|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
h2c->st0 = H2_CS_ERROR2;
@@ -3471,6 +3558,7 @@ static void h2_process_demux(struct h2c *h2c)
if ((int)hdr.len < 0 || (int)hdr.len > global.tune.bufsize) {
/* RFC7540#3.5: a GOAWAY frame MAY be omitted */
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("invalid settings frame length", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_RX_SETTINGS|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR);
h2c->st0 = H2_CS_ERROR2;
@@ -3512,6 +3600,7 @@ static void h2_process_demux(struct h2c *h2c)
}
if ((int)hdr.len < 0 || (int)hdr.len > global.tune.bufsize) {
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("invalid H2 frame length", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR);
if (!h2c->nb_streams && !(h2c->flags & H2_CF_IS_BACK)) {
@@ -3542,6 +3631,7 @@ static void h2_process_demux(struct h2c *h2c)
* padlen in the flow control, so it must be adjusted.
*/
if (hdr.len < 1) {
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("invalid H2 padded frame length", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR);
if (!(h2c->flags & H2_CF_IS_BACK))
@@ -3559,6 +3649,7 @@ static void h2_process_demux(struct h2c *h2c)
padlen = *(uint8_t *)b_peek(&h2c->dbuf, 9);
if (padlen > hdr.len) {
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("invalid H2 padding length", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn);
/* RFC7540#6.1 : pad length = length of
* frame payload or greater => error.
@@ -3591,6 +3682,7 @@ static void h2_process_demux(struct h2c *h2c)
/* check for minimum basic frame format validity */
ret = h2_frame_check(h2c->dft, 1, h2c->dsi, h2c->dfl, global.tune.bufsize);
if (ret != H2_ERR_NO_ERROR) {
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("received invalid H2 frame header", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, ret);
if (!(h2c->flags & H2_CF_IS_BACK))
@@ -3679,6 +3771,7 @@ static void h2_process_demux(struct h2c *h2c)
* frames' parsers consume all following CONTINUATION
* frames so this one is out of sequence.
*/
+ h2c_report_glitch(h2c, 1);
TRACE_ERROR("received unexpected H2 CONTINUATION frame", H2_EV_RX_FRAME|H2_EV_RX_CONT|H2_EV_H2C_ERR, h2c->conn, h2s);
h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
if (!(h2c->flags & H2_CF_IS_BACK))
@@ -4568,6 +4661,9 @@ static int h2_ctl(struct connection *conn, enum mux_ctl_type mux_ctl, void *outp
tasklet_wakeup(h2c->wait_event.tasklet);
return 0;
+ case MUX_CTL_GET_GLITCHES:
+ return h2c->glitches;
+
default:
return -1;
}
@@ -5004,7 +5100,8 @@ static void h2_shutw(struct stconn *sc, enum co_shw_mode mode)
*
* The H2_SF_HEADERS_RCVD flag is also looked at in the <flags> field prior to
* decoding, in order to detect if we're dealing with a headers or a trailers
- * block (the trailers block appears after H2_SF_HEADERS_RCVD was seen).
+ * block (the trailers block appears after H2_SF_HEADERS_RCVD was seen). The
+ * function takes care of counting glitches.
*/
static int h2c_dec_hdrs(struct h2c *h2c, struct buffer *rxbuf, uint32_t *flags, unsigned long long *body_len, char *upgrade_protocol)
{
@@ -5014,7 +5111,8 @@ static int h2c_dec_hdrs(struct h2c *h2c, struct buffer *rxbuf, uint32_t *flags,
struct buffer *copy = NULL;
unsigned int msgf;
struct htx *htx = NULL;
- int flen; // header frame len
+ int flen = 0; // header frame len
+ int fragments = 0;
int hole = 0;
int ret = 0;
int outlen;
@@ -5048,6 +5146,7 @@ next_frame:
if (hdr.ft != H2_FT_CONTINUATION) {
/* RFC7540#6.10: frame of unexpected type */
+ h2c_report_glitch(h2c, 1);
TRACE_STATE("not continuation!", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_RX_HDR|H2_EV_RX_CONT|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err);
@@ -5056,6 +5155,7 @@ next_frame:
if (hdr.sid != h2c->dsi) {
/* RFC7540#6.10: frame of different stream */
+ h2c_report_glitch(h2c, 1);
TRACE_STATE("different stream ID!", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_RX_HDR|H2_EV_RX_CONT|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err);
@@ -5064,6 +5164,7 @@ next_frame:
if ((unsigned)hdr.len > (unsigned)global.tune.bufsize) {
/* RFC7540#4.2: invalid frame length */
+ h2c_report_glitch(h2c, 1);
TRACE_STATE("too large frame!", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_RX_HDR|H2_EV_RX_CONT|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR);
goto fail;
@@ -5086,6 +5187,7 @@ next_frame:
hole += h2c->dpl + 9;
h2c->dpl = 0;
TRACE_STATE("waiting for next continuation frame", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_RX_CONT|H2_EV_RX_HDR, h2c->conn);
+ fragments++;
goto next_frame;
}
@@ -5109,6 +5211,7 @@ next_frame:
if (h2c->dff & H2_F_HEADERS_PRIORITY) {
if (read_n32(hdrs) == h2c->dsi) {
/* RFC7540#5.3.1 : stream dep may not depend on itself */
+ h2c_report_glitch(h2c, 1);
TRACE_STATE("invalid stream dependency!", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err);
@@ -5116,6 +5219,7 @@ next_frame:
}
if (flen < 5) {
+ h2c_report_glitch(h2c, 1);
TRACE_STATE("frame too short for priority!", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR);
goto fail;
@@ -5167,6 +5271,7 @@ next_frame:
}
if (outlen < 0) {
+ h2c_report_glitch(h2c, 1);
TRACE_STATE("failed to decompress HPACK", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_COMPRESSION_ERROR);
goto fail;
@@ -5199,6 +5304,7 @@ next_frame:
if (outlen < 0 || htx_free_space(htx) < global.tune.maxrewrite) {
/* too large headers? this is a stream error only */
+ h2c_report_glitch(h2c, 1);
TRACE_STATE("message headers too large or invalid", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2S_ERR|H2_EV_PROTO_ERR, h2c->conn);
htx->flags |= HTX_FL_PARSING_ERROR;
goto fail;
@@ -5234,6 +5340,7 @@ next_frame:
if (h2c->dff & H2_F_HEADERS_END_STREAM) {
if (msgf & H2_MSGF_RSP_1XX) {
/* RFC9113#8.1 : HEADERS frame with the ES flag set that carries an informational status code is malformed */
+ h2c_report_glitch(h2c, 1);
TRACE_STATE("invalid interim response with ES flag!", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
goto fail;
}
@@ -5269,6 +5376,20 @@ next_frame:
htx_to_buf(htx, rxbuf);
free_trash_chunk(copy);
TRACE_LEAVE(H2_EV_RX_FRAME|H2_EV_RX_HDR, h2c->conn);
+
+ /* Check for abuse of CONTINUATION: more than 4 fragments and less than
+ * 1kB per fragment is clearly unusual and suspicious enough to count
+ * one glitch per 1kB fragment in a 16kB buffer, which means that an
+ * abuser sending 1600 1-byte frames in a 16kB buffer would increment
+ * its counter by 100.
+ */
+ if (unlikely(fragments > 4) && fragments > flen / 1024 && ret != 0) {
+ if (h2c_report_glitch(h2c, (fragments + 15) / 16)) {
+ TRACE_STATE("glitch limit reached on CONTINUATION frame", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
+ ret = -1;
+ }
+ }
+
return ret;
fail:
@@ -5279,6 +5400,7 @@ next_frame:
/* This is the last HEADERS frame hence a trailer */
if (!(h2c->dff & H2_F_HEADERS_END_STREAM)) {
/* It's a trailer but it's missing ES flag */
+ h2c_report_glitch(h2c, 1);
TRACE_STATE("missing EH on trailers frame", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err);
@@ -7249,9 +7371,9 @@ static int h2_dump_h2c_info(struct buffer *msg, struct h2c *h2c, const char *pfx
hmbuf = br_head(h2c->mbuf);
tmbuf = br_tail(h2c->mbuf);
chunk_appendf(msg, " h2c.st0=%s .err=%d .maxid=%d .lastid=%d .flg=0x%04x"
- " .nbst=%u .nbsc=%u",
+ " .nbst=%u .nbsc=%u, .glitches=%d",
h2c_st_to_str(h2c->st0), h2c->errcode, h2c->max_id, h2c->last_sid, h2c->flags,
- h2c->nb_streams, h2c->nb_sc);
+ h2c->nb_streams, h2c->nb_sc, h2c->glitches);
if (pfx)
chunk_appendf(msg, "\n%s", pfx);
@@ -7407,6 +7529,27 @@ static int h2_takeover(struct connection *conn, int orig_tid)
/* functions below are dedicated to the config parsers */
/*******************************************************/
+/* config parser for global "tune.h2.{fe,be}.glitches-threshold" */
+static int h2_parse_glitches_threshold(char **args, int section_type, struct proxy *curpx,
+ const struct proxy *defpx, const char *file, int line,
+ char **err)
+{
+ int *vptr;
+
+ if (too_many_args(1, args, err, NULL))
+ return -1;
+
+ /* backend/frontend */
+ vptr = (args[0][8] == 'b') ? &h2_be_glitches_threshold : &h2_fe_glitches_threshold;
+
+ *vptr = atoi(args[1]);
+ if (*vptr < 0) {
+ memprintf(err, "'%s' expects a positive numeric value.", args[0]);
+ return -1;
+ }
+ return 0;
+}
+
/* config parser for global "tune.h2.header-table-size" */
static int h2_parse_header_table_size(char **args, int section_type, struct proxy *curpx,
const struct proxy *defpx, const char *file, int line,
@@ -7565,8 +7708,10 @@ INITCALL1(STG_REGISTER, register_mux_proto, &mux_proto_h2);
/* config keyword parsers */
static struct cfg_kw_list cfg_kws = {ILH, {
+ { CFG_GLOBAL, "tune.h2.be.glitches-threshold", h2_parse_glitches_threshold },
{ CFG_GLOBAL, "tune.h2.be.initial-window-size", h2_parse_initial_window_size },
{ CFG_GLOBAL, "tune.h2.be.max-concurrent-streams", h2_parse_max_concurrent_streams },
+ { CFG_GLOBAL, "tune.h2.fe.glitches-threshold", h2_parse_glitches_threshold },
{ CFG_GLOBAL, "tune.h2.fe.initial-window-size", h2_parse_initial_window_size },
{ CFG_GLOBAL, "tune.h2.fe.max-concurrent-streams", h2_parse_max_concurrent_streams },
{ CFG_GLOBAL, "tune.h2.fe.max-total-streams", h2_parse_max_total_streams },
diff --git a/src/mux_quic.c b/src/mux_quic.c
index de87368..05c92fa 100644
--- a/src/mux_quic.c
+++ b/src/mux_quic.c
@@ -2398,13 +2398,6 @@ static void qcc_release(struct qcc *qcc)
qcc->task = NULL;
}
- tasklet_free(qcc->wait_event.tasklet);
- if (conn && qcc->wait_event.events) {
- conn->xprt->unsubscribe(conn, conn->xprt_ctx,
- qcc->wait_event.events,
- &qcc->wait_event);
- }
-
/* liberate remaining qcs instances */
node = eb64_first(&qcc->streams_by_id);
while (node) {
@@ -2413,6 +2406,13 @@ static void qcc_release(struct qcc *qcc)
qcs_free(qcs);
}
+ tasklet_free(qcc->wait_event.tasklet);
+ if (conn && qcc->wait_event.events) {
+ conn->xprt->unsubscribe(conn, conn->xprt_ctx,
+ qcc->wait_event.events,
+ &qcc->wait_event);
+ }
+
while (!LIST_ISEMPTY(&qcc->lfctl.frms)) {
struct quic_frame *frm = LIST_ELEM(qcc->lfctl.frms.n, struct quic_frame *, list);
qc_frm_free(qcc->conn->handle.qc, &frm);
diff --git a/src/peers.c b/src/peers.c
index 5eefd18..9ba3d9b 100644
--- a/src/peers.c
+++ b/src/peers.c
@@ -1652,9 +1652,9 @@ static inline int peer_send_teach_process_msgs(struct appctx *appctx, struct pee
{
int ret;
- HA_RWLOCK_WRUNLOCK(STK_TABLE_LOCK, &st->table->lock);
+ HA_RWLOCK_WRUNLOCK(STK_TABLE_LOCK, &st->table->updt_lock);
ret = peer_send_teachmsgs(appctx, p, peer_teach_process_stksess_lookup, st);
- HA_RWLOCK_WRLOCK(STK_TABLE_LOCK, &st->table->lock);
+ HA_RWLOCK_WRLOCK(STK_TABLE_LOCK, &st->table->updt_lock);
return ret;
}
@@ -2688,18 +2688,18 @@ static inline int peer_send_msgs(struct appctx *appctx,
}
if (!(peer->flags & PEER_F_TEACH_PROCESS)) {
- HA_RWLOCK_WRLOCK(STK_TABLE_LOCK, &st->table->lock);
+ HA_RWLOCK_WRLOCK(STK_TABLE_LOCK, &st->table->updt_lock);
if (!(peer->flags & PEER_F_LEARN_ASSIGN) &&
(st->last_pushed != st->table->localupdate)) {
repl = peer_send_teach_process_msgs(appctx, peer, st);
if (repl <= 0) {
- HA_RWLOCK_WRUNLOCK(STK_TABLE_LOCK, &st->table->lock);
+ HA_RWLOCK_WRUNLOCK(STK_TABLE_LOCK, &st->table->updt_lock);
peer->stop_local_table = peer->last_local_table;
return repl;
}
}
- HA_RWLOCK_WRUNLOCK(STK_TABLE_LOCK, &st->table->lock);
+ HA_RWLOCK_WRUNLOCK(STK_TABLE_LOCK, &st->table->updt_lock);
}
else if (!(peer->flags & PEER_F_TEACH_FINISHED)) {
if (!(st->flags & SHTABLE_F_TEACH_STAGE1)) {
@@ -2894,7 +2894,7 @@ static inline void init_accepted_peer(struct peer *peer, struct peers *peers)
uint commitid, updateid;
st->last_get = st->last_acked = 0;
- HA_RWLOCK_WRLOCK(STK_TABLE_LOCK, &st->table->lock);
+ HA_RWLOCK_WRLOCK(STK_TABLE_LOCK, &st->table->updt_lock);
/* if st->update appears to be in future it means
* that the last acked value is very old and we
* remain unconnected a too long time to use this
@@ -2918,7 +2918,7 @@ static inline void init_accepted_peer(struct peer *peer, struct peers *peers)
__ha_cpu_relax();
}
- HA_RWLOCK_WRUNLOCK(STK_TABLE_LOCK, &st->table->lock);
+ HA_RWLOCK_WRUNLOCK(STK_TABLE_LOCK, &st->table->updt_lock);
}
/* reset teaching and learning flags to 0 */
@@ -2959,7 +2959,7 @@ static inline void init_connected_peer(struct peer *peer, struct peers *peers)
uint updateid, commitid;
st->last_get = st->last_acked = 0;
- HA_RWLOCK_WRLOCK(STK_TABLE_LOCK, &st->table->lock);
+ HA_RWLOCK_WRLOCK(STK_TABLE_LOCK, &st->table->updt_lock);
/* if st->update appears to be in future it means
* that the last acked value is very old and we
* remain unconnected a too long time to use this
@@ -2983,7 +2983,7 @@ static inline void init_connected_peer(struct peer *peer, struct peers *peers)
__ha_cpu_relax();
}
- HA_RWLOCK_WRUNLOCK(STK_TABLE_LOCK, &st->table->lock);
+ HA_RWLOCK_WRUNLOCK(STK_TABLE_LOCK, &st->table->updt_lock);
}
/* Init confirm counter */
diff --git a/src/proxy.c b/src/proxy.c
index ef95340..19e6c4b 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -277,6 +277,8 @@ void free_proxy(struct proxy *p)
list_for_each_entry_safe(rule, ruleb, &p->switching_rules, list) {
LIST_DELETE(&rule->list);
free_acl_cond(rule->cond);
+ if (rule->dynamic)
+ free_logformat_list(&rule->be.expr);
free(rule->file);
free(rule);
}
diff --git a/src/qpack-dec.c b/src/qpack-dec.c
index 97392bb..7a8726f 100644
--- a/src/qpack-dec.c
+++ b/src/qpack-dec.c
@@ -135,6 +135,19 @@ int qpack_decode_enc(struct buffer *buf, int fin, void *ctx)
}
else if (inst & QPACK_ENC_INST_SDTC_BIT) {
/* Set dynamic table capacity */
+ int capacity = *b_head(buf) & 0x1f;
+
+ /* RFC 9204 4.3.1. Set Dynamic Table Capacity
+ *
+ * The decoder MUST treat a new dynamic table capacity
+ * value that exceeds this limit as a connection error of type
+ * QPACK_ENCODER_STREAM_ERROR.
+ */
+ if (capacity) {
+ qcc_set_error(qcs->qcc, QPACK_ENCODER_STREAM_ERROR, 1);
+ return -1;
+ }
+
}
return 0;
@@ -173,6 +186,18 @@ int qpack_decode_dec(struct buffer *buf, int fin, void *ctx)
inst = (unsigned char)*b_head(buf) & QPACK_DEC_INST_BITMASK;
if (inst == QPACK_DEC_INST_ICINC) {
/* Insert count increment */
+
+ /* RFC 9204 4.4.3. Insert Count Increment
+ *
+ * An encoder that receives an Increment field equal to zero, or one
+ * that increases the Known Received Count beyond what the encoder has
+ * sent, MUST treat this as a connection error of type
+ * QPACK_DECODER_STREAM_ERROR.
+ */
+
+ /* For the moment haproxy does not emit dynamic table insertion. */
+ qcc_set_error(qcs->qcc, QPACK_DECODER_STREAM_ERROR, 1);
+ return -1;
}
else if (inst & QPACK_DEC_INST_SACK) {
/* Section Acknowledgment */
diff --git a/src/quic_cli.c b/src/quic_cli.c
index 56301fa..f237a1f 100644
--- a/src/quic_cli.c
+++ b/src/quic_cli.c
@@ -204,7 +204,7 @@ static void dump_quic_full(struct show_quic_ctx *ctx, struct quic_conn *qc)
}
chunk_appendf(&trash, " srtt=%-4u rttvar=%-4u rttmin=%-4u ptoc=%-4u cwnd=%-6llu"
- " mcwnd=%-6llu sentpkts=%-6llu lostpkts=%-6llu\n reorderedpkts=%-6llu",
+ " mcwnd=%-6llu sentpkts=%-6llu lostpkts=%-6llu reorderedpkts=%-6llu\n",
qc->path->loss.srtt, qc->path->loss.rtt_var,
qc->path->loss.rtt_min, qc->path->loss.pto_count, (ullong)qc->path->cwnd,
(ullong)qc->path->mcwnd, (ullong)qc->cntrs.sent_pkt, (ullong)qc->path->loss.nb_lost_pkt, (ullong)qc->path->loss.nb_reordered_pkt);
diff --git a/src/quic_frame.c b/src/quic_frame.c
index 61d2c93..41309db 100644
--- a/src/quic_frame.c
+++ b/src/quic_frame.c
@@ -13,7 +13,7 @@
#include <haproxy/buf-t.h>
#include <haproxy/chunk.h>
#include <haproxy/pool.h>
-#include <haproxy/quic_conn-t.h>
+#include <haproxy/quic_conn.h>
#include <haproxy/quic_enc.h>
#include <haproxy/quic_frame.h>
#include <haproxy/quic_rx-t.h>
@@ -1114,7 +1114,13 @@ int qc_parse_frm(struct quic_frame *frm, struct quic_rx_packet *pkt,
frm->type = *(*pos)++;
if (frm->type >= QUIC_FT_MAX) {
+ /* RFC 9000 12.4. Frames and Frame Types
+ *
+ * An endpoint MUST treat the receipt of a frame of unknown type as a
+ * connection error of type FRAME_ENCODING_ERROR.
+ */
TRACE_DEVEL("wrong frame type", QUIC_EV_CONN_PRSFRM, qc, frm);
+ quic_set_connection_close(qc, quic_err_transport(QC_ERR_FRAME_ENCODING_ERROR));
goto leave;
}
diff --git a/src/quic_rx.c b/src/quic_rx.c
index 9e55aa3..585c71a 100644
--- a/src/quic_rx.c
+++ b/src/quic_rx.c
@@ -719,29 +719,7 @@ static int qc_handle_crypto_frm(struct quic_conn *qc,
crypto_frm->offset = cstream->rx.offset;
}
- if (crypto_frm->offset == cstream->rx.offset && ncb_is_empty(ncbuf)) {
- struct qf_crypto *qf_crypto;
-
- qf_crypto = pool_alloc(pool_head_qf_crypto);
- if (!qf_crypto) {
- TRACE_ERROR("CRYPTO frame allocation failed", QUIC_EV_CONN_PRSHPKT, qc);
- goto leave;
- }
-
- qf_crypto->offset = crypto_frm->offset;
- qf_crypto->len = crypto_frm->len;
- qf_crypto->data = crypto_frm->data;
- qf_crypto->qel = qel;
- LIST_APPEND(&qel->rx.crypto_frms, &qf_crypto->list);
-
- cstream->rx.offset += crypto_frm->len;
- HA_ATOMIC_OR(&qc->wait_event.tasklet->state, TASK_HEAVY);
- TRACE_DEVEL("increment crypto level offset", QUIC_EV_CONN_PHPKTS, qc, qel);
- goto done;
- }
-
- if (!quic_get_ncbuf(ncbuf) ||
- ncb_is_null(ncbuf)) {
+ if (!quic_get_ncbuf(ncbuf) || ncb_is_null(ncbuf)) {
TRACE_ERROR("CRYPTO ncbuf allocation failed", QUIC_EV_CONN_PRSHPKT, qc);
goto leave;
}
@@ -1048,6 +1026,14 @@ static int qc_parse_pkt_frms(struct quic_conn *qc, struct quic_rx_packet *pkt,
if (qc_is_listener(qc)) {
TRACE_ERROR("non accepted QUIC_FT_HANDSHAKE_DONE frame",
QUIC_EV_CONN_PRSHPKT, qc);
+
+ /* RFC 9000 19.20. HANDSHAKE_DONE Frames
+ *
+ * A
+ * server MUST treat receipt of a HANDSHAKE_DONE frame as a connection
+ * error of type PROTOCOL_VIOLATION.
+ */
+ quic_set_connection_close(qc, quic_err_transport(QC_ERR_PROTOCOL_VIOLATION));
goto leave;
}
diff --git a/src/quic_sock.c b/src/quic_sock.c
index c479249..f796513 100644
--- a/src/quic_sock.c
+++ b/src/quic_sock.c
@@ -686,14 +686,16 @@ int qc_snd_buf(struct quic_conn *qc, const struct buffer *buf, size_t sz,
if (ret < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK ||
errno == ENOTCONN || errno == EINPROGRESS) {
+ /* transient error */
if (errno == EAGAIN || errno == EWOULDBLOCK)
qc->cntrs.socket_full++;
else
qc->cntrs.sendto_err++;
- /* transient error */
- fd_want_send(qc->fd);
- fd_cant_send(qc->fd);
+ if (qc_test_fd(qc)) {
+ fd_want_send(qc->fd);
+ fd_cant_send(qc->fd);
+ }
TRACE_PRINTF(TRACE_LEVEL_USER, QUIC_EV_CONN_SPPKTS, qc, 0, 0, 0,
"UDP send failure errno=%d (%s)", errno, strerror(errno));
return 0;
diff --git a/src/quic_ssl.c b/src/quic_ssl.c
index 314f587..85b6717 100644
--- a/src/quic_ssl.c
+++ b/src/quic_ssl.c
@@ -636,29 +636,12 @@ int qc_ssl_provide_all_quic_data(struct quic_conn *qc, struct ssl_sock_ctx *ctx)
{
int ret = 0;
struct quic_enc_level *qel;
- struct ncbuf ncbuf = NCBUF_NULL;
TRACE_ENTER(QUIC_EV_CONN_PHPKTS, qc);
list_for_each_entry(qel, &qc->qel_list, list) {
- struct qf_crypto *qf_crypto, *qf_back;
+ struct quic_cstream *cstream = qel->cstream;
- list_for_each_entry_safe(qf_crypto, qf_back, &qel->rx.crypto_frms, list) {
- const unsigned char *crypto_data = qf_crypto->data;
- size_t crypto_len = qf_crypto->len;
-
- /* Free this frame asap */
- LIST_DELETE(&qf_crypto->list);
- pool_free(pool_head_qf_crypto, qf_crypto);
-
- if (!qc_ssl_provide_quic_data(&ncbuf, qel->level, ctx,
- crypto_data, crypto_len))
- goto leave;
-
- TRACE_DEVEL("buffered crypto data were provided to TLS stack",
- QUIC_EV_CONN_PHPKTS, qc, qel);
- }
-
- if (!qel->cstream)
+ if (!cstream)
continue;
if (!qc_treat_rx_crypto_frms(qc, qel, ctx))
diff --git a/src/quic_tls.c b/src/quic_tls.c
index 581d615..aa72831 100644
--- a/src/quic_tls.c
+++ b/src/quic_tls.c
@@ -170,7 +170,6 @@ struct quic_cstream *quic_cstream_new(struct quic_conn *qc)
void quic_conn_enc_level_uninit(struct quic_conn *qc, struct quic_enc_level *qel)
{
int i;
- struct qf_crypto *qf_crypto, *qfback;
TRACE_ENTER(QUIC_EV_CONN_CLOSE, qc);
@@ -181,11 +180,6 @@ void quic_conn_enc_level_uninit(struct quic_conn *qc, struct quic_enc_level *qel
}
}
- list_for_each_entry_safe(qf_crypto, qfback, &qel->rx.crypto_frms, list) {
- LIST_DELETE(&qf_crypto->list);
- pool_free(pool_head_qf_crypto, qf_crypto);
- }
-
ha_free(&qel->tx.crypto.bufs);
quic_cstream_free(qel->cstream);
@@ -223,7 +217,6 @@ static int quic_conn_enc_level_init(struct quic_conn *qc,
qel->rx.pkts = EB_ROOT;
LIST_INIT(&qel->rx.pqpkts);
- LIST_INIT(&qel->rx.crypto_frms);
/* Allocate only one buffer. */
/* TODO: use a pool */
diff --git a/src/server.c b/src/server.c
index 829fbb3..9196fac 100644
--- a/src/server.c
+++ b/src/server.c
@@ -900,6 +900,11 @@ static int srv_parse_disabled(char **args, int *cur_arg,
static int srv_parse_enabled(char **args, int *cur_arg,
struct proxy *curproxy, struct server *newsrv, char **err)
{
+ if (newsrv->flags & SRV_F_DYNAMIC) {
+ ha_warning("Keyword 'enabled' is ignored for dynamic servers. It will be rejected from 3.0 onward.");
+ return 0;
+ }
+
newsrv->next_admin &= ~SRV_ADMF_CMAINT & ~SRV_ADMF_FMAINT;
newsrv->next_state = SRV_ST_RUNNING;
newsrv->check.state &= ~CHK_ST_PAUSED;
@@ -2223,7 +2228,7 @@ void srv_compute_all_admin_states(struct proxy *px)
*/
static struct srv_kw_list srv_kws = { "ALL", { }, {
{ "backup", srv_parse_backup, 0, 1, 1 }, /* Flag as backup server */
- { "cookie", srv_parse_cookie, 1, 1, 0 }, /* Assign a cookie to the server */
+ { "cookie", srv_parse_cookie, 1, 1, 1 }, /* Assign a cookie to the server */
{ "disabled", srv_parse_disabled, 0, 1, 1 }, /* Start the server in 'disabled' state */
{ "enabled", srv_parse_enabled, 0, 1, 1 }, /* Start the server in 'enabled' state */
{ "error-limit", srv_parse_error_limit, 1, 1, 1 }, /* Configure the consecutive count of check failures to consider a server on error */
@@ -2507,8 +2512,10 @@ static void srv_conn_src_cpy(struct server *srv, const struct server *src)
srv->conn_src.bind_hdr_occ = src->conn_src.bind_hdr_occ;
srv->conn_src.tproxy_addr = src->conn_src.tproxy_addr;
#endif
- if (src->conn_src.iface_name != NULL)
+ if (src->conn_src.iface_name != NULL) {
srv->conn_src.iface_name = strdup(src->conn_src.iface_name);
+ srv->conn_src.iface_len = src->conn_src.iface_len;
+ }
}
/*
@@ -5528,6 +5535,11 @@ static int cli_parse_add_server(char **args, char *payload, struct appctx *appct
*/
srv->rid = (srv_id_reuse_cnt) ? (srv_id_reuse_cnt / 2) : 0;
+ /* generate new server's dynamic cookie if enabled on backend */
+ if (be->ck_opts & PR_CK_DYNAMIC) {
+ srv_set_dyncookie(srv);
+ }
+
/* adding server cannot fail when we reach this:
* publishing EVENT_HDL_SUB_SERVER_ADD
*/
@@ -5549,6 +5561,9 @@ static int cli_parse_add_server(char **args, char *payload, struct appctx *appct
ha_alert("System might be unstable, consider to execute a reload");
}
+ if (srv->cklen && be->mode != PR_MODE_HTTP)
+ ha_warning("Ignoring cookie as HTTP mode is disabled.\n");
+
ha_notice("New server registered.\n");
cli_umsg(appctx, LOG_INFO);
@@ -5637,7 +5652,7 @@ static int cli_parse_delete_server(char **args, char *payload, struct appctx *ap
* TODO idle connections should not prevent server deletion. A proper
* cleanup function should be implemented to be used here.
*/
- if (srv->cur_sess || srv->curr_idle_conns ||
+ if (srv->curr_used_conns || srv->curr_idle_conns ||
!eb_is_empty(&srv->queue.head) || srv_has_streams(srv)) {
cli_err(appctx, "Server still has connections attached to it, cannot remove it.");
goto out;
diff --git a/src/sink.c b/src/sink.c
index 66c2b8c..58adfcd 100644
--- a/src/sink.c
+++ b/src/sink.c
@@ -439,7 +439,7 @@ static void sink_forward_oc_io_handler(struct appctx *appctx)
struct ring *ring = sink->ctx.ring;
struct buffer *buf = &ring->buf;
uint64_t msg_len;
- size_t len, cnt, ofs;
+ size_t len, cnt, ofs, last_ofs;
int ret = 0;
char *p;
@@ -530,16 +530,24 @@ static void sink_forward_oc_io_handler(struct appctx *appctx)
}
HA_ATOMIC_INC(b_peek(buf, ofs));
+ last_ofs = b_tail_ofs(buf);
sft->ofs = b_peek_ofs(buf, ofs);
-
HA_RWLOCK_RDUNLOCK(RING_LOCK, &ring->lock);
if (ret) {
/* let's be woken up once new data arrive */
HA_RWLOCK_WRLOCK(RING_LOCK, &ring->lock);
LIST_APPEND(&ring->waiters, &appctx->wait_entry);
+ ofs = b_tail_ofs(buf);
HA_RWLOCK_WRUNLOCK(RING_LOCK, &ring->lock);
- applet_have_no_more_data(appctx);
+ if (ofs != last_ofs) {
+ /* more data was added into the ring between the
+ * unlock and the lock, and the writer might not
+ * have seen us. We need to reschedule a read.
+ */
+ applet_have_more_data(appctx);
+ } else
+ applet_have_no_more_data(appctx);
}
HA_SPIN_UNLOCK(SFT_LOCK, &sft->lock);
diff --git a/src/ssl_ckch.c b/src/ssl_ckch.c
index afe6ff6..ebab1f3 100644
--- a/src/ssl_ckch.c
+++ b/src/ssl_ckch.c
@@ -3936,7 +3936,7 @@ static struct cli_kw_list cli_kws = {{ },{
{ { "del", "ssl", "ca-file", NULL }, "del ssl ca-file <cafile> : delete an unused CA file", cli_parse_del_cafile, NULL, NULL },
{ { "show", "ssl", "ca-file", NULL }, "show ssl ca-file [<cafile>[:<index>]] : display the SSL CA files used in memory, or the details of a <cafile>, or a single certificate of index <index> of a CA file <cafile>", cli_parse_show_cafile, cli_io_handler_show_cafile, cli_release_show_cafile },
- { { "new", "ssl", "crl-file", NULL }, "new ssl crlfile <crlfile> : create a new CRL file to be used in a crt-list", cli_parse_new_crlfile, NULL, NULL },
+ { { "new", "ssl", "crl-file", NULL }, "new ssl crl-file <crlfile> : create a new CRL file to be used in a crt-list", cli_parse_new_crlfile, NULL, NULL },
{ { "set", "ssl", "crl-file", NULL }, "set ssl crl-file <crlfile> <payload> : replace a CRL file", cli_parse_set_crlfile, NULL, NULL },
{ { "commit", "ssl", "crl-file", NULL },"commit ssl crl-file <crlfile> : commit a CRL file", cli_parse_commit_crlfile, cli_io_handler_commit_cafile_crlfile, cli_release_commit_crlfile },
{ { "abort", "ssl", "crl-file", NULL }, "abort ssl crl-file <crlfile> : abort a transaction for a CRL file", cli_parse_abort_crlfile, NULL, NULL },
diff --git a/src/ssl_crtlist.c b/src/ssl_crtlist.c
index dcd9171..d788bec 100644
--- a/src/ssl_crtlist.c
+++ b/src/ssl_crtlist.c
@@ -1514,7 +1514,6 @@ static int cli_parse_del_crtlist(char **args, char *payload, struct appctx *appc
list_for_each_entry_safe(inst, inst_s, &entry->ckch_inst, by_crtlist_entry) {
struct sni_ctx *sni, *sni_s;
- struct ckch_inst_link_ref *link_ref, *link_ref_s;
HA_RWLOCK_WRLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
list_for_each_entry_safe(sni, sni_s, &inst->sni_ctx, by_ckch_inst) {
@@ -1524,12 +1523,6 @@ static int cli_parse_del_crtlist(char **args, char *payload, struct appctx *appc
free(sni);
}
HA_RWLOCK_WRUNLOCK(SNI_LOCK, &inst->bind_conf->sni_lock);
- LIST_DELETE(&inst->by_ckchs);
- list_for_each_entry_safe(link_ref, link_ref_s, &inst->cafile_link_refs, list) {
- LIST_DELETE(&link_ref->link->list);
- LIST_DELETE(&link_ref->list);
- free(link_ref);
- }
ckch_inst_free(inst);
}
diff --git a/src/ssl_ocsp.c b/src/ssl_ocsp.c
index 3e7408a..5b103af 100644
--- a/src/ssl_ocsp.c
+++ b/src/ssl_ocsp.c
@@ -843,7 +843,6 @@ static struct proxy *httpclient_ocsp_update_px;
static struct ssl_ocsp_task_ctx {
struct certificate_ocsp *cur_ocsp;
struct httpclient *hc;
- struct appctx *appctx;
int flags;
int update_status;
} ssl_ocsp_task_ctx;
@@ -1078,18 +1077,41 @@ void ocsp_update_response_end_cb(struct httpclient *hc)
/*
- * Send a log line that will use the dedicated proxy's error_logformat string.
- * It uses the sess_log function instead of app_log for instance in order to
- * benefit from the "generic" items that can be added to a log format line such
- * as the date and frontend name that can be found at the beginning of the
- * ocspupdate_log_format line.
+ * Send a log line that will mimic this previously used logformat :
+ * char ocspupdate_log_format[] = "%ci:%cp [%tr] %ft %[ssl_ocsp_certname] \
+ * %[ssl_ocsp_status] %{+Q}[ssl_ocsp_status_str] %[ssl_ocsp_fail_cnt] \
+ * %[ssl_ocsp_success_cnt]";
+ * We can't use the regular sess_log function because we don't have any control
+ * over the stream and session used by the httpclient which might not exist
+ * anymore by the time we call this function.
*/
static void ssl_ocsp_send_log()
{
- if (!ssl_ocsp_task_ctx.appctx)
+ int status_str_len = 0;
+ char *status_str = NULL;
+ struct certificate_ocsp *ocsp = ssl_ocsp_task_ctx.cur_ocsp;
+ struct tm tm;
+ char timebuf[25];
+
+ if (!httpclient_ocsp_update_px)
return;
- sess_log(ssl_ocsp_task_ctx.appctx->sess);
+ if (ocsp && ssl_ocsp_task_ctx.update_status < OCSP_UPDT_ERR_LAST) {
+ status_str_len = istlen(ocsp_update_errors[ssl_ocsp_task_ctx.update_status]);
+ status_str = istptr(ocsp_update_errors[ssl_ocsp_task_ctx.update_status]);
+ }
+
+ get_localtime(date.tv_sec, &tm);
+ date2str_log(timebuf, &tm, &date, 25);
+
+ send_log(httpclient_ocsp_update_px, LOG_INFO, "-:- [%s] %s %s %u \"%.*s\" %u %u",
+ timebuf,
+ httpclient_ocsp_update_px->id,
+ ocsp->path,
+ ssl_ocsp_task_ctx.update_status,
+ status_str_len, status_str,
+ ocsp ? ocsp->num_failure : 0,
+ ocsp ? ocsp->num_success : 0);
}
/*
@@ -1282,7 +1304,7 @@ static struct task *ssl_ocsp_update_responses(struct task *task, void *context,
hc->ops.res_payload = ocsp_update_response_body_cb;
hc->ops.res_end = ocsp_update_response_end_cb;
- if (!(ctx->appctx = httpclient_start(hc))) {
+ if (!httpclient_start(hc)) {
goto leave;
}
@@ -1346,7 +1368,6 @@ http_error:
return task;
}
-char ocspupdate_log_format[] = "%ci:%cp [%tr] %ft %[ssl_ocsp_certname] %[ssl_ocsp_status] %{+Q}[ssl_ocsp_status_str] %[ssl_ocsp_fail_cnt] %[ssl_ocsp_success_cnt]";
/*
* Initialize the proxy for the OCSP update HTTP client with 2 servers, one for
@@ -1358,7 +1379,6 @@ static int ssl_ocsp_update_precheck()
httpclient_ocsp_update_px = httpclient_create_proxy("<OCSP-UPDATE>");
if (!httpclient_ocsp_update_px)
return 1;
- httpclient_ocsp_update_px->conf.error_logformat_string = strdup(ocspupdate_log_format);
httpclient_ocsp_update_px->conf.logformat_string = httpclient_log_format;
httpclient_ocsp_update_px->options2 |= PR_O2_NOLOGNORM;
@@ -1662,7 +1682,8 @@ int ocsp_update_check_cfg_consistency(struct ckch_store *store, struct crtlist_e
if (store->data->ocsp_update_mode != SSL_SOCK_OCSP_UPDATE_DFLT || entry->ssl_conf) {
if ((!entry->ssl_conf && store->data->ocsp_update_mode == SSL_SOCK_OCSP_UPDATE_ON)
- || (entry->ssl_conf && store->data->ocsp_update_mode != entry->ssl_conf->ocsp_update)) {
+ || (entry->ssl_conf && entry->ssl_conf->ocsp_update != SSL_SOCK_OCSP_UPDATE_OFF &&
+ store->data->ocsp_update_mode != entry->ssl_conf->ocsp_update)) {
memprintf(err, "%sIncompatibilities found in OCSP update mode for certificate %s\n", err && *err ? *err : "", crt_path);
err_code |= ERR_ALERT | ERR_FATAL;
}
diff --git a/src/ssl_sample.c b/src/ssl_sample.c
index 789637f..22b4072 100644
--- a/src/ssl_sample.c
+++ b/src/ssl_sample.c
@@ -251,7 +251,7 @@ static int sample_conv_aes_gcm_dec(const struct arg *arg_p, struct sample *smp,
{
struct sample nonce, key, aead_tag;
struct buffer *smp_trash = NULL, *smp_trash_alloc = NULL;
- EVP_CIPHER_CTX *ctx;
+ EVP_CIPHER_CTX *ctx = NULL;
int dec_size, ret;
smp_trash_alloc = alloc_trash_chunk();
@@ -350,11 +350,13 @@ static int sample_conv_aes_gcm_dec(const struct arg *arg_p, struct sample *smp,
smp_dup(smp);
free_trash_chunk(smp_trash_alloc);
free_trash_chunk(smp_trash);
+ EVP_CIPHER_CTX_free(ctx);
return 1;
err:
free_trash_chunk(smp_trash_alloc);
free_trash_chunk(smp_trash);
+ EVP_CIPHER_CTX_free(ctx);
return 0;
}
#endif
@@ -1347,7 +1349,9 @@ smp_fetch_ssl_fc_ec(const struct arg *args, struct sample *smp, const char *kw,
* different functional calls and to make it consistent while upgrading OpenSSL versions,
* will convert the curve name returned by SSL_get0_group_name to upper case.
*/
- for (int i = 0; curve_name[i]; i++)
+ int i;
+
+ for (i = 0; curve_name[i]; i++)
curve_name[i] = toupper(curve_name[i]);
}
#else
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index c7403b8..96d826e 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -4136,6 +4136,14 @@ int ssl_sock_load_cert(char *path, struct bind_conf *bind_conf, char **err)
if ((ckchs = ckchs_lookup(path))) {
/* we found the ckchs in the tree, we can use it directly */
cfgerr |= ssl_sock_load_ckchs(path, ckchs, bind_conf, NULL, NULL, 0, &ckch_inst, err);
+
+ /* This certificate has an 'ocsp-update' already set in a
+ * previous crt-list so we must raise an error. */
+ if (ckchs->data->ocsp_update_mode == SSL_SOCK_OCSP_UPDATE_ON) {
+ memprintf(err, "%sIncompatibilities found in OCSP update mode for certificate %s\n", err && *err ? *err: "", path);
+ cfgerr |= ERR_ALERT | ERR_FATAL;
+ }
+
found++;
} else if (stat(path, &buf) == 0) {
found++;
diff --git a/src/stats.c b/src/stats.c
index 0ed5758..ac47f00 100644
--- a/src/stats.c
+++ b/src/stats.c
@@ -5037,6 +5037,14 @@ static int stats_dump_json_schema_to_buffer(struct appctx *appctx)
return 1;
}
+static void http_stats_release(struct appctx *appctx)
+{
+ struct show_stat_ctx *ctx = appctx->svcctx;
+
+ if (ctx->px_st == STAT_PX_ST_SV)
+ srv_drop(ctx->obj2);
+}
+
static int cli_parse_clear_counters(char **args, char *payload, struct appctx *appctx, void *private)
{
struct proxy *px;
@@ -5255,6 +5263,14 @@ static int cli_io_handler_dump_stat(struct appctx *appctx)
return stats_dump_stat_to_buffer(appctx_sc(appctx), NULL);
}
+static void cli_io_handler_release_stat(struct appctx *appctx)
+{
+ struct show_stat_ctx *ctx = appctx->svcctx;
+
+ if (ctx->px_st == STAT_PX_ST_SV)
+ srv_drop(ctx->obj2);
+}
+
static int cli_io_handler_dump_json_schema(struct appctx *appctx)
{
trash_chunk = b_make(trash.area, trash.size, 0, 0);
@@ -5499,7 +5515,7 @@ REGISTER_PER_THREAD_FREE(free_trash_counters);
static struct cli_kw_list cli_kws = {{ },{
{ { "clear", "counters", NULL }, "clear counters [all] : clear max statistics counters (or all counters)", cli_parse_clear_counters, NULL, NULL },
{ { "show", "info", NULL }, "show info [desc|json|typed|float]* : report information about the running process", cli_parse_show_info, cli_io_handler_dump_info, NULL },
- { { "show", "stat", NULL }, "show stat [desc|json|no-maint|typed|up]*: report counters for each proxy and server", cli_parse_show_stat, cli_io_handler_dump_stat, NULL },
+ { { "show", "stat", NULL }, "show stat [desc|json|no-maint|typed|up]*: report counters for each proxy and server", cli_parse_show_stat, cli_io_handler_dump_stat, cli_io_handler_release_stat },
{ { "show", "schema", "json", NULL }, "show schema json : report schema used for stats", NULL, cli_io_handler_dump_json_schema, NULL },
{{},}
}};
@@ -5510,7 +5526,7 @@ struct applet http_stats_applet = {
.obj_type = OBJ_TYPE_APPLET,
.name = "<STATS>", /* used for logging */
.fct = http_stats_io_handler,
- .release = NULL,
+ .release = http_stats_release,
};
/*
diff --git a/src/stconn.c b/src/stconn.c
index 8e3ae7e..df119a1 100644
--- a/src/stconn.c
+++ b/src/stconn.c
@@ -526,7 +526,7 @@ static inline int sc_cond_forward_shut(struct stconn *sc)
if (!(sc->flags & (SC_FL_EOS|SC_FL_ABRT_DONE)) || !(sc->flags & SC_FL_NOHALF))
return 0;
- if (co_data(sc_ic(sc)) && !(sc_ic(sc)->flags & CF_WRITE_TIMEOUT)) {
+ if ((co_data(sc_ic(sc)) || sc_ep_have_ff_data(sc_opposite(sc))) && !(sc_ic(sc)->flags & CF_WRITE_TIMEOUT)) {
/* the shutdown cannot be forwarded now because
* we should flush outgoing data first. But instruct the output
* channel it should be done ASAP.
@@ -1060,7 +1060,7 @@ void sc_notify(struct stconn *sc)
struct task *task = sc_strm_task(sc);
/* process consumer side */
- if (!co_data(oc)) {
+ if (!co_data(oc) && !sc_ep_have_ff_data(sco)) {
struct connection *conn = sc_conn(sc);
if (((sc->flags & (SC_FL_SHUT_DONE|SC_FL_SHUT_WANTED)) == SC_FL_SHUT_WANTED) &&
diff --git a/src/stick_table.c b/src/stick_table.c
index 6427568..b1ce9d4 100644
--- a/src/stick_table.c
+++ b/src/stick_table.c
@@ -608,9 +608,9 @@ void stktable_requeue_exp(struct stktable *t, const struct stksess *ts)
new_exp = tick_first(expire, old_exp);
}
- HA_RWLOCK_WRUNLOCK(STK_TABLE_LOCK, &t->lock);
-
task_queue(t->exp_task);
+
+ HA_RWLOCK_WRUNLOCK(STK_TABLE_LOCK, &t->lock);
}
/* Returns a valid or initialized stksess for the specified stktable_key in the
diff --git a/src/stream.c b/src/stream.c
index a3c0c93..e643a6d 100644
--- a/src/stream.c
+++ b/src/stream.c
@@ -537,7 +537,7 @@ struct stream *stream_new(struct session *sess, struct stconn *sc, struct buffer
s->res.analyse_exp = TICK_ETERNITY;
s->txn = NULL;
- s->hlua = NULL;
+ s->hlua[0] = s->hlua[1] = NULL;
s->resolv_ctx.requester = NULL;
s->resolv_ctx.hostname_dn = NULL;
@@ -649,8 +649,10 @@ void stream_free(struct stream *s)
flt_stream_stop(s);
flt_stream_release(s, 0);
- hlua_ctx_destroy(s->hlua);
- s->hlua = NULL;
+ hlua_ctx_destroy(s->hlua[0]);
+ hlua_ctx_destroy(s->hlua[1]);
+ s->hlua[0] = s->hlua[1] = NULL;
+
if (s->txn)
http_destroy_txn(s);
@@ -2367,7 +2369,7 @@ struct task *process_stream(struct task *t, void *context, unsigned int state)
/* shutdown(write) pending */
if (unlikely((scb->flags & (SC_FL_SHUT_DONE|SC_FL_SHUT_WANTED)) == SC_FL_SHUT_WANTED &&
- (!co_data(req) || (req->flags & CF_WRITE_TIMEOUT)))) {
+ ((!co_data(req) && !sc_ep_have_ff_data(scb)) || (req->flags & CF_WRITE_TIMEOUT)))) {
if (scf->flags & SC_FL_ERROR)
scb->flags |= SC_FL_NOLINGER;
sc_shutdown(scb);
@@ -2475,7 +2477,7 @@ struct task *process_stream(struct task *t, void *context, unsigned int state)
/* shutdown(write) pending */
if (unlikely((scf->flags & (SC_FL_SHUT_DONE|SC_FL_SHUT_WANTED)) == SC_FL_SHUT_WANTED &&
- (!co_data(res) || (res->flags & CF_WRITE_TIMEOUT)))) {
+ ((!co_data(res) && !sc_ep_have_ff_data(scf)) || (res->flags & CF_WRITE_TIMEOUT)))) {
sc_shutdown(scf);
}
@@ -2681,6 +2683,20 @@ void sess_change_server(struct stream *strm, struct server *newsrv)
{
struct server *oldsrv = strm->srv_conn;
+ /* Dynamic servers may be deleted during process lifetime. This
+ * operation is always conducted under thread isolation. Several
+ * conditions prevent deletion, one of them is if server streams list
+ * is not empty. sess_change_server() uses stream_add_srv_conn() to
+ * ensure the latter condition.
+ *
+ * A race condition could exist for stream which referenced a server
+ * instance (s->target) without registering itself in its server list.
+ * This is notably the case for SF_DIRECT streams which referenced a
+ * server earlier during process_stream(). However at this time the
+ * code is deemed safe as process_stream() cannot be rescheduled before
+ * invocation of sess_change_server().
+ */
+
if (oldsrv == newsrv)
return;
diff --git a/src/tools.c b/src/tools.c
index b2814b5..e1ba241 100644
--- a/src/tools.c
+++ b/src/tools.c
@@ -6333,7 +6333,7 @@ void *dlopen(const char *filename, int flags)
static int init_tools_per_thread()
{
/* Let's make each thread start from a different position */
- statistical_prng_state += tid * MAX_THREADS;
+ statistical_prng_state += ha_random32();
if (!statistical_prng_state)
statistical_prng_state++;
return 1;