diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:20:34 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:20:34 +0000 |
commit | f3bb08bb1d94c77704371f8546a739119f0a05b4 (patch) | |
tree | 5dfb47fa424ecde655f8950098411a311e9296e6 | |
parent | Releasing progress-linux version 2.9.6-1~progress7.99u1. (diff) | |
download | haproxy-f3bb08bb1d94c77704371f8546a739119f0a05b4.tar.xz haproxy-f3bb08bb1d94c77704371f8546a739119f0a05b4.zip |
Merging upstream version 2.9.7.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
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 @@ -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 @@ -1,2 +1,2 @@ --9eafce5 +-5742051 @@ -1,2 +1,2 @@ -2024-02-26 18:42:42 +0100 -2024/02/26 +2024-04-05 20:18:55 +0200 +2024/04/05 @@ -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) @@ -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); @@ -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); @@ -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; @@ -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; |