Compare commits

..

33 Commits

Author SHA1 Message Date
Frederic Lecaille
8f79ac238c MINOR: mux-quic-be: Attach the mux to the stream connector
Add missing stream initialization required for connections to servers.
Modify qmux_init() to initialize a local stream.
Attach the mux to the stream connector.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
c6d79ebede MINOR: mux-quic-be: Add backend support to QUIC mux.
These modifications at least allow the configuration parser to support  addresses
for connection to servers without such an error:

config : proxy 'xxxx' : MUX protocol 'quic' is not usable for server 'yyyy'
2025-06-10 10:57:25 +02:00
Frederic Lecaille
b57eea3bf3 MINOR: quic-be: get rid of ->li quic_conn member
Replace ->li quic_conn pointer to struct listener member by  ->target which is
an object type enum and adapt the code.
Use __objt_(listener|server)() where the object type is known. Typically
this is were the code which is specific to one connection type (frontend/backend).
Remove <server> parameter passed to qc_new_conn(). It is redundant with the
<target> parameter.
GSO is not supported at this time for QUIC backend. qc_prep_pkts() is modified
to prevent it from building more than an MTU. This has as consequence to prevent
qc_send_ppkts() to use GSO.
ssl_clienthello.c code is run only by listeners. This is why __objt_listener()
is used in place of ->li.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
979b41fe5f MINOR: quic-be: SSL_get_peer_quic_transport_params() not defined by OpenSSL 3.5 QUIC API
Disable the code around SSL_get_peer_quic_transport_params() as this was done
for USE_QUIC_OPENSSL_COMPAT because SSL_get_peer_quic_transport_params() is not
defined by OpenSSL 3.5 QUIC API.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
f363190bd7 MINOR: quic-be: Make the secret derivation works for QUIC backends (USE_QUIC_OPENSSL_COMPAT)
quic_tls_compat_keylog_callback() is the callback used by the QUIC OpenSSL
compatibility module to derive the TLS secrets from other secrets provided
by keylog. The <write> local variable to this function is initialized to denote
the direction (write to send, read to receive) the secret is supposed to be used
for. That said, as the QUIC cryptographic algorithms are symmetrical, the
direction is inversed between the peer: a secret which is used to write/send/cipher
data from a peer point of view is also the secret which is used to
read/receive/decipher data. This was confirmed by the fact that without this
patch, the TLS stack first provides the peer with Handshake to send/cipher
data. The client could not use such secret to decipher the Handshake packets
received from the server. This patch simply reverse the direction stored by
<write> variable to make the secrets derivation works for the QUIC client.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
21e2c2e9dc MINOR: quic-be: Missing callbacks initializations (USE_QUIC_OPENSSL_COMPAT)
quic_tls_compat_init() function is called from OpenSSL QUIC compatibility module
(USE_QUIC_OPENSSL_COMPAT) to initialize the keylog callback and the callback
which stores the QUIC transport parameters as a TLS extensions into the stack.
These callbacks must also be initialized for QUIC backends.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
ad5b52e07a MINOR: quic-be: Version Information transport parameter check
Add a little check to verify that the version chosen by the server matches
with the client one. Initiliazes local transport parameters ->negotiated_version
value with this version if this is the case. If not, return 0;
2025-06-10 10:57:25 +02:00
Frederic Lecaille
acbb56eac6 MINOR: quic-be: Correct Version Information transp. param encoding
According to the RFC, a QUIC client must encode the QUIC version it supports
into the "Available Versions" of "Version Information" transport parameter
order by descending preference.

This is done defining <quic_version_2> and <quic_version_draft_29> new variables
pointers to the corresponding version of <quic_versions> array elements.
A client announces its available versions as follows: v1, v2, draft29.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
cfa82a8606 MINOR: quic-be: Store the remote transport parameters asap
This is done from TLS secrets derivation callback at Application level (the last
encryption level) calling SSL_get_peer_quic_transport_params() to have an access
to the TLS transport paremeters extension embedded into the Server Hello TLS message.
Then, quic_transport_params_store() is called to store a decoded version of
these transport parameters.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
7fc69b202a MINOR: quic-be: I/O handler switch adaptation
For connection to QUIC servers, this patch modifies the moment where the I/O
handler callback is switched to quic_conn_app_io_cb(). This is no more
done as for listener just after the handshake has completed but just after
it has been confirmed.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
a0d2a4fcfd MINOR: quic-be: Initial packet number space discarding.
Discard the Initial packet number space as soon as possible. This is done
during handshakes in quic_conn_io_cb() as soon as an Handshake packet could
be successfully sent.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
74a35d94ea MINOR: quic-be: Add the conn object to the server SSL context
The initialization of <ssl_app_data_index> SSL user data index is required
to make all the SSL sessions to QUIC servers work as this is done for TCP
servers. The conn object notably retrieve for SSL callback which are
server specific (e.g. ssl_sess_new_srv_cb()).
2025-06-10 10:57:25 +02:00
Frederic Lecaille
c7accba1db MINOR: quic-be: Build post handshake frames
This action is not specific to listeners. A QUIC client also have to send
NEW_CONNECTION_ID frames.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
9d515eb7f3 MINOR: quic-be: Store asap the DCID
Store the peer connection ID (SCID) as the connection DCID as soon as an Initial
packet is received.
Stop comparing the packet to QUIC_PACKET_TYPE_0RTT is already match as
QUIC_PACKET_TYPE_INITIAL.
A QUIC server must not send too short datagram with ack-eliciting packets inside.
This cannot be done from quic_rx_pkt_parse() because one does not know if
there is ack-eliciting frame into the Initial packets. If the packet must be
dropped, this is after having parsed it!
2025-06-10 10:57:25 +02:00
Frederic Lecaille
fb5ce7fd02 MINOR: h3-be: Correctly retrieve h3 counters
This is done using qc_counters() function which supports also QUIC servers.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
1f4cb27157 MINOR: quic-be: Handshake packet number space discarding
This is done for QUIC clients (or haproxy QUIC servers) when the handshake is
confirmed.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
bfa6885c2f MINOR: quic-be: Mux initialization
Reset the flags which denotes that the connection is waiting for a TLS handshake
completion. This must be accomplished before the mux creation.
Check the alpn has been successfully negotiated.
Then, finally create the mux. Note that the ->mux_proto of the server must be
set before calling conn_create_mux().
2025-06-10 10:57:25 +02:00
Frederic Lecaille
7d3f60f079 MINOR: quic-be: Datagrams and packet parsing support
Modify quic_dgram_parse() to stop passing it a listener as third parameter.
In place the object type address of the connection socket owner is passed
to support the haproxy servers with QUIC as transport protocol.
qc_owner_obj_type() is implemented to return this address.
qc_counters() is also implemented to return the QUIC specific counters of
the proxy of owner of the connection.
quic_rx_pkt_parse() called by quic_dgram_parse() is also modify to use
the object type address used by this latter as last parameter. It is
also modified to send Retry packet only from listeners. A QUIC client
(connection to haproxy QUIC servers) must drop the Initial packets with
non null token length. It is also not supposed to receive O-RTT packets
which are dropped.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
73054af3fc MINOR: quic-be: Do not redispatch the datagrams
The QUIC datagram redispatch is there to counter the race condition which
exists only for QUIC connections to listener where datagrams may arrive
on the wrong socket between the bind() and connect() calls.
Run this code part only for listeners.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
d5a2e9b44e MINOR: quic-be: add field for max_udp_payload_size into quic_conn
Add ->max_udp_payload_size new member to quic_conn struct.
Initialize it from qc_new_conn().
Adapt qc_snd_buf() to use it.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
5517b9c521 MINOR: quic-be: xprt ->init() adapatations
Allocate a connection to connect to QUIC servers from qc_conn_init() which is the
->init() QUIC xprt callback.
Also initialize ->prepare_srv and ->destroy_srv callback as this done for TCP
servers.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
e4f7a1fcde MINOR: quic-be: QUIC connection allocation adaptation (qc_new_conn())
For haproxy QUIC servers (or QUIC clients), the peer is considered as validated.
This is a property which is more specific to QUIC servers (haproxy QUIC listeners).
No <odcid> is used for the QUIC client connection. It is used only on the QUIC server side.
The <token_odcid> is also not used on the QUIC client side. It must be embedded into
the transport parameters only on the QUIC server side.
The quic_conn is created before the socket allocation. So, the local address is
zeroed.
Initilize the transport parameter with qc_srv_params_init().
Stop hardcoding the <server> parameter passed value to qc_new_isecs() to correctly
initialize the Initial secrets.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
7f89f1e451 MINOR: quic-be: ->connect() protocol callback adaptations
Modify quic_connect_server() which is the ->connect() callback for QUIC protocol:
    - add a BUG_ON() run when entering this funtion: the <fd> socket must equal -1
    - conn->handle is a union. conn->handle.qc is use for QUIC connection,
      conn->handle.fd must not be used to store the fd.
    - code alignment fix for setsockopt(fd, SOL_SOCKET, (SO_SNDBUF|SO_RCVBUF))
	  statements
    - remove the section of code which was duplicated from ->connect() TCP callback
    - fd_insert() the new socket file decriptor created to connect to the QUIC
      server with quic_conn_sock_fd_iocb() as callback for read event.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
b99ce3c985 MINOR: sock: Add protocol and socket types parameters to sock_create_server_socket()
This patch only adds <proto_type> new proto_type enum parameter and <sock_type>
socket type parameter to sock_create_server_socket() and adapts its callers.
This is to prepare the use of this function by QUIC servers/backends.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
abd35e9b17 MINOR: quic-be: Prevent the MUX to send/receive data
Such actions must be interrupted until the handshake completion.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
d5669868e6 MINOR: quic-be: Add a function to initialize the QUIC client transport parameters
Implement qc_srv_params_init() to initialize the QUIC client transport parameters
in relation with connections to haproxy servers/backends.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
7265955aa4 MINOR: quic-be: SSL sessions initializations
Modify qc_alloc_ssl_sock_ctx() to pass the connection object as parameter. It is
NULL for a QUIC listener, not NULL for a QUIC server. This connection object is
set as value for ->conn quic_conn struct member. Initialise the SSL session object from
this function for QUIC servers.
qc_ssl_set_quic_transport_params() is also modified to pass the SSL object as parameter.
This is the unique parameter this function needs. <qc> parameter is used only for
the trace.
SSL_do_handshake() must be calle as soon as the SSL object is initialized for
the QUIC backend connection. This triggers the TLS CRYPTO data delivery.
tasklet_wakeup() is also called to send asap these CRYPTO data.
Modify the QUIC_EV_CONN_NEW event trace to dump the potential errors returned by
SSL_do_handshake().
2025-06-10 10:57:25 +02:00
Frederic Lecaille
cc5ae9a29b MINOR: quic-be: ssl_sock contexts allocation and misc adaptations
Implement ssl_sock_new_ssl_ctx() to allocate a SSL server context as this is currently
done for TCP servers and also for QUIC servers depending on the <is_quic> boolean value
passed as new parameter. For QUIC servers, this function calls ssl_quic_srv_new_ssl_ctx()
which is specific to QUIC.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
e46e8e2bd3 MINOR: quic-be: Correct the QUIC protocol lookup
From connect_server(), QUIC protocol could not be retreived by protocol_lookup()
because of the PROTO_TYPE_STREAM default passed as argument. In place to support
QUIC srv->addr_type.proto_type may be safely passed.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
efbac62ded MINOR: quic-be: Add a function for the TLS context allocations
Implement ssl_quic_srv_new_ssl_ctx() whose aim is to allocate a TLS context
for QUIC servers.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
02f85dd6af MINOR: quic-be: QUIC server xprt already set when preparing their CTXs
The QUIC servers xprts have already been set at server line parsing time.
This patch prevents the QUIC servers xprts to be reset to <ssl_sock> value which is
the value used for SSL/TCP connections.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
e124a66c90 MINOR: quic-be: QUIC backend XPRT and transport parameters init during parsing
Add ->quic_params new member to server struct.
Add inlined function srv_is_quic() which to identifies the servers which are QUIC servers.
Also set the ->xprt member of the server being initialized and initialize asap its
transport parameters from _srv_parse_init().
Force the use of "expose-experimental-directives" global parameter to use QUIC
addresses on "server" lines.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
438bb236cf MINOR: quic-be: Call ->prepare_srv() callback at parsing time
This XPRT callback is called from check_config_validity() after the configuration
has been parsed to initialize all the SSL server contexts.

This patch implements the same thing for the QUIC servers.
2025-06-10 10:56:18 +02:00
21 changed files with 167 additions and 665 deletions

View File

@ -1,47 +1,6 @@
ChangeLog : ChangeLog :
=========== ===========
2025/06/11 : 3.3-dev1
- BUILD: tools: properly define ha_dump_backtrace() to avoid a build warning
- DOC: config: Fix a typo in 2.7 (Name format for maps and ACLs)
- REGTESTS: Do not use REQUIRE_VERSION for HAProxy 2.5+ (5)
- REGTESTS: Remove REQUIRE_VERSION=2.3 from all tests
- REGTESTS: Remove REQUIRE_VERSION=2.4 from all tests
- REGTESTS: Remove tests with REQUIRE_VERSION_BELOW=2.4
- REGTESTS: Remove support for REQUIRE_VERSION and REQUIRE_VERSION_BELOW
- MINOR: server: group postinit server tasks under _srv_postparse()
- MINOR: stats: add stat_col flags
- MINOR: stats: add ME_NEW_COMMON() helper
- MINOR: proxy: collect per-capability stat in proxy_cond_disable()
- MINOR: proxy: add a true list containing all proxies
- MINOR: log: only run postcheck_log_backend() checks on backend
- MEDIUM: proxy: use global proxy list for REGISTER_POST_PROXY_CHECK() hook
- MEDIUM: server: automatically add server to proxy list in new_server()
- MEDIUM: server: add and use srv_init() function
- BUG/MAJOR: leastconn: Protect tree_elt with the lbprm lock
- BUG/MEDIUM: check: Requeue healthchecks on I/O events to handle check timeout
- CLEANUP: applet: Update comment for applet_put* functions
- DEBUG: check: Add the healthcheck's expiration date in the trace messags
- BUG/MINOR: mux-spop: Fix null-pointer deref on SPOP stream allocation failure
- CLEANUP: sink: remove useless cleanup in sink_new_from_logger()
- MAJOR: counters: add shared counters base infrastructure
- MINOR: counters: add shared counters helpers to get and drop shared pointers
- MINOR: counters: add common struct and flags to {fe,be}_counters_shared
- MEDIUM: counters: manage shared counters using dedicated helpers
- CLEANUP: counters: merge some common counters between {fe,be}_counters_shared
- MINOR: counters: add local-only internal rates to compute some maxes
- MAJOR: counters: dispatch counters over thread groups
- BUG/MEDIUM: cli: Properly parse empty lines and avoid crashed
- BUG/MINOR: config: emit warning for empty args only in discovery mode
- BUG/MINOR: config: fix arg number reported on empty arg warning
- BUG/MINOR: quic: Missing SSL session object freeing
- MINOR: applet: Add API functions to manipulate input and output buffers
- MINOR: applet: Add API functions to get data from the input buffer
- CLEANUP: applet: Simplify a bit comments for applet_put* functions
- MEDIUM: hlua: Update TCP applet functions to use the new applet API
- BUG/MEDIUM: fd: Use the provided tgid in fd_insert() to get tgroup_info
- BUG/MINIR: h1: Fix doc of 'accept-unsafe-...-request' about URI parsing
2025/05/28 : 3.3-dev0 2025/05/28 : 3.3-dev0
- MINOR: version: mention that it's development again - MINOR: version: mention that it's development again

View File

@ -1,2 +1,2 @@
$Format:%ci$ $Format:%ci$
2025/06/11 2025/05/28

View File

@ -1 +1 @@
3.3-dev1 3.3-dev0

View File

@ -3,7 +3,7 @@
Configuration Manual Configuration Manual
---------------------- ----------------------
version 3.3 version 3.3
2025/06/11 2025/05/28
This document covers the configuration language as implemented in the version This document covers the configuration language as implemented in the version
@ -9077,14 +9077,11 @@ no option accept-unsafe-violations-in-http-request
* In H1 only, NULL character in header value will be accepted; * In H1 only, NULL character in header value will be accepted;
* In H1 only, characters above 127 in the URI will be accepted. The list of * The list of characters allowed to appear in a URI is well defined by
characters allowed to appear in a URI is well defined by RFC3986, and RFC3986, and chars 0-31, 32 (space), 34 ('"'), 60 ('<'), 62 ('>'), 92
chars 0-31, 32 (space), 34 ('"'), 60 ('<'), 62 ('>'), 92 ('\'), 94 ('^'), ('\'), 94 ('^'), 96 ('`'), 123 ('{'), 124 ('|'), 125 ('}'), 127 (delete)
96 ('`'), 123 ('{'), 124 ('|'), 125 ('}'), 127 (delete) and anything and anything above are normally not allowed. But here, in H1 only,
above are normally not allowed. In H1, all character between (0..32) and HAProxy will only block a number of them (0..32, 127);
127 will always be blocked. All characters above 127 (excluded) will also
be blocked, except when this option is enabled. Other characters
(33..126) will not be checked at all.
* In H1 and H2, URLs containing fragment references ('#' after the path) * In H1 and H2, URLs containing fragment references ('#' after the path)
will be accepted; will be accepted;

View File

@ -282,92 +282,6 @@ static inline void applet_expect_data(struct appctx *appctx)
se_fl_clr(appctx->sedesc, SE_FL_EXP_NO_DATA); se_fl_clr(appctx->sedesc, SE_FL_EXP_NO_DATA);
} }
/* Returns the buffer containing data pushed to the applet by the stream. For
* applets using its own buffers it is the appctx input buffer. For legacy
* applet, it is the output channel buffer.
*/
static inline struct buffer *applet_get_inbuf(struct appctx *appctx)
{
if (appctx->flags & APPCTX_FL_INOUT_BUFS)
return &appctx->inbuf;
else
return sc_ob(appctx_sc(appctx));
}
/* Returns the buffer containing data pushed by the applets to the stream. For
* applets using its own buffer it is the appctx output buffer. For legacy
* applet, it is the input channel buffer.
*/
static inline struct buffer *applet_get_outbuf(struct appctx *appctx)
{
if (appctx->flags & APPCTX_FL_INOUT_BUFS)
return &appctx->outbuf;
else
return sc_ib(appctx_sc(appctx));
}
/* Returns the amount of data in the input buffer (see applet_get_inbuf) */
static inline size_t applet_input_data(const struct appctx *appctx)
{
if (appctx->flags & APPCTX_FL_INOUT_BUFS)
return b_data(&appctx->inbuf);
else
return co_data(sc_oc(appctx_sc(appctx)));
}
/* Skips <len> bytes from the input buffer (see applet_get_inbuf).
*
* This is useful when data have been read directly from the buffer. It is
* illegal to call this function with <len> causing a wrapping at the end of the
* buffer. It's the caller's responsibility to ensure that <len> is never larger
* than available ouput data.
*/
static inline void applet_skip_input(struct appctx *appctx, size_t len)
{
if (appctx->flags & APPCTX_FL_INOUT_BUFS)
b_del(&appctx->inbuf, len);
else
co_skip(sc_oc(appctx_sc(appctx)), len);
}
/* Removes all bytes from the input buffer (see applet_get_inbuf).
*/
static inline void applet_reset_input(struct appctx *appctx)
{
if (appctx->flags & APPCTX_FL_INOUT_BUFS) {
b_reset(&appctx->inbuf);
applet_fl_clr(appctx, APPCTX_FL_INBLK_FULL);
}
else
co_skip(sc_oc(appctx_sc(appctx)), co_data(sc_oc(appctx_sc(appctx))));
}
/* Returns the amout of space available at the output buffer (see applet_get_outbuf).
*/
static inline size_t applet_output_room(const struct appctx *appctx)
{
if (appctx->flags & APPCTX_FL_INOUT_BUFS)
return b_room(&appctx->outbuf);
else
return channel_recv_max(sc_ic(appctx_sc(appctx)));
}
/*Indicates that the applet have more data to deliver and it needs more room in
* the output buffer to do so (see applet_get_outbuf).
*
* For applets using its own buffers, <room_needed> is not used and only
* <appctx> flags are updated. For legacy applets, the amount of free space
* required must be specified. In this last case, it is the caller
* responsibility to be sure <room_needed> is valid.
*/
static inline void applet_need_room(struct appctx *appctx, size_t room_needed)
{
if (appctx->flags & APPCTX_FL_INOUT_BUFS)
applet_have_more_data(appctx);
else
sc_need_room(appctx_sc(appctx), room_needed);
}
/* Should only be used via wrappers applet_putchk() / applet_putchk_stress(). */ /* Should only be used via wrappers applet_putchk() / applet_putchk_stress(). */
static inline int _applet_putchk(struct appctx *appctx, struct buffer *chunk, static inline int _applet_putchk(struct appctx *appctx, struct buffer *chunk,
int stress) int stress)
@ -404,7 +318,8 @@ static inline int _applet_putchk(struct appctx *appctx, struct buffer *chunk,
return ret; return ret;
} }
/* writes chunk <chunk> into the applet output buffer (see applet_get_outbuf). /* writes chunk <chunk> into the applet's output buffer if it uses its own
* bufferx or into the input channel of the stream attached to this applet.
* *
* Returns the number of written bytes on success or -1 on error (lake of space, * Returns the number of written bytes on success or -1 on error (lake of space,
* shutdown, invalid call...) * shutdown, invalid call...)
@ -420,7 +335,8 @@ static inline int applet_putchk_stress(struct appctx *appctx, struct buffer *chu
return _applet_putchk(appctx, chunk, 1); return _applet_putchk(appctx, chunk, 1);
} }
/* writes <len> chars from <blk> into the applet output buffer (see applet_get_outbuf). /* writes <len> chars from <blk> into the applet's output buffer if it uses its own
* bufferx or into the input channel of the stream attached to this applet.
* *
* Returns the number of written bytes on success or -1 on error (lake of space, * Returns the number of written bytes on success or -1 on error (lake of space,
* shutdown, invalid call...) * shutdown, invalid call...)
@ -455,8 +371,9 @@ static inline int applet_putblk(struct appctx *appctx, const char *blk, int len)
return ret; return ret;
} }
/* writes chars from <str> up to the trailing zero (excluded) into the applet /* writes chars from <str> up to the trailing zero (excluded) into the applet's
* output buffer (see applet_get_outbuf). * output buffer if it uses its own bufferx or into the input channel of the
* stream attached to this applet.
* *
* Returns the number of written bytes on success or -1 on error (lake of space, * Returns the number of written bytes on success or -1 on error (lake of space,
* shutdown, invalid call...) * shutdown, invalid call...)
@ -492,7 +409,8 @@ static inline int applet_putstr(struct appctx *appctx, const char *str)
return ret; return ret;
} }
/* writes character <chr> into the applet's output buffer (see applet_get_outbuf). /* writes character <chr> into the applet's output buffer if it uses its own
* bufferx or into the input channel of the stream attached to this applet.
* *
* Returns the number of written bytes on success or -1 on error (lake of space, * Returns the number of written bytes on success or -1 on error (lake of space,
* shutdown, invalid call...) * shutdown, invalid call...)
@ -528,283 +446,6 @@ static inline int applet_putchr(struct appctx *appctx, char chr)
return ret; return ret;
} }
static inline int applet_may_get(const struct appctx *appctx, size_t len)
{
if (appctx->flags & APPCTX_FL_INOUT_BUFS) {
if (len > b_data(&appctx->inbuf)) {
if (se_fl_test(appctx->sedesc, SE_FL_SHW))
return -1;
return 0;
}
}
else {
const struct stconn *sc = appctx_sc(appctx);
if ((sc->flags & SC_FL_SHUT_DONE) || len > co_data(sc_oc(sc))) {
if (sc->flags & (SC_FL_SHUT_DONE|SC_FL_SHUT_WANTED))
return -1;
return 0;
}
}
return 1;
}
/* Gets one char from the applet input buffer (see appet_get_inbuf),
*
* Return values :
* 1 : number of bytes read, equal to requested size.
* =0 : not enough data available. <c> is left undefined.
* <0 : no more bytes readable because output is shut.
*
* The status of the corresponding buffer is not changed. The caller must call
* applet_skip_input() to update it.
*/
static inline int applet_getchar(const struct appctx *appctx, char *c)
{
int ret;
ret = applet_may_get(appctx, 1);
if (ret <= 0)
return ret;
*c = ((appctx->flags & APPCTX_FL_INOUT_BUFS)
? *(b_head(&appctx->inbuf))
: *(co_head(sc_oc(appctx_sc(appctx)))));
return 1;
}
/* Copies one full block of data from the applet input buffer (see
* appet_get_inbuf).
*
* <len> bytes are capied, starting at the offset <offset>.
*
* Return values :
* >0 : number of bytes read, equal to requested size.
* =0 : not enough data available. <blk> is left undefined.
* <0 : no more bytes readable because output is shut.
*
* The status of the corresponding buffer is not changed. The caller must call
* applet_skip_input() to update it.
*/
static inline int applet_getblk(const struct appctx *appctx, char *blk, int len, int offset)
{
const struct buffer *buf;
int ret;
ret = applet_may_get(appctx, len+offset);
if (ret <= 0)
return ret;
buf = ((appctx->flags & APPCTX_FL_INOUT_BUFS)
? &appctx->inbuf
: sc_ob(appctx_sc(appctx)));
return b_getblk(buf, blk, len, offset);
}
/* Gets one text block representing a word from the applet input buffer (see
* appet_get_inbuf).
*
* The separator is waited for as long as some data can still be received and the
* destination is not full. Otherwise, the string may be returned as is, without
* the separator.
*
* Return values :
* >0 : number of bytes read. Includes the separator if present before len or end.
* =0 : no separator before end found. <str> is left undefined.
* <0 : no more bytes readable because output is shut.
*
* The status of the corresponding buffer is not changed. The caller must call
* applet_skip_input() to update it.
*/
static inline int applet_getword(const struct appctx *appctx, char *str, int len, char sep)
{
const struct buffer *buf;
char *p;
size_t input, max = len;
int ret = 0;
ret = applet_may_get(appctx, 1);
if (ret <= 0)
goto out;
if (appctx->flags & APPCTX_FL_INOUT_BUFS) {
buf = &appctx->inbuf;
input = b_data(buf);
}
else {
struct stconn *sc = appctx_sc(appctx);
buf = sc_ob(sc);
input = co_data(sc_oc(sc));
}
if (max > input) {
max = input;
str[max-1] = 0;
}
p = b_head(buf);
while (max) {
*str++ = *p;
ret++;
max--;
if (*p == sep)
goto out;
p = b_next(buf, p);
}
if (appctx->flags & APPCTX_FL_INOUT_BUFS) {
if (ret < len && (ret < input || b_room(buf)) &&
!se_fl_test(appctx->sedesc, SE_FL_SHW))
ret = 0;
}
else {
struct stconn *sc = appctx_sc(appctx);
if (ret < len && (ret < input || channel_may_recv(sc_oc(sc))) &&
!(sc->flags & (SC_FL_SHUT_DONE|SC_FL_SHUT_WANTED)))
ret = 0;
}
out:
if (max)
*str = 0;
return ret;
}
/* Gets one text block representing a line from the applet input buffer (see
* appet_get_inbuf).
*
* The '\n' is waited for as long as some data can still be received and the
* destination is not full. Otherwise, the string may be returned as is, without
* the '\n'.
*
* Return values :
* >0 : number of bytes read. Includes the \n if present before len or end.
* =0 : no '\n' before end found. <str> is left undefined.
* <0 : no more bytes readable because output is shut.
*
* The status of the corresponding buffer is not changed. The caller must call
* applet_skip_input() to update it.
*/
static inline int applet_getline(const struct appctx *appctx, char *str, int len)
{
return applet_getword(appctx, str, len, '\n');
}
/* Gets one or two blocks of data at once from the applet input buffer (see appet_get_inbuf),
*
* Data are not copied.
*
* Return values :
* >0 : number of blocks filled (1 or 2). blk1 is always filled before blk2.
* =0 : not enough data available. <blk*> are left undefined.
* <0 : no more bytes readable because output is shut.
*
* The status of the corresponding buffer is not changed. The caller must call
* applet_skip_input() to update it.
*/
static inline int applet_getblk_nc(const struct appctx *appctx, const char **blk1, size_t *len1, const char **blk2, size_t *len2)
{
const struct buffer *buf;
size_t max;
int ret;
ret = applet_may_get(appctx, 1);
if (ret <= 0)
return ret;
if (appctx->flags & APPCTX_FL_INOUT_BUFS) {
buf = &appctx->inbuf;
max = b_data(buf);
}
else {
struct stconn *sc = appctx_sc(appctx);
buf = sc_ob(sc);
max = co_data(sc_oc(sc));
}
return b_getblk_nc(buf, blk1, len1, blk2, len2, 0, max);
}
/* Gets one or two blocks of text representing a word from the applet input
* buffer (see appet_get_inbuf).
*
* Data are not copied. The separator is waited for as long as some data can
* still be received and the destination is not full. Otherwise, the string may
* be returned as is, without the separator.
*
* Return values :
* >0 : number of bytes read. Includes the separator if present before len or end.
* =0 : no separator before end found. <str> is left undefined.
* <0 : no more bytes readable because output is shut.
*
* The status of the corresponding buffer is not changed. The caller must call
* applet_skip_input() to update it.
*/
static inline int applet_getword_nc(const struct appctx *appctx, const char **blk1, size_t *len1, const char **blk2, size_t *len2, char sep)
{
int ret;
size_t l;
ret = applet_getblk_nc(appctx, blk1, len1, blk2, len2);
if (unlikely(ret <= 0))
return ret;
for (l = 0; l < *len1 && (*blk1)[l] != sep; l++);
if (l < *len1 && (*blk1)[l] == sep) {
*len1 = l + 1;
return 1;
}
if (ret >= 2) {
for (l = 0; l < *len2 && (*blk2)[l] != sep; l++);
if (l < *len2 && (*blk2)[l] == sep) {
*len2 = l + 1;
return 2;
}
}
/* If we have found no LF and the buffer is full or the SC is shut, then
* the resulting string is made of the concatenation of the pending
* blocks (1 or 2).
*/
if (appctx->flags & APPCTX_FL_INOUT_BUFS) {
if (b_full(&appctx->inbuf) || se_fl_test(appctx->sedesc, SE_FL_SHW))
return ret;
}
else {
struct stconn *sc = appctx_sc(appctx);
if (!channel_may_recv(sc_oc(sc)) || sc->flags & (SC_FL_SHUT_DONE|SC_FL_SHUT_WANTED))
return ret;
}
/* No LF yet and not shut yet */
return 0;
}
/* Gets one or two blocks of text representing a line from the applet input
* buffer (see appet_get_inbuf).
*
* Data are not copied. The '\n' is waited for as long as some data can still be
* received and the destination is not full. Otherwise, the string may be
* returned as is, without the '\n'.
*
* Return values :
* >0 : number of bytes read. Includes the \n if present before len or end.
* =0 : no '\n' before end found. <str> is left undefined.
* <0 : no more bytes readable because output is shut.
*
* The status of the corresponding buffer is not changed. The caller must call
* applet_skip_input() to update it.
*/
static inline int applet_getline_nc(const struct appctx *appctx, const char **blk1, size_t *len1, const char **blk2, size_t *len2)
{
return applet_getword_nc(appctx, blk1, len1, blk2, len2, '\n');
}
#endif /* _HAPROXY_APPLET_H */ #endif /* _HAPROXY_APPLET_H */
/* /*

View File

@ -499,7 +499,6 @@ static inline long fd_clr_running(int fd)
static inline void fd_insert(int fd, void *owner, void (*iocb)(int fd), int tgid, unsigned long thread_mask) static inline void fd_insert(int fd, void *owner, void (*iocb)(int fd), int tgid, unsigned long thread_mask)
{ {
extern void sock_conn_iocb(int); extern void sock_conn_iocb(int);
struct tgroup_info *tginfo = &ha_tgroup_info[tgid - 1];
int newstate; int newstate;
/* conn_fd_handler should support edge-triggered FDs */ /* conn_fd_handler should support edge-triggered FDs */
@ -529,7 +528,7 @@ static inline void fd_insert(int fd, void *owner, void (*iocb)(int fd), int tgid
BUG_ON(fdtab[fd].state != 0); BUG_ON(fdtab[fd].state != 0);
BUG_ON(tgid < 1 || tgid > MAX_TGROUPS); BUG_ON(tgid < 1 || tgid > MAX_TGROUPS);
thread_mask &= tginfo->threads_enabled; thread_mask &= tg->threads_enabled;
BUG_ON(thread_mask == 0); BUG_ON(thread_mask == 0);
fd_claim_tgid(fd, tgid); fd_claim_tgid(fd, tgid);

View File

@ -144,6 +144,7 @@ struct qc_stream_rxbuf {
struct qcs { struct qcs {
struct qcc *qcc; struct qcc *qcc;
struct sedesc *sd; struct sedesc *sd;
struct session *sess;
uint32_t flags; /* QC_SF_* */ uint32_t flags; /* QC_SF_* */
enum qcs_state st; /* QC_SS_* state */ enum qcs_state st; /* QC_SS_* state */
void *ctx; /* app-ops context */ void *ctx; /* app-ops context */
@ -202,7 +203,7 @@ struct qcc_app_ops {
/* Initialize <qcs> stream app context or leave it to NULL if rejected. */ /* Initialize <qcs> stream app context or leave it to NULL if rejected. */
int (*attach)(struct qcs *qcs, void *conn_ctx); int (*attach)(struct qcs *qcs, void *conn_ctx);
/* Convert received HTTP payload to HTX. Returns amount of decoded bytes from <b> or a negative error code. */ /* Convert received HTTP payload to HTX. */
ssize_t (*rcv_buf)(struct qcs *qcs, struct buffer *b, int fin); ssize_t (*rcv_buf)(struct qcs *qcs, struct buffer *b, int fin);
/* Convert HTX to HTTP payload for sending. */ /* Convert HTX to HTTP payload for sending. */
@ -232,7 +233,7 @@ struct qcc_app_ops {
#define QC_CF_ERRL 0x00000001 /* fatal error detected locally, connection should be closed soon */ #define QC_CF_ERRL 0x00000001 /* fatal error detected locally, connection should be closed soon */
#define QC_CF_ERRL_DONE 0x00000002 /* local error properly handled, connection can be released */ #define QC_CF_ERRL_DONE 0x00000002 /* local error properly handled, connection can be released */
#define QC_CF_IS_BACK 0x00000004 /* backend side */ /* unused 0x00000004 */
#define QC_CF_CONN_FULL 0x00000008 /* no stream buffers available on connection */ #define QC_CF_CONN_FULL 0x00000008 /* no stream buffers available on connection */
/* unused 0x00000010 */ /* unused 0x00000010 */
#define QC_CF_ERR_CONN 0x00000020 /* fatal error reported by transport layer */ #define QC_CF_ERR_CONN 0x00000020 /* fatal error reported by transport layer */

View File

@ -2020,13 +2020,9 @@ int connect_server(struct stream *s)
srv_conn->ctx = s->scb; srv_conn->ctx = s->scb;
#if defined(USE_OPENSSL) && defined(TLSEXT_TYPE_application_layer_protocol_negotiation) #if defined(USE_OPENSSL) && defined(TLSEXT_TYPE_application_layer_protocol_negotiation)
/* Delay mux initialization if SSL and ALPN/NPN is set. Note
* that this is skipped in TCP mode as we only want mux-pt
* anyway.
*/
if (!srv || if (!srv ||
(srv->use_ssl != 1 || (!(srv->ssl_ctx.alpn_str) && !(srv->ssl_ctx.npn_str)) || (srv->use_ssl != 1 || (!(srv->ssl_ctx.alpn_str) && !(srv->ssl_ctx.npn_str)) ||
!IS_HTX_STRM(s))) srv->mux_proto || !IS_HTX_STRM(s)))
#endif #endif
init_mux = 1; init_mux = 1;

View File

@ -4115,13 +4115,6 @@ out_uri_auth_compat:
int mode = conn_pr_mode_to_proto_mode(curproxy->mode); int mode = conn_pr_mode_to_proto_mode(curproxy->mode);
const struct mux_proto_list *mux_ent; const struct mux_proto_list *mux_ent;
if (srv_is_quic(newsrv)) {
if (!newsrv->mux_proto) {
/* Force QUIC as mux-proto on server with quic addresses, similarly to bind on FE side. */
newsrv->mux_proto = get_mux_proto(ist("quic"));
}
}
if (!newsrv->mux_proto) if (!newsrv->mux_proto)
continue; continue;

View File

@ -1385,13 +1385,9 @@ static ssize_t h3_parse_settings_frm(struct h3c *h3c, const struct buffer *buf,
/* Transcode HTTP/3 payload received in buffer <b> to HTX data for stream /* Transcode HTTP/3 payload received in buffer <b> to HTX data for stream
* <qcs>. If <fin> is set, it indicates that no more data will arrive after. * <qcs>. If <fin> is set, it indicates that no more data will arrive after.
* *
* It may be necessary to call this function with an empty input buffer to * Returns the count of consumed bytes or a negative error code. If 0 is
* signal a standalone FIN. * returned, stream data is incomplete, decoding should be call again later
* * with more content.
* Returns the amount of decoded bytes from <b> or a negative error code. A
* null value can be returned even if input buffer is not empty, meaning that
* transcoding cannot be performed due to partial HTTP/3 content. Receive
* operation may be called later with newer data available.
*/ */
static ssize_t h3_rcv_buf(struct qcs *qcs, struct buffer *b, int fin) static ssize_t h3_rcv_buf(struct qcs *qcs, struct buffer *b, int fin)
{ {
@ -1691,13 +1687,6 @@ static int h3_encode_header(struct buffer *buf,
return qpack_encode_header(buf, n, v_strip); return qpack_encode_header(buf, n, v_strip);
} }
/* Convert a HTX status-line and associated headers stored into <htx> into a
* HTTP/3 HEADERS response frame. HTX blocks are removed up to end-of-trailer
* included.
*
* Returns the amount of consumed bytes from <htx> buffer or a negative error
* code.
*/
static int h3_resp_headers_send(struct qcs *qcs, struct htx *htx) static int h3_resp_headers_send(struct qcs *qcs, struct htx *htx)
{ {
int err; int err;
@ -1852,15 +1841,15 @@ static int h3_resp_headers_send(struct qcs *qcs, struct htx *htx)
} }
/* Convert a series of HTX trailer blocks from <htx> buffer into <qcs> buffer /* Convert a series of HTX trailer blocks from <htx> buffer into <qcs> buffer
* as a HTTP/3 HEADERS frame. Forbidden trailers are skipped. HTX trailer * as a H3 HEADERS frame. H3 forbidden trailers are skipped. HTX trailer blocks
* blocks are removed from <htx> up to end-of-trailer included. * are removed from <htx> until EOT is found and itself removed.
* *
* If only a EOT HTX block is present without trailer, no H3 frame is produced. * If only a EOT HTX block is present without trailer, no H3 frame is produced.
* Caller is responsible to emit an empty QUIC STREAM frame to signal the end * Caller is responsible to emit an empty QUIC STREAM frame to signal the end
* of the stream. * of the stream.
* *
* Returns the amount of consumed bytes from <htx> buffer or a negative error * Returns the size of HTX blocks removed. A negative error code is returned in
* code. * case of a fatal error which should caused a connection closure.
*/ */
static int h3_resp_trailers_send(struct qcs *qcs, struct htx *htx) static int h3_resp_trailers_send(struct qcs *qcs, struct htx *htx)
{ {
@ -2032,9 +2021,9 @@ static int h3_resp_trailers_send(struct qcs *qcs, struct htx *htx)
* underlying HTX area via <buf> as this will be used if zero-copy can be * underlying HTX area via <buf> as this will be used if zero-copy can be
* performed. * performed.
* *
* Returns the amount of consumed bytes from <htx> buffer, which corresponds to * Returns the total bytes of encoded HTTP/3 payload. This corresponds to the
* the length sum of encoded frames payload. A negative error code is returned * total bytes of HTX block removed. A negative error code is returned in case
* in case of a fatal error which should caused a connection closure. * of a fatal error which should caused a connection closure.
*/ */
static int h3_resp_data_send(struct qcs *qcs, struct htx *htx, static int h3_resp_data_send(struct qcs *qcs, struct htx *htx,
struct buffer *buf, size_t count) struct buffer *buf, size_t count)
@ -2159,14 +2148,6 @@ static int h3_resp_data_send(struct qcs *qcs, struct htx *htx,
return -1; return -1;
} }
/* Transcode HTX data from <buf> of length <count> into HTTP/3 frame for <qcs>
* stream instance. Successfully transcoded HTX blocks are removed from input
* buffer.
*
* Returns the amount of consumed bytes from <buf>. In case of error,
* connection is flagged and transcoding is interrupted. The returned value is
* unchanged though.
*/
static size_t h3_snd_buf(struct qcs *qcs, struct buffer *buf, size_t count) static size_t h3_snd_buf(struct qcs *qcs, struct buffer *buf, size_t count)
{ {
size_t total = 0; size_t total = 0;

View File

@ -5299,20 +5299,48 @@ __LJMP static int hlua_applet_tcp_get_priv(lua_State *L)
__LJMP static int hlua_applet_tcp_getline_yield(lua_State *L, int status, lua_KContext ctx) __LJMP static int hlua_applet_tcp_getline_yield(lua_State *L, int status, lua_KContext ctx)
{ {
struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1)); struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1));
struct stconn *sc = appctx_sc(luactx->appctx);
int ret; int ret;
const char *blk1 = NULL; const char *blk1;
size_t len1 = 0; size_t len1;
const char *blk2 = NULL; const char *blk2;
size_t len2 = 0; size_t len2;
/* Read the maximum amount of data available. */ /* Read the maximum amount of data available. */
ret = applet_getline_nc(luactx->appctx, &blk1, &len1, &blk2, &len2); if (luactx->appctx->flags & APPCTX_FL_INOUT_BUFS) {
size_t l;
int line_found = 0;
ret = b_getblk_nc(&luactx->appctx->inbuf, &blk1, &len1, &blk2, &len2, 0, b_data(&luactx->appctx->inbuf));
if (ret == 0 && se_fl_test(luactx->appctx->sedesc, SE_FL_SHW))
ret = -1;
if (ret >= 1) {
for (l = 0; l < len1 && blk1[l] != '\n'; l++);
if (l < len1 && blk1[l] == '\n') {
len1 = l + 1;
line_found = 1;
}
}
if (!line_found && ret >= 2) {
for (l = 0; l < len2 && blk2[l] != '\n'; l++);
if (l < len2 && blk2[l] == '\n') {
len2 = l + 1;
line_found = 1;
}
}
if (!line_found && !se_fl_test(luactx->appctx->sedesc, SE_FL_SHW))
ret = 0;
}
else
ret = co_getline_nc(sc_oc(sc), &blk1, &len1, &blk2, &len2);
/* Data not yet available. return yield. */ /* Data not yet available. return yield. */
if (ret == 0) { if (ret == 0) {
applet_need_more_data(luactx->appctx); applet_need_more_data(luactx->appctx);
MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_tcp_getline_yield, TICK_ETERNITY, 0)); MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_tcp_getline_yield, TICK_ETERNITY, 0));
return 0;
} }
/* End of data: commit the total strings and return. */ /* End of data: commit the total strings and return. */
@ -5327,10 +5355,13 @@ __LJMP static int hlua_applet_tcp_getline_yield(lua_State *L, int status, lua_KC
/* don't check the max length read and don't check. */ /* don't check the max length read and don't check. */
luaL_addlstring(&luactx->b, blk1, len1); luaL_addlstring(&luactx->b, blk1, len1);
if (len2) luaL_addlstring(&luactx->b, blk2, len2);
luaL_addlstring(&luactx->b, blk2, len2);
if (luactx->appctx->flags & APPCTX_FL_INOUT_BUFS)
b_del(&luactx->appctx->inbuf, len1 + len2);
else
co_skip(sc_oc(sc), len1 + len2);
applet_skip_input(luactx->appctx, len1+len2);
luaL_pushresult(&luactx->b); luaL_pushresult(&luactx->b);
return 1; return 1;
} }
@ -5352,16 +5383,23 @@ __LJMP static int hlua_applet_tcp_getline(lua_State *L)
__LJMP static int hlua_applet_tcp_recv_try(lua_State *L) __LJMP static int hlua_applet_tcp_recv_try(lua_State *L)
{ {
struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1)); struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1));
struct stconn *sc = appctx_sc(luactx->appctx);
size_t len = MAY_LJMP(luaL_checkinteger(L, 2)); size_t len = MAY_LJMP(luaL_checkinteger(L, 2));
int exp_date = MAY_LJMP(luaL_checkinteger(L, 3)); int exp_date = MAY_LJMP(luaL_checkinteger(L, 3));
int ret; int ret;
const char *blk1 = NULL; const char *blk1;
size_t len1 = 0; size_t len1;
const char *blk2 = NULL; const char *blk2;
size_t len2 = 0; size_t len2;
/* Read the maximum amount of data available. */ /* Read the maximum amount of data available. */
ret = applet_getblk_nc(luactx->appctx, &blk1, &len1, &blk2, &len2); if (luactx->appctx->flags & APPCTX_FL_INOUT_BUFS) {
ret = b_getblk_nc(&luactx->appctx->inbuf, &blk1, &len1, &blk2, &len2, 0, b_data(&luactx->appctx->inbuf));
if (ret == 0 && se_fl_test(luactx->appctx->sedesc, SE_FL_SHW))
ret = -1;
}
else
ret = co_getblk_nc(sc_oc(sc), &blk1, &len1, &blk2, &len2);
/* Data not yet available. return yield. */ /* Data not yet available. return yield. */
if (ret == 0) { if (ret == 0) {
@ -5392,9 +5430,11 @@ __LJMP static int hlua_applet_tcp_recv_try(lua_State *L)
* the end of data stream. * the end of data stream.
*/ */
luaL_addlstring(&luactx->b, blk1, len1); luaL_addlstring(&luactx->b, blk1, len1);
if (len2) luaL_addlstring(&luactx->b, blk2, len2);
luaL_addlstring(&luactx->b, blk2, len2); if (luactx->appctx->flags & APPCTX_FL_INOUT_BUFS)
applet_skip_input(luactx->appctx, len1+len2); b_del(&luactx->appctx->inbuf, len1 + len2);
else
co_skip(sc_oc(sc), len1 + len2);
if (tick_is_expired(exp_date, now_ms)) { if (tick_is_expired(exp_date, now_ms)) {
/* return the result. */ /* return the result. */
@ -5414,14 +5454,15 @@ __LJMP static int hlua_applet_tcp_recv_try(lua_State *L)
len -= len1; len -= len1;
/* Copy the second block. */ /* Copy the second block. */
if (len2) { if (len2 > len)
if (len2 > len) len2 = len;
len2 = len; luaL_addlstring(&luactx->b, blk2, len2);
luaL_addlstring(&luactx->b, blk2, len2); len -= len2;
len -= len2;
}
applet_skip_input(luactx->appctx, len1+len2); if (luactx->appctx->flags & APPCTX_FL_INOUT_BUFS)
b_del(&luactx->appctx->inbuf, len1 + len2);
else
co_skip(sc_oc(sc), len1 + len2);
/* If there is no other data available, yield waiting for new data. */ /* If there is no other data available, yield waiting for new data. */
if (len > 0 && !tick_is_expired(exp_date, now_ms)) { if (len > 0 && !tick_is_expired(exp_date, now_ms)) {
@ -5530,10 +5571,15 @@ __LJMP static int hlua_applet_tcp_send_yield(lua_State *L, int status, lua_KCont
struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1)); struct hlua_appctx *luactx = MAY_LJMP(hlua_checkapplet_tcp(L, 1));
const char *str = MAY_LJMP(luaL_checklstring(L, 2, &len)); const char *str = MAY_LJMP(luaL_checklstring(L, 2, &len));
int l = MAY_LJMP(luaL_checkinteger(L, 3)); int l = MAY_LJMP(luaL_checkinteger(L, 3));
struct stconn *sc = appctx_sc(luactx->appctx);
struct channel *chn = sc_ic(sc);
int max; int max;
/* Get the max amount of data which can be written */ /* Get the max amount of data which can be written */
max = applet_output_room(luactx->appctx); if (luactx->appctx->flags & APPCTX_FL_INOUT_BUFS)
max = b_room(&luactx->appctx->outbuf);
else
max = channel_recv_max(chn);
if (max > (len - l)) if (max > (len - l))
max = len - l; max = len - l;
@ -5550,7 +5596,10 @@ __LJMP static int hlua_applet_tcp_send_yield(lua_State *L, int status, lua_KCont
* applet, and returns a yield. * applet, and returns a yield.
*/ */
if (l < len) { if (l < len) {
applet_need_room(luactx->appctx, applet_output_room(luactx->appctx) + 1); if (luactx->appctx->flags & APPCTX_FL_INOUT_BUFS)
applet_have_more_data(luactx->appctx);
else
sc_need_room(sc, channel_recv_max(chn) + 1);
MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_tcp_send_yield, TICK_ETERNITY, 0)); MAY_LJMP(hlua_yieldk(L, 0, 0, hlua_applet_tcp_send_yield, TICK_ETERNITY, 0));
} }
@ -11212,7 +11261,10 @@ out:
if (yield) if (yield)
return; return;
applet_reset_input(ctx); if (ctx->flags & APPCTX_FL_INOUT_BUFS)
b_reset(&ctx->inbuf);
else
co_skip(sc_oc(sc), co_data(sc_oc(sc)));
return; return;
error: error:

View File

@ -6,15 +6,13 @@
#include <haproxy/dynbuf.h> #include <haproxy/dynbuf.h>
#include <haproxy/htx.h> #include <haproxy/htx.h>
#include <haproxy/http.h> #include <haproxy/http.h>
#include <haproxy/istbuf.h>
#include <haproxy/mux_quic.h> #include <haproxy/mux_quic.h>
#include <haproxy/qmux_http.h> #include <haproxy/qmux_http.h>
#include <haproxy/qmux_trace.h> #include <haproxy/qmux_trace.h>
#include <haproxy/quic_utils.h> #include <haproxy/quic_utils.h>
#include <haproxy/trace.h> #include <haproxy/trace.h>
/* HTTP/0.9 request -> HTX. */ static ssize_t hq_interop_rcv_buf(struct qcs *qcs, struct buffer *b, int fin)
static ssize_t hq_interop_rcv_buf_req(struct qcs *qcs, struct buffer *b, int fin)
{ {
struct htx *htx; struct htx *htx;
struct htx_sl *sl; struct htx_sl *sl;
@ -94,74 +92,12 @@ static ssize_t hq_interop_rcv_buf_req(struct qcs *qcs, struct buffer *b, int fin
return b_data(b); return b_data(b);
} }
/* HTTP/0.9 response -> HTX. */
static ssize_t hq_interop_rcv_buf_res(struct qcs *qcs, struct buffer *b, int fin)
{
ssize_t ret = 0;
struct htx *htx;
struct htx_sl *sl;
struct buffer *htx_buf;
const struct stream *strm = __sc_strm(qcs->sd->sc);
const unsigned int flags = HTX_SL_F_VER_11|HTX_SL_F_XFER_LEN;
size_t htx_sent;
htx_buf = qcc_get_stream_rxbuf(qcs);
BUG_ON(!htx_buf);
htx = htx_from_buf(htx_buf);
if (htx_is_empty(htx) && !strm->res.total) {
/* First data transfer, add HTX response start-line first. */
sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags,
ist("HTTP/1.0"), ist("200"), ist(""));
BUG_ON(!sl);
if (fin && !b_data(b))
sl->flags |= HTX_SL_F_BODYLESS;
htx_add_endof(htx, HTX_BLK_EOH);
}
if (!b_data(b)) {
if (fin && quic_stream_is_bidi(qcs->id)) {
if (qcs_http_handle_standalone_fin(qcs)) {
htx_to_buf(htx, htx_buf);
return -1;
}
}
}
else {
BUG_ON(b_data(b) > htx_free_data_space(htx)); /* TODO */
BUG_ON(b_head(b) + b_data(b) > b_wrap(b)); /* TODO */
htx_sent = htx_add_data(htx, ist2(b_head(b), b_data(b)));
BUG_ON(htx_sent < b_data(b)); /* TODO */
ret = htx_sent;
if (fin && b_data(b) == htx_sent)
htx->flags |= HTX_FL_EOM;
}
htx_to_buf(htx, htx_buf);
return ret;
}
/* Returns the amount of decoded bytes from <b> or a negative error code. */
static ssize_t hq_interop_rcv_buf(struct qcs *qcs, struct buffer *b, int fin)
{
/* hq-interop parser does not support buffer wrapping. */
BUG_ON(b_data(b) != b_contig_data(b, 0));
return !(qcs->qcc->flags & QC_CF_IS_BACK) ?
hq_interop_rcv_buf_req(qcs, b, fin) :
hq_interop_rcv_buf_res(qcs, b, fin);
}
/* Returns the amount of consumed bytes from <buf>. */
static size_t hq_interop_snd_buf(struct qcs *qcs, struct buffer *buf, static size_t hq_interop_snd_buf(struct qcs *qcs, struct buffer *buf,
size_t count) size_t count)
{ {
enum htx_blk_type btype; enum htx_blk_type btype;
struct htx *htx = NULL; struct htx *htx = NULL;
struct htx_blk *blk; struct htx_blk *blk;
struct htx_sl *sl = NULL;
int32_t idx; int32_t idx;
uint32_t bsize, fsize; uint32_t bsize, fsize;
struct buffer *res = NULL; struct buffer *res = NULL;
@ -177,25 +113,9 @@ static size_t hq_interop_snd_buf(struct qcs *qcs, struct buffer *buf,
btype = htx_get_blk_type(blk); btype = htx_get_blk_type(blk);
fsize = bsize = htx_get_blksz(blk); fsize = bsize = htx_get_blksz(blk);
BUG_ON(btype == HTX_BLK_REQ_SL);
switch (btype) { switch (btype) {
case HTX_BLK_REQ_SL:
res = qcc_get_stream_txbuf(qcs, &err, 0);
if (!res) {
BUG_ON(err); /* TODO */
goto end;
}
BUG_ON_HOT(sl); /* Only one start-line expected */
sl = htx_get_blk_ptr(htx, blk);
/* Only GET supported for HTTP/0.9. */
b_putist(res, ist("GET "));
b_putist(res, htx_sl_req_uri(sl));
b_putist(res, ist(" HTTP/0.9\r\n"));
htx_remove_blk(htx, blk);
total += fsize;
break;
case HTX_BLK_DATA: case HTX_BLK_DATA:
res = qcc_get_stream_txbuf(qcs, &err, 0); res = qcc_get_stream_txbuf(qcs, &err, 0);
if (!res) { if (!res) {

View File

@ -1091,15 +1091,14 @@ void listener_accept(struct listener *l)
#endif #endif
if (p && p->fe_sps_lim) { if (p && p->fe_sps_lim) {
int max = 0; int max = 0;
int it;
for (it = 0; it < global.nbtgroups; it++) for (int it = 0; it < global.nbtgroups; it++)
max += freq_ctr_remain(&p->fe_counters.shared->tg[it]->sess_per_sec, p->fe_sps_lim, 0); max += freq_ctr_remain(&p->fe_counters.shared->tg[it]->sess_per_sec, p->fe_sps_lim, 0);
if (unlikely(!max)) { if (unlikely(!max)) {
unsigned int min_wait = 0; unsigned int min_wait = 0;
for (it = 0; it < global.nbtgroups; it++) { for (int it = 0; it < global.nbtgroups; it++) {
unsigned int cur_wait = next_event_delay(&p->fe_counters.shared->tg[it]->sess_per_sec, p->fe_sps_lim, 0); unsigned int cur_wait = next_event_delay(&p->fe_counters.shared->tg[it]->sess_per_sec, p->fe_sps_lim, 0);
if (!it || cur_wait < min_wait) if (!it || cur_wait < min_wait)
min_wait = cur_wait; min_wait = cur_wait;

View File

@ -135,6 +135,7 @@ static struct qcs *qcs_new(struct qcc *qcc, uint64_t id, enum qcs_type type)
qcs->stream = NULL; qcs->stream = NULL;
qcs->qcc = qcc; qcs->qcc = qcc;
qcs->sd = NULL; qcs->sd = NULL;
qcs->sess = NULL;
qcs->flags = QC_SF_NONE; qcs->flags = QC_SF_NONE;
qcs->st = QC_SS_IDLE; qcs->st = QC_SS_IDLE;
qcs->ctx = NULL; qcs->ctx = NULL;
@ -3115,10 +3116,10 @@ static void qcc_shutdown(struct qcc *qcc)
TRACE_LEAVE(QMUX_EV_QCC_END, qcc->conn); TRACE_LEAVE(QMUX_EV_QCC_END, qcc->conn);
} }
/* Loop through all qcs from <qcc> and wake their associated data layer if /* Loop through all qcs from <qcc>. Report error on stream endpoint if
* still active. Also report error on it if connection is already in error. * connection on error and wake them.
*/ */
static void qcc_wake_streams(struct qcc *qcc) static int qcc_wake_some_streams(struct qcc *qcc)
{ {
struct qcs *qcs; struct qcs *qcs;
struct eb64_node *node; struct eb64_node *node;
@ -3135,10 +3136,11 @@ static void qcc_wake_streams(struct qcc *qcc)
if (qcc->flags & (QC_CF_ERR_CONN|QC_CF_ERRL)) { if (qcc->flags & (QC_CF_ERR_CONN|QC_CF_ERRL)) {
TRACE_POINT(QMUX_EV_QCC_WAKE, qcc->conn, qcs); TRACE_POINT(QMUX_EV_QCC_WAKE, qcc->conn, qcs);
se_fl_set_error(qcs->sd); se_fl_set_error(qcs->sd);
qcs_alert(qcs);
} }
qcs_alert(qcs);
} }
return 0;
} }
/* Conduct operations which should be made for <qcc> connection after /* Conduct operations which should be made for <qcc> connection after
@ -3193,7 +3195,7 @@ static int qcc_io_process(struct qcc *qcc)
/* Report error if set on stream endpoint layer. */ /* Report error if set on stream endpoint layer. */
if (qcc->flags & (QC_CF_ERR_CONN|QC_CF_ERRL)) if (qcc->flags & (QC_CF_ERR_CONN|QC_CF_ERRL))
qcc_wake_streams(qcc); qcc_wake_some_streams(qcc);
out: out:
if (qcc_is_dead(qcc)) if (qcc_is_dead(qcc))
@ -3419,7 +3421,7 @@ static int qmux_init(struct connection *conn, struct proxy *prx,
_qcc_init(qcc); _qcc_init(qcc);
conn->ctx = qcc; conn->ctx = qcc;
qcc->nb_hreq = qcc->nb_sc = 0; qcc->nb_hreq = qcc->nb_sc = 0;
qcc->flags = conn_is_back(conn) ? QC_CF_IS_BACK : 0; qcc->flags = 0;
qcc->app_st = QCC_APP_ST_NULL; qcc->app_st = QCC_APP_ST_NULL;
qcc->glitches = 0; qcc->glitches = 0;
qcc->err = quic_err_transport(QC_ERR_NO_ERROR); qcc->err = quic_err_transport(QC_ERR_NO_ERROR);
@ -3532,34 +3534,10 @@ static int qmux_init(struct connection *conn, struct proxy *prx,
if (qcc->app_ops == &h3_ops && !conn_is_back(conn)) if (qcc->app_ops == &h3_ops && !conn_is_back(conn))
proxy_inc_fe_cum_sess_ver_ctr(sess->listener, prx, 3); proxy_inc_fe_cum_sess_ver_ctr(sess->listener, prx, 3);
if (!conn_is_back(conn)) { /* Register conn for idle front closing. This is done once everything is allocated. */
/* Register conn for idle front closing. */ if (!conn_is_back(conn))
LIST_APPEND(&mux_stopping_data[tid].list, &conn->stopping_list); LIST_APPEND(&mux_stopping_data[tid].list, &conn->stopping_list);
/* init read cycle */
tasklet_wakeup(qcc->wait_event.tasklet);
/* MUX is initialized before QUIC handshake completion if early data
* received. Flag connection to delay stream processing if
* wait-for-handshake is active.
*/
if (conn->handle.qc->state < QUIC_HS_ST_COMPLETE) {
if (!(conn->flags & CO_FL_EARLY_SSL_HS)) {
TRACE_STATE("flag connection with early data", QMUX_EV_QCC_WAKE, conn);
conn->flags |= CO_FL_EARLY_SSL_HS;
/* subscribe for handshake completion */
conn->xprt->subscribe(conn, conn->xprt_ctx, SUB_RETRY_RECV,
&qcc->wait_event);
qcc->flags |= QC_CF_WAIT_HS;
}
}
}
else { else {
/* Initiate backend side transfer by creating the first
* bidirectional stream. MUX will then be woken up on QUIC
* handshake completion so that stream layer can start the
* transfer itself.
*/
struct qcs *qcs; struct qcs *qcs;
struct stconn *sc = conn_ctx; struct stconn *sc = conn_ctx;
@ -3572,9 +3550,28 @@ static int qmux_init(struct connection *conn, struct proxy *prx,
sc_attach_mux(sc, qcs, conn); sc_attach_mux(sc, qcs, conn);
qcs->sd = sc->sedesc; qcs->sd = sc->sedesc;
qcs->sess = sess;
qcc->nb_sc++; qcc->nb_sc++;
} }
/* init read cycle */
tasklet_wakeup(qcc->wait_event.tasklet);
/* MUX is initialized before QUIC handshake completion if early data
* received. Flag connection to delay stream processing if
* wait-for-handshake is active.
*/
if (conn->handle.qc->state < QUIC_HS_ST_COMPLETE) {
if (!(conn->flags & CO_FL_EARLY_SSL_HS)) {
TRACE_STATE("flag connection with early data", QMUX_EV_QCC_WAKE, conn);
conn->flags |= CO_FL_EARLY_SSL_HS;
/* subscribe for handshake completion */
conn->xprt->subscribe(conn, conn->xprt_ctx, SUB_RETRY_RECV,
&qcc->wait_event);
qcc->flags |= QC_CF_WAIT_HS;
}
}
TRACE_LEAVE(QMUX_EV_QCC_NEW, conn); TRACE_LEAVE(QMUX_EV_QCC_NEW, conn);
return 0; return 0;
@ -3679,12 +3676,10 @@ static size_t qmux_strm_rcv_buf(struct stconn *sc, struct buffer *buf,
TRACE_STATE("report end-of-input", QMUX_EV_STRM_RECV, qcc->conn, qcs); TRACE_STATE("report end-of-input", QMUX_EV_STRM_RECV, qcc->conn, qcs);
se_fl_set(qcs->sd, SE_FL_EOI); se_fl_set(qcs->sd, SE_FL_EOI);
if (!(qcc->flags & QC_CF_IS_BACK)) { /* If request EOM is reported to the upper layer, it means the
/* If request EOM is reported to the upper layer, it means the * QCS now expects data from the opposite side.
* QCS now expects data from the opposite side. */
*/ se_expect_data(qcs->sd);
se_expect_data(qcs->sd);
}
} }
/* Set end-of-stream on read closed. */ /* Set end-of-stream on read closed. */
@ -3966,11 +3961,7 @@ static int qmux_wake(struct connection *conn)
goto release; goto release;
} }
/* Wake all streams, unless an error is set as qcc_io_process() has qcc_wake_some_streams(qcc);
* already woken them in this case.
*/
if (!(qcc->flags & (QC_CF_ERR_CONN|QC_CF_ERRL)))
qcc_wake_streams(qcc);
qcc_refresh_timeout(qcc); qcc_refresh_timeout(qcc);

View File

@ -431,7 +431,6 @@ int quic_connect_server(struct connection *conn, int flags)
fd_insert(fd, qc, quic_conn_sock_fd_iocb, tgid, ti->ltid_bit); fd_insert(fd, qc, quic_conn_sock_fd_iocb, tgid, ti->ltid_bit);
fd_want_recv(fd); fd_want_recv(fd);
conn_ctrl_init(conn);
return SF_ERR_NONE; /* connection is OK */ return SF_ERR_NONE; /* connection is OK */
} }

View File

@ -145,7 +145,7 @@ void qmux_dump_qcc_info(struct buffer *msg, const struct qcc *qcc)
{ {
const struct quic_conn *qc = qcc->conn->handle.qc; const struct quic_conn *qc = qcc->conn->handle.qc;
chunk_appendf(msg, " qcc=%p(%c)", qcc, (qcc->flags & QC_CF_IS_BACK) ? 'B' : 'F'); chunk_appendf(msg, " qcc=%p(F)", qcc);
if (qcc->conn->handle.qc) if (qcc->conn->handle.qc)
chunk_appendf(msg, " qc=%p", qcc->conn->handle.qc); chunk_appendf(msg, " qc=%p", qcc->conn->handle.qc);
chunk_appendf(msg, " .st=%s .sc=%llu .hreq=%llu .flg=0x%04x", chunk_appendf(msg, " .st=%s .sc=%llu .hreq=%llu .flg=0x%04x",

View File

@ -1047,7 +1047,7 @@ struct task *qc_process_timer(struct task *task, void *ctx, unsigned int state)
/* Allocate a new QUIC connection with <version> as QUIC version. <ipv4> /* Allocate a new QUIC connection with <version> as QUIC version. <ipv4>
* boolean is set to 1 for IPv4 connection, 0 for IPv6. <server> is set to 1 * boolean is set to 1 for IPv4 connection, 0 for IPv6. <server> is set to 1
* for QUIC servers (or haproxy listeners), 0 for QUIC clients. * for QUIC servers (or haproxy listeners).
* <dcid> is the destination connection ID, <scid> is the source connection ID. * <dcid> is the destination connection ID, <scid> is the source connection ID.
* This latter <scid> CID as the same value on the wire as the one for <conn_id> * This latter <scid> CID as the same value on the wire as the one for <conn_id>
* which is the first CID of this connection but a different internal * which is the first CID of this connection but a different internal
@ -1060,9 +1060,6 @@ struct task *qc_process_timer(struct task *task, void *ctx, unsigned int state)
* into the Retry token sent to the client before instantiated this connection. * into the Retry token sent to the client before instantiated this connection.
* Endpoints addresses are specified via <local_addr> and <peer_addr>. * Endpoints addresses are specified via <local_addr> and <peer_addr>.
* Returns the connection if succeeded, NULL if not. * Returns the connection if succeeded, NULL if not.
* For QUIC clients, <dcid>, <scid>, <token_odcid>, <conn_id> must be null,
* and <token> value must be 0. This is the responsability of the caller to ensure
* this is the case.
*/ */
struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4, struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
struct quic_cid *dcid, struct quic_cid *scid, struct quic_cid *dcid, struct quic_cid *scid,
@ -1351,15 +1348,7 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
return qc; return qc;
err: err:
if (!l && !conn_id) { pool_free(pool_head_quic_connection_id, conn_id);
/* For QUIC clients, <conn_id> is locally used and initialized to <conn_cid>
* value as soon as this latter is attached to the CIDs tree. It must
* be freed only if it has not been attached to this tree. This is
* quic_conn_release() which free this CID when it is attached to the tree.
*/
pool_free(pool_head_quic_connection_id, conn_id);
}
quic_conn_release(qc); quic_conn_release(qc);
/* Decrement global counters. Done only for errors happening before or /* Decrement global counters. Done only for errors happening before or

View File

@ -500,19 +500,11 @@ static int quic_parse_new_token_frame(struct quic_frame *frm, struct quic_conn *
{ {
struct qf_new_token *new_token_frm = &frm->new_token; struct qf_new_token *new_token_frm = &frm->new_token;
if (!quic_dec_int(&new_token_frm->len, pos, end) || end - *pos < new_token_frm->len) if (!quic_dec_int(&new_token_frm->len, pos, end) || end - *pos < new_token_frm->len ||
sizeof(new_token_frm->data) < new_token_frm->len)
return 0; return 0;
/* TODO token length is unknown as it is dependent from the peer. Hence
* dynamic allocation should be implemented for token storage, albeit
* with constraint to ensure memory usage remains reasonable.
*/
#if 0
if (sizeof(new_token_frm->data) < new_token_frm->len)
return 0;
memcpy(new_token_frm->data, *pos, new_token_frm->len); memcpy(new_token_frm->data, *pos, new_token_frm->len);
#endif
*pos += new_token_frm->len; *pos += new_token_frm->len;
return 1; return 1;

View File

@ -949,11 +949,7 @@ static int qc_parse_pkt_frms(struct quic_conn *qc, struct quic_rx_packet *pkt,
goto err; goto err;
} }
else { else {
/* TODO NEW_TOKEN not implemented on client side. /* TODO */
* Note that for now token is not copied into <data> field
* of qf_new_token frame. See quic_parse_new_token_frame()
* for further explanations.
*/
} }
break; break;
case QUIC_FT_STREAM_8 ... QUIC_FT_STREAM_F: case QUIC_FT_STREAM_8 ... QUIC_FT_STREAM_F:

View File

@ -780,9 +780,13 @@ SSL_CTX *ssl_quic_srv_new_ssl_ctx(void)
SSL_OP_SINGLE_ECDH_USE | SSL_OP_SINGLE_ECDH_USE |
SSL_OP_CIPHER_SERVER_PREFERENCE; SSL_OP_CIPHER_SERVER_PREFERENCE;
TRACE_ENTER(QUIC_EV_CONN_NEW);
ctx = SSL_CTX_new(TLS_client_method()); ctx = SSL_CTX_new(TLS_client_method());
if (!ctx) if (!ctx) {
goto err; TRACE_ERROR("Could not allocate a new TLS context", QUIC_EV_CONN_NEW);
goto leave;
}
SSL_CTX_set_options(ctx, options); SSL_CTX_set_options(ctx, options);
SSL_CTX_set_min_proto_version(ctx, TLS1_3_VERSION); SSL_CTX_set_min_proto_version(ctx, TLS1_3_VERSION);
@ -793,10 +797,12 @@ SSL_CTX *ssl_quic_srv_new_ssl_ctx(void)
#endif #endif
leave: leave:
TRACE_LEAVE(QUIC_EV_CONN_NEW);
return ctx; return ctx;
err: err:
SSL_CTX_free(ctx); SSL_CTX_free(ctx);
ctx = NULL; ctx = NULL;
TRACE_DEVEL("leaving on error", QUIC_EV_CONN_NEW);
goto leave; goto leave;
} }
@ -955,6 +961,7 @@ static int qc_ssl_provide_quic_data(struct ncbuf *ncbuf,
else { else {
const unsigned char *alpn; const unsigned char *alpn;
size_t alpn_len; size_t alpn_len;
struct server *s = objt_server(ctx->conn->target);
ctx->conn->flags &= ~(CO_FL_SSL_WAIT_HS | CO_FL_WAIT_L6_CONN); ctx->conn->flags &= ~(CO_FL_SSL_WAIT_HS | CO_FL_WAIT_L6_CONN);
if (!ssl_sock_get_alpn(ctx->conn, ctx, (const char **)&alpn, (int *)&alpn_len) || if (!ssl_sock_get_alpn(ctx->conn, ctx, (const char **)&alpn, (int *)&alpn_len) ||
@ -964,13 +971,12 @@ static int qc_ssl_provide_quic_data(struct ncbuf *ncbuf,
goto leave; goto leave;
} }
s->mux_proto = get_mux_proto(ist("quic"));
if (conn_create_mux(ctx->conn, NULL) < 0) { if (conn_create_mux(ctx->conn, NULL) < 0) {
TRACE_ERROR("mux creation failed", QUIC_EV_CONN_IO_CB, qc, &state); TRACE_ERROR("mux creation failed", QUIC_EV_CONN_IO_CB, qc, &state);
goto leave; goto leave;
} }
/* Wake up MUX after its creation. Operation similar to TLS+ALPN on TCP stack. */
ctx->conn->mux->wake(ctx->conn);
qc->mux_state = QC_MUX_READY; qc->mux_state = QC_MUX_READY;
} }

View File

@ -3836,15 +3836,6 @@ static int _srv_parse_finalize(char **args, int cur_arg,
} }
} }
#ifdef USE_QUIC
if (srv_is_quic(srv)) {
if (!srv->use_ssl) {
ha_alert("QUIC protocol detected without explicit SSL requirement. Use 'ssl' to fix this.\n");
return ERR_ALERT | ERR_FATAL;
}
}
#endif
srv_lb_commit_status(srv); srv_lb_commit_status(srv);
return 0; return 0;