Compare commits

...

63 Commits

Author SHA1 Message Date
Amaury Denoyelle
577fa44691 BUG/MINOR: quic: work around NEW_TOKEN parsing error on backend side
NEW_TOKEN frame is never emitted by a client, hence parsing was not
tested on frontend side.

On backend side, an issue can occur, as expected token length is static,
based on the token length used internally by haproxy. This is not
sufficient for most server implementation which uses larger token. This
causes a parsing error, which may cause skipping of following frames in
the same packet. This issue was detected using ngtcp2 as server.

As for now tokens are unused by haproxy, simply discard test on token
length during NEW_TOKEN frame parsing. The token itself is merely
skipped without being stored. This is sufficient for now to continue on
experimenting with QUIC backend implementation.

This does not need to be backported.
2025-06-12 17:47:15 +02:00
Amaury Denoyelle
830affc17d MINOR: server: reject QUIC servers without explicit SSL
Report an error during server configuration if QUIC is used by SSL is
not activiated via 'ssl' keyword. This is done in _srv_parse_finalize(),
which is both used by static and dynamic servers.

Note that contrary to listeners, an error is reported instead of a
warning, and SSL is not automatically activated if missing. This is
mainly due to the complex server configuration : _srv_parse_finalize()
is ideal to affect every servers, including dynamic entries. However, it
is executed after server SSL context allocation performed via
<prepare_srv> XPRT operation. A proper fix would be to move SSL ctx
alloc in _srv_parse_finalize(), but this may have unknown impact. Thus,
for now a simpler solution has been chosen.
2025-06-12 16:16:43 +02:00
Amaury Denoyelle
33cd96a5e9 BUG/MINOR: quic: prevent crash on startup with -dt
QUIC traces in ssl_quic_srv_new_ssl_ctx() are problematic as this
function is called early during startup. If activating traces via -dt
command-line argument, a crash occurs due to stderr sink not yet
available.

Thus, traces from ssl_quic_srv_new_ssl_ctx() are simply removed.

No backport needed.
2025-06-12 15:15:56 +02:00
Frederic Lecaille
5a0ae9e9be MINOR: quic-be: Avoid SSL context unreachable code without USE_QUIC_OPENSSL_COMPAT
This commit added a "err" C label reachable only with USE_QUIC_OPENSSL_COMPAT:

   MINOR: quic-be: Missing callbacks initializations (USE_QUIC_OPENSSL_COMPAT)

leading coverity to warn this:

*** CID 1611481:         Control flow issues  (UNREACHABLE)
/src/quic_ssl.c: 802             in ssl_quic_srv_new_ssl_ctx()
796     		goto err;
797     #endif
798
799      leave:
800     	TRACE_LEAVE(QUIC_EV_CONN_NEW);
801     	return ctx;
>>>     CID 1611481:         Control flow issues  (UNREACHABLE)
>>>     This code cannot be reached: "err:
SSL_CTX_free(ctx);".
802      err:
803     	SSL_CTX_free(ctx);
804     	ctx = NULL;
805     	TRACE_DEVEL("leaving on error", QUIC_EV_CONN_NEW);
806     	goto leave;
807     }

The less intrusive (without #ifdef) way to fix this it to add a "goto err"
statement from the code part which is reachable without USE_QUIC_OPENSSL_COMPAT.

Thank you to @chipitsine for having reported this issue in GH #3003.
2025-06-12 11:45:21 +02:00
Frederic Lecaille
869fb457ed BUG/MINOR: quic-be: CID double free upon qc_new_conn() failures
This issue may occur when qc_new_conn() fails after having allocated
and attached <conn_cid> to its tree. This is the case when compiling
haproxy against WolfSSL for an unknown reason at this time. In this
case the <conn_cid> is freed by pool_head_quic_connection_id(), then
freed again by quic_conn_release().

This bug arrived with this commit:

    MINOR: quic-be: QUIC connection allocation adaptation (qc_new_conn())

So, the aim of this patch is to free <conn_cid> only for QUIC backends
and if it is not attached to its tree. This is the case when <conn_id>
local variable passed with NULL value to qc_new_conn() is then intialized
to the same <conn_cid> value.
2025-06-12 11:45:21 +02:00
Frederic Lecaille
dc3fb3a731 CLEANUP: quic-be: Add comments about qc_new_conn() usage
This patch should have come with this last commit for the last qc_new_conn()
modifications for QUIC backends:

     MINOR: quic-be: get rid of ->li quic_conn member

qc_new_conn() must be passed NULL pointers for several variables as mentioned
by the comment. Some of these local variables are used to avoid too much
code modifications.
2025-06-12 11:45:21 +02:00
Amaury Denoyelle
603afd495b MINOR: hq-interop: encode request from HTX for backend side support
Implement transcoding of a HTX request into HTTP/0.9. This protocol is a
simplified version of HTTP. Request only supports GET method without any
header. As such, only a request line is written during snd_buf
operation.
2025-06-12 11:28:54 +02:00
Amaury Denoyelle
a286d5476b MINOR: hq-interop: decode response into HTX for backend side support
Implement transcoding of a HTTP/0.9 response into a HTX message.

HTTP/0.9 is a really simple substract of HTTP spec. The response does
not have any status line and is contains only the payload body. Response
is finished when the underlying connection/stream is closed.

A status line is generated to be compliant with HTX. This is performed
on the first invokation of rcv_buf for the current stream. Status code
is set to 200. Payload body if present is then copied using
htx_add_data().
2025-06-12 11:28:54 +02:00
Amaury Denoyelle
4031bf7432 MINOR: quic: wakeup backend MUX on handshake completed
This commit is the second and final step to initiate QUIC MUX on the
backend side. On handshake completion, MUX is woken up just after its
creation. This step is necessary to notify the stream layer, via the QCS
instance pre-initialized on MUX init, so that the transfer can be
resumed.

This mode of operation is similar to TCP stack when TLS+ALPN are used,
which forces MUX initialization to be delayed after handshake
completion.
2025-06-12 11:28:54 +02:00
Amaury Denoyelle
1efaca8a57 MINOR: mux-quic: instantiate first stream on backend side
Adjust qmux_init() to handle frontend and backend sides differently.
Most notably, on backend side, the first bidirectional stream is created
preemptively. This step is necessary as MUX layer will be woken up just
after handshake completion.
2025-06-12 11:28:54 +02:00
Amaury Denoyelle
f8d096c05f MINOR: mux-quic: set expect data only on frontend side
Stream data layer is notified that data is expected when FIN is
received, which marks the end of the HTTP request. This prepares data
layer to be able to handle the expected HTTP response.

Thus, this step is only relevant on frontend side. On backend side, FIN
marks the end of the HTTP response. No further content is expected, thus
expect data should not be set in this case.

Note that se_expect_data() invokation via qcs_attach_sc() is not
protected. This is because this function will only be called during
request headers parsing which is performed on the frontend side.
2025-06-12 11:28:54 +02:00
Amaury Denoyelle
e8775d51df MINOR: mux-quic: define flag for backend side
Mux connection is flagged with new QC_CF_IS_BACK if used on the backend
side. For now the only change is during traces, to be able to
differentiate frontend and backend usage.
2025-06-12 11:28:54 +02:00
Amaury Denoyelle
93b904702f MINOR: mux-quic: improve documentation for snd/rcv app-ops
Complete document for rcv_buf/snd_buf operations. In particular, return
value is now explicitely defined. For H3 layer, associated functions
documentation is also extended.
2025-06-12 11:28:54 +02:00
Amaury Denoyelle
e7f1db0348 MINOR: quic: mark ctrl layer as ready on quic_connect_server()
Use conn_ctrl_init() on the connection when quic_connect_server()
succeeds. This is necessary so that the connection is considered as
completely initialized. Without this, connect operation will be call
again if connection is reused.
2025-06-12 11:25:12 +02:00
Amaury Denoyelle
a0db93f3d8 MEDIUM: backend: delay MUX init with ALPN even if proto is forced
On backend side, multiplexer layer is initialized during
connect_server(). However, this step is not performed if ALPN is used,
as the negotiated protocol may be unknown. Multiplexer initialization is
delayed after TLS handshake completion.

There are still exceptions though that forces the MUX to be initialized
even if ALPN is used. One of them was if <mux_proto> server field was
already set at this stage, which is the case when an explicit proto is
selected on the server line configuration. Remove this condition so that
now MUX init is delayed with ALPN even if proto is forced.

The scope of this change should be minimal. In fact, the only impact
concerns server config with both proto and ALPN set, which is pretty
unlikely as it is contradictory.

The main objective of this patch is to prepare QUIC support on the
backend side. Indeed, QUIC proto will be forced on the server if a QUIC
address is used, similarly to bind configuration. However, we still want
to delay MUX initialization after QUIC handshake completion. This is
mandatory to know the selected application protocol, required during
QUIC MUX init.
2025-06-12 11:21:32 +02:00
Amaury Denoyelle
044ad3a602 BUG/MEDIUM: mux-quic: adjust wakeup behavior
Change wake callback behavior for QUIC MUX. This operation loops over
each QCS and notify their stream data layer on certain events via
internal helper qcc_wake_some_streams().

Previously, streams were notified only if an error occured on the
connection. Change this to notify streams data layer everytime wake
callback is used. This behavior is now identical to H2 MUX.

qcc_wake_some_streams() is also renamed to qcc_wake_streams(), as it
better reflect its true behavior.

This change should not have performance impact as wake mux ops should
not be called frequently. Note that qcc_wake_streams() can also be
called directly via qcc_io_process() to ensure a new error is correctly
propagated. As wake callback first uses qcc_io_process(), it will only
call qcc_wake_streams() if no error is present.

No known issue is associated with this commit. However, it could prevent
freezing transfer under certain condition. As such, it is considered as
a bug fix worthy of backporting.

This should be backported after a period of observation.
2025-06-12 11:12:49 +02:00
Christopher Faulet
2c3f3eaaed BUILD: hlua: Fix warnings about uninitialized variables (2)
It was still failing on Ubuntu-24.04 with GCC+ASAN. So, instead of
understand the code path the compiler followed to report uninitialized
variables, let's init them now.

No backport needed.
2025-06-12 10:49:54 +02:00
Aurelien DARRAGON
b5067a972c BUILD: listener: fix 'for' loop inline variable declaration
commit 16eb0fab3 ("MAJOR: counters: dispatch counters over thread groups")
introduced a build regression on some compilers:

  src/listener.c: In function 'listener_accept':
  src/listener.c:1095:3: error: 'for' loop initial declarations are only allowed in C99 mode
     for (int it = 0; it < global.nbtgroups; it++)
     ^
  src/listener.c:1095:3: note: use option -std=c99 or -std=gnu99 to compile your code
  src/listener.c:1101:4: error: 'for' loop initial declarations are only allowed in C99 mode
      for (int it = 0; it < global.nbtgroups; it++) {
      ^
  make: *** [src/listener.o] Error 1
  make: *** Waiting for unfinished jobs....

Let's fix that.
No backport needed
2025-06-12 08:46:36 +02:00
Christopher Faulet
01f011faeb BUILD: hlua: Fix warnings about uninitialized variables
In hlua_applet_tcp_recv_try() and hlua_applet_tcp_getline_yield(), GCC 14.2
reports warnings about 'blk2' variable that may be used uninitialized. It is
a bit strange because the code is pretty similar than before. But to make it
happy and to avoid bugs if the API change in future, 'blk2' is now used only
when its length is greater than 0.

No need to backport.
2025-06-12 08:46:36 +02:00
Christopher Faulet
8c573deb9f BUG/MINOR: hlua: Don't forget the return statement after a hlua_yieldk()
In hlua_applet_tcp_getline_yield(), the function may yield if there is no
data available. However we must take care to add a return statement just
after the call to hlua_yieldk(). I don't know the details of the LUA API,
but at least, this return statement fix a build error about uninitialized
variables that may be used.

It is a 3.3-specific issue. No backport needed.
2025-06-12 08:46:36 +02:00
Frederic Lecaille
bf6e576cfd MEDIUM: quic-be: initialize MUX on handshake completion
On backend side, MUX is instantiated after QUIC handshake completion.
This step is performed via qc_ssl_provide_quic_data(). First, connection
flags for handshake completion are resetted. Then, MUX is instantiated
via conn_create_mux() function.
2025-06-11 18:37:34 +02:00
Amaury Denoyelle
cdcecb9b65 MINOR: quic: define proper proto on QUIC servers
Force QUIC as <mux_proto> for server if a QUIC address is used. This is
similarly to what is already done for bind instances on the frontend
side. This step ensures that conn_create_mux() will select the proper
protocol.
2025-06-11 18:37:34 +02:00
Frederic Lecaille
855fd63f90 MINOR: quic-be: Prevent the MUX to send/receive data
Such actions must be interrupted until the handshake completion.
2025-06-11 18:37:34 +02:00
Frederic Lecaille
b9703cf711 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-11 18:37:34 +02:00
Frederic Lecaille
f6ef3bbc8a 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-11 18:37:34 +02:00
Frederic Lecaille
034cf74437 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-11 18:37:34 +02:00
Frederic Lecaille
d1cd0bb987 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-11 18:37:34 +02:00
Frederic Lecaille
fc90964b55 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-11 18:37:34 +02:00
Frederic Lecaille
8c2f2615f4 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-11 18:37:34 +02:00
Frederic Lecaille
f085a2f5bf 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-11 18:37:34 +02:00
Frederic Lecaille
a62098bfb0 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-11 18:37:34 +02:00
Frederic Lecaille
e226a7cb79 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-11 18:37:34 +02:00
Frederic Lecaille
2d076178c6 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-11 18:37:34 +02:00
Frederic Lecaille
b4a9b53515 MINOR: h3-be: Correctly retrieve h3 counters
This is done using qc_counters() function which supports also QUIC servers.
2025-06-11 18:37:34 +02:00
Frederic Lecaille
e27b7b4889 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-11 18:37:34 +02:00
Frederic Lecaille
43d88a44f1 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-11 18:37:34 +02:00
Frederic Lecaille
266b10b8a4 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-11 18:37:34 +02:00
Frederic Lecaille
89d5a59933 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-11 18:37:34 +02:00
Frederic Lecaille
f7c0f5ac1b 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-11 18:37:34 +02:00
Frederic Lecaille
29fb1aee57 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-11 18:37:34 +02:00
Frederic Lecaille
9831f596ea 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-11 18:37:34 +02:00
Frederic Lecaille
52ec3430f2 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-11 18:37:34 +02:00
Frederic Lecaille
9c84f64652 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-11 18:37:34 +02:00
Frederic Lecaille
f49bbd36b9 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-11 18:37:34 +02:00
Frederic Lecaille
1408d94bc4 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-11 18:37:34 +02:00
Frederic Lecaille
7c76252d8a 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-11 18:37:34 +02:00
Frederic Lecaille
1e45690656 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-11 18:37:34 +02:00
Frederic Lecaille
a4e1296208 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-11 18:37:34 +02:00
Frederic Lecaille
24fc44c44d MINOR: quic-be: QUIC backend XPRT and transport parameters init during parsing
Add ->quic_params new member to server struct.
Also set the ->xprt member of the server being initialized and initialize asap its
transport parameters from _srv_parse_init().
2025-06-11 18:37:34 +02:00
Frederic Lecaille
0e67687ca9 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-11 18:37:34 +02:00
Frederic Lecaille
5a711551a2 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-11 18:37:34 +02:00
Frederic Lecaille
990c9f95f7 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-11 18:37:34 +02:00
Amaury Denoyelle
9c751a3cc1 MINOR: mux-quic-be: allow QUIC proto on backend side
Activate QUIC protocol support for MUX-QUIC on the backend side,
additionally to current frontend support. This change is mandatory to be
able to implement QUIC on the backend side.

Without this modification, it is impossible to activate explicitely QUIC
protocol on a server line, hence an error is reported :
  config : proxy 'xxxx' : MUX protocol 'quic' is not usable for server 'yyyy'
2025-06-11 18:37:34 +02:00
Amaury Denoyelle
f66b495f8e MINOR: server: mark QUIC support as experimental
Mark QUIC address support for servers as experimental on the backend
side. Previously, it was allowed but wouldn't function as expected. As
QUIC backend support requires several changes, it is better to declare
it as experimental first.
2025-06-11 18:37:33 +02:00
Amaury Denoyelle
bdd5e58179 MINOR: server: implement helper to identify QUIC servers
Define srv_is_quic() which can be used to quickly identified if a server
uses QUIC protocol.
2025-06-11 18:37:19 +02:00
Amaury Denoyelle
1ecf2e9bab BUG/MINOR: config/server: reject QUIC addresses
QUIC is not implemented on the backend side. To prevent any issue, it is
better to reject any server configured which uses it. This is done via
_srv_parse_init() which is used both for static and dynamic servers.

This should be backported up to all stable versions.
2025-06-11 18:37:17 +02:00
Christopher Faulet
b5525fe759 [RELEASE] Released version 3.3-dev1
Released version 3.3-dev1 with the following main changes :
    - 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-06-11 14:31:33 +02:00
Christopher Faulet
b2f64af341 BUG/MINIR: h1: Fix doc of 'accept-unsafe-...-request' about URI parsing
The description of tests performed on the URI in H1 when
'accept-unsafe-violations-in-http-request' option is wrong. It states that
only characters below 32 and 127 are blocked when this option is set,
suggesting that otherwise, when it is not set, all invalid characters in the
URI, according to the RFC3986, are blocked.

But in fact, it is not true. By default all character below 32 and above 127
are blocked. And when 'accept-unsafe-violations-in-http-request' option is
set, characters above 127 (excluded) are accepted. But characters in
(33..126) are never checked, independently of this option.

This patch should fix the issue #2906. It should be backported as far as
3.0. For older versions, the docuementation could also be clarified because
this part is not really clear.

Note the request URI validation is still under discution because invalid
characters in (33.126) are never checked and some users request a stricter
parsing.
2025-06-10 19:17:56 +02:00
Olivier Houchard
6993981cd6 BUG/MEDIUM: fd: Use the provided tgid in fd_insert() to get tgroup_info
In fd_insert(), use the provided tgid to ghet the thread group info,
instead of using the one of the current thread, as we may call
fd_insert() from a thread of another thread group, that will happen at
least when binding the listeners. Otherwise we'd end up accessing the
thread mask containing enabled thread of the wrong thread group, which
can lead to crashes if we're binding on threads not present in the
thread group.
This should fix Github issue #2991.

This should be backported up to 2.8.
2025-06-10 15:10:56 +02:00
Christopher Faulet
9df380a152 MEDIUM: hlua: Update TCP applet functions to use the new applet API
The functions responsible to extract data from the applet input buffer or to
push data into the applet output buffer are now relying on the newly added
functions in the applet API. This simplifies a bit the code.
2025-06-10 08:16:10 +02:00
Christopher Faulet
18f9c71041 CLEANUP: applet: Simplify a bit comments for applet_put* functions
Instead of repeating which buffer is used depending on the API used by the
applet, a reference to applet_get_outbuf() was added.
2025-06-10 08:16:10 +02:00
Christopher Faulet
79445766a3 MINOR: applet: Add API functions to get data from the input buffer
There was already functions to pushed data from the applet to the stream by
inserting them in the right buffer, depending the applet was using or not
the legacy API. Here, functions to retreive data pushed to the applet by the
stream were added:

  * applet_getchar   : Gets one character

  * applet_getblk    : Copies a full block of data

  * applet_getword   : Copies one text block representing a word using a
                       custom separator as delimiter

  * applet_getline   : Copies one text line

  * applet_getblk_nc : Get one or two blocks of data

  * applet_getword_nc: Gets one or two blocks of text representing a word
                       using a custom separator as delimiter

  * applet_getline_nc: Gets one or two blocks of text representing a line
2025-06-10 08:16:10 +02:00
Christopher Faulet
0d8ecb1edc MINOR: applet: Add API functions to manipulate input and output buffers
In this patch, some functions were added to ease input and output buffers
manipulation, regardless the corresponding applet is using its own buffers
or it is relying on channels buffers. Following functions were added:

  * applet_get_inbuf  : Get the buffer containing data pushed to the applet
                        by the stream

  * applet_get_outbuf : Get the buffer containing data pushed by the applet
                        to the stream

  * applet_input_data : Return the amount of data in the input buffer

  * applet_skip_input : Skips <len> bytes from the input buffer

  * applet_reset_input: Skips all bytes from the input buffer

  * applet_output_room: Returns the amout of space available at the output
                        buffer

  * applet_need_room  : Indicates that the applet have more data to deliver
                        and it needs more room in the output buffer to do
			so
2025-06-10 08:16:10 +02:00
46 changed files with 1246 additions and 396 deletions

View File

@ -1,6 +1,47 @@
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
- MINOR: version: mention that it's development again

View File

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

View File

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

View File

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

View File

@ -282,6 +282,92 @@ static inline void applet_expect_data(struct appctx *appctx)
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(). */
static inline int _applet_putchk(struct appctx *appctx, struct buffer *chunk,
int stress)
@ -318,8 +404,7 @@ static inline int _applet_putchk(struct appctx *appctx, struct buffer *chunk,
return ret;
}
/* 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.
/* writes chunk <chunk> into the applet output buffer (see applet_get_outbuf).
*
* Returns the number of written bytes on success or -1 on error (lake of space,
* shutdown, invalid call...)
@ -335,8 +420,7 @@ static inline int applet_putchk_stress(struct appctx *appctx, struct buffer *chu
return _applet_putchk(appctx, chunk, 1);
}
/* 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.
/* writes <len> chars from <blk> into the applet output buffer (see applet_get_outbuf).
*
* Returns the number of written bytes on success or -1 on error (lake of space,
* shutdown, invalid call...)
@ -371,9 +455,8 @@ static inline int applet_putblk(struct appctx *appctx, const char *blk, int len)
return ret;
}
/* writes chars from <str> up to the trailing zero (excluded) into the applet's
* output buffer if it uses its own bufferx or into the input channel of the
* stream attached to this applet.
/* writes chars from <str> up to the trailing zero (excluded) into the applet
* output buffer (see applet_get_outbuf).
*
* Returns the number of written bytes on success or -1 on error (lake of space,
* shutdown, invalid call...)
@ -409,8 +492,7 @@ static inline int applet_putstr(struct appctx *appctx, const char *str)
return ret;
}
/* 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.
/* writes character <chr> into the applet's output buffer (see applet_get_outbuf).
*
* Returns the number of written bytes on success or -1 on error (lake of space,
* shutdown, invalid call...)
@ -446,6 +528,283 @@ static inline int applet_putchr(struct appctx *appctx, char chr)
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 */
/*

View File

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

View File

@ -202,7 +202,7 @@ struct qcc_app_ops {
/* Initialize <qcs> stream app context or leave it to NULL if rejected. */
int (*attach)(struct qcs *qcs, void *conn_ctx);
/* Convert received HTTP payload to HTX. */
/* Convert received HTTP payload to HTX. Returns amount of decoded bytes from <b> or a negative error code. */
ssize_t (*rcv_buf)(struct qcs *qcs, struct buffer *b, int fin);
/* Convert HTX to HTTP payload for sending. */
@ -232,7 +232,7 @@ struct qcc_app_ops {
#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 */
/* unused 0x00000004 */
#define QC_CF_IS_BACK 0x00000004 /* backend side */
#define QC_CF_CONN_FULL 0x00000008 /* no stream buffers available on connection */
/* unused 0x00000010 */
#define QC_CF_ERR_CONN 0x00000020 /* fatal error reported by transport layer */

View File

@ -228,6 +228,9 @@ struct quic_version {
extern const struct quic_version quic_versions[];
extern const size_t quic_versions_nb;
extern const struct quic_version *preferred_version;
extern const struct quic_version *quic_version_draft_29;
extern const struct quic_version *quic_version_1;
extern const struct quic_version *quic_version_2;
/* unused: 0x01 */
/* Flag the packet number space as requiring an ACK frame to be sent. */
@ -305,6 +308,7 @@ struct qcc_app_ops;
/* Number of received bytes. */ \
uint64_t rx; \
} bytes; \
size_t max_udp_payload; \
/* First DCID used by client on its Initial packet. */ \
struct quic_cid odcid; \
/* DCID of our endpoint - not updated when a new DCID is used */ \
@ -315,7 +319,7 @@ struct qcc_app_ops;
* with a connection \
*/ \
struct eb_root *cids; \
struct listener *li; /* only valid for frontend connections */ \
enum obj_type *target; \
/* Idle timer task */ \
struct task *idle_timer_task; \
unsigned int idle_expire; \
@ -456,6 +460,7 @@ struct quic_conn_closed {
#define QUIC_FL_CONN_HPKTNS_DCD (1U << 16) /* Handshake packet number space discarded */
#define QUIC_FL_CONN_PEER_VALIDATED_ADDR (1U << 17) /* Peer address is considered as validated for this connection. */
#define QUIC_FL_CONN_NO_TOKEN_RCVD (1U << 18) /* Client dit not send any token */
#define QUIC_FL_CONN_SCID_RECEIVED (1U << 19) /* (client only: first Initial received. */
/* gap here */
#define QUIC_FL_CONN_TO_KILL (1U << 24) /* Unusable connection, to be killed */
#define QUIC_FL_CONN_TX_TP_RECEIVED (1U << 25) /* Peer transport parameters have been received (used for the transmitting part) */

View File

@ -69,7 +69,8 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
struct quic_connection_id *conn_id,
struct sockaddr_storage *local_addr,
struct sockaddr_storage *peer_addr,
int server, int token, void *owner);
int token, void *owner,
struct connection *conn);
int quic_build_post_handshake_frames(struct quic_conn *qc);
const struct quic_version *qc_supported_version(uint32_t version);
int quic_peer_validated_addr(struct quic_conn *qc);
@ -163,6 +164,22 @@ static inline void quic_free_ncbuf(struct ncbuf *ncbuf)
*ncbuf = NCBUF_NULL;
}
/* Return the address of the QUIC counters attached to the proxy of
* the owner of the connection whose object type address is <o> for
* listener and servers, or NULL for others object type.
*/
static inline void *qc_counters(enum obj_type *o, const struct stats_module *m)
{
struct proxy *p;
struct listener *l = objt_listener(o);
struct server *s = objt_server(o);
p = l ? l->bind_conf->frontend :
s ? s->proxy : NULL;
return p ? EXTRA_COUNTERS_GET(p->extra_counters_fe, m) : NULL;
}
void chunk_frm_appendf(struct buffer *buf, const struct quic_frame *frm);
void quic_set_connection_close(struct quic_conn *qc, const struct quic_err err);
void quic_set_tls_alert(struct quic_conn *qc, int alert);

View File

@ -26,7 +26,7 @@
#include <haproxy/quic_rx-t.h>
int quic_dgram_parse(struct quic_dgram *dgram, struct quic_conn *from_qc,
struct listener *li);
enum obj_type *obj_type);
int qc_treat_rx_pkts(struct quic_conn *qc);
int qc_parse_hd_form(struct quic_rx_packet *pkt,
unsigned char **pos, const unsigned char *end);

View File

@ -33,6 +33,7 @@
#include <haproxy/connection-t.h>
#include <haproxy/fd-t.h>
#include <haproxy/listener-t.h>
#include <haproxy/obj_type.h>
#include <haproxy/quic_conn-t.h>
#include <haproxy/quic_sock-t.h>
@ -78,7 +79,8 @@ static inline char qc_test_fd(struct quic_conn *qc)
*/
static inline int qc_fd(struct quic_conn *qc)
{
return qc_test_fd(qc) ? qc->fd : qc->li->rx.fd;
/* TODO: check this: For backends, qc->fd is always initialized */
return qc_test_fd(qc) ? qc->fd : __objt_listener(qc->target)->rx.fd;
}
/* Try to increment <l> handshake current counter. If listener limit is

View File

@ -34,7 +34,8 @@
#include <haproxy/ssl_sock-t.h>
int ssl_quic_initial_ctx(struct bind_conf *bind_conf);
int qc_alloc_ssl_sock_ctx(struct quic_conn *qc);
SSL_CTX *ssl_quic_srv_new_ssl_ctx(void);
int qc_alloc_ssl_sock_ctx(struct quic_conn *qc, struct connection *conn);
int qc_ssl_provide_all_quic_data(struct quic_conn *qc, struct ssl_sock_ctx *ctx);
int quic_ssl_set_tls_cbs(SSL *ssl);

View File

@ -26,6 +26,9 @@ int qc_lstnr_params_init(struct quic_conn *qc,
const unsigned char *dcid, size_t dcidlen,
const unsigned char *scid, size_t scidlen,
const struct quic_cid *token_odcid);
void qc_srv_params_init(struct quic_conn *qc,
const struct quic_transport_params *srv_params,
const unsigned char *scid, size_t scidlen);
/* Dump <cid> transport parameter connection ID value if present (non null length).
* Used only for debugging purposes.

View File

@ -478,6 +478,9 @@ struct server {
char *alpn_str; /* ALPN protocol string */
int alpn_len; /* ALPN protocol string length */
} ssl_ctx;
#ifdef USE_QUIC
struct quic_transport_params quic_params; /* QUIC transport parameters */
#endif
struct resolv_srvrq *srvrq; /* Pointer representing the DNS SRV requeest, if any */
struct list srv_rec_item; /* to attach server to a srv record item */
struct list ip_rec_item; /* to attach server to a A or AAAA record item */

View File

@ -343,6 +343,16 @@ static inline void srv_detach(struct server *srv)
}
}
static inline int srv_is_quic(const struct server *srv)
{
#ifdef USE_QUIC
return srv->addr_type.proto_type == PROTO_TYPE_DGRAM &&
srv->addr_type.xprt_type == PROTO_TYPE_STREAM;
#else
return 0;
#endif
}
#endif /* _HAPROXY_SERVER_H */
/*

View File

@ -28,9 +28,11 @@
#include <haproxy/api.h>
#include <haproxy/connection-t.h>
#include <haproxy/listener-t.h>
#include <haproxy/protocol-t.h>
#include <haproxy/sock-t.h>
int sock_create_server_socket(struct connection *conn, struct proxy *be, int *stream_err);
int sock_create_server_socket(struct connection *conn, struct proxy *be,
enum proto_type proto_type, int sock_type, int *stream_err);
void sock_enable(struct receiver *rx);
void sock_disable(struct receiver *rx);
void sock_unbind(struct receiver *rx);

View File

@ -62,7 +62,7 @@ struct ckch_inst *ckch_inst_new();
int ckch_inst_new_load_store(const char *path, struct ckch_store *ckchs, struct bind_conf *bind_conf,
struct ssl_bind_conf *ssl_conf, char **sni_filter, int fcount, int is_default, struct ckch_inst **ckchi, char **err);
int ckch_inst_new_load_srv_store(const char *path, struct ckch_store *ckchs,
struct ckch_inst **ckchi, char **err);
struct ckch_inst **ckchi, char **err, int is_quic);
int ckch_inst_rebuild(struct ckch_store *ckch_store, struct ckch_inst *ckchi,
struct ckch_inst **new_inst, char **err);

View File

@ -1992,7 +1992,9 @@ int connect_server(struct stream *s)
/* set the correct protocol on the output stream connector */
if (srv) {
if (conn_prepare(srv_conn, protocol_lookup(srv_conn->dst->ss_family, PROTO_TYPE_STREAM, srv->alt_proto), srv->xprt)) {
struct protocol *proto = protocol_lookup(srv_conn->dst->ss_family, srv->addr_type.proto_type, srv->alt_proto);
if (conn_prepare(srv_conn, proto, srv->xprt)) {
conn_free(srv_conn);
return SF_ERR_INTERNAL;
}
@ -2018,9 +2020,13 @@ int connect_server(struct stream *s)
srv_conn->ctx = s->scb;
#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 ||
(srv->use_ssl != 1 || (!(srv->ssl_ctx.alpn_str) && !(srv->ssl_ctx.npn_str)) ||
srv->mux_proto || !IS_HTX_STRM(s)))
!IS_HTX_STRM(s)))
#endif
init_mux = 1;

View File

@ -3767,6 +3767,8 @@ out_uri_auth_compat:
((newsrv->flags & SRV_F_DEFSRV_USE_SSL) && newsrv->use_ssl != 1)) {
if (xprt_get(XPRT_SSL) && xprt_get(XPRT_SSL)->prepare_srv)
cfgerr += xprt_get(XPRT_SSL)->prepare_srv(newsrv);
else if (xprt_get(XPRT_QUIC) && xprt_get(XPRT_QUIC)->prepare_srv)
cfgerr += xprt_get(XPRT_QUIC)->prepare_srv(newsrv);
}
if ((newsrv->flags & SRV_F_FASTOPEN) &&
@ -4113,6 +4115,13 @@ out_uri_auth_compat:
int mode = conn_pr_mode_to_proto_mode(curproxy->mode);
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)
continue;

View File

@ -1423,7 +1423,8 @@ static int cli_io_handler_show_fd(struct appctx *appctx)
#if defined(USE_QUIC)
else if (fdt.iocb == quic_conn_sock_fd_iocb) {
qc = fdtab[fd].owner;
li = qc ? qc->li : NULL;
li = qc ? objt_listener(qc->target) : NULL;
sv = qc ? objt_server(qc->target) : NULL;
xprt_ctx = qc ? qc->xprt_ctx : NULL;
conn = qc ? qc->conn : NULL;
xprt = conn ? conn->xprt : NULL; // in fact it's &ssl_quic

View File

@ -36,6 +36,7 @@
#include <haproxy/qmux_http.h>
#include <haproxy/qpack-dec.h>
#include <haproxy/qpack-enc.h>
#include <haproxy/quic_conn.h>
#include <haproxy/quic_enc.h>
#include <haproxy/quic_fctl.h>
#include <haproxy/quic_frame.h>
@ -1384,9 +1385,13 @@ 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
* <qcs>. If <fin> is set, it indicates that no more data will arrive after.
*
* Returns the count of consumed bytes or a negative error code. If 0 is
* returned, stream data is incomplete, decoding should be call again later
* with more content.
* It may be necessary to call this function with an empty input buffer to
* signal a standalone FIN.
*
* 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)
{
@ -1686,6 +1691,13 @@ static int h3_encode_header(struct buffer *buf,
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)
{
int err;
@ -1840,15 +1852,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
* as a H3 HEADERS frame. H3 forbidden trailers are skipped. HTX trailer blocks
* are removed from <htx> until EOT is found and itself removed.
* as a HTTP/3 HEADERS frame. Forbidden trailers are skipped. HTX trailer
* blocks are removed from <htx> up to end-of-trailer included.
*
* 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
* of the stream.
*
* Returns the size of HTX blocks removed. A negative error code is returned in
* case of a fatal error which should caused a connection closure.
* Returns the amount of consumed bytes from <htx> buffer or a negative error
* code.
*/
static int h3_resp_trailers_send(struct qcs *qcs, struct htx *htx)
{
@ -2020,9 +2032,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
* performed.
*
* Returns the total bytes of encoded HTTP/3 payload. This corresponds to the
* total bytes of HTX block removed. A negative error code is returned in case
* of a fatal error which should caused a connection closure.
* Returns the amount of consumed bytes from <htx> buffer, which corresponds to
* the length sum of encoded frames payload. A negative error code is returned
* in case of a fatal error which should caused a connection closure.
*/
static int h3_resp_data_send(struct qcs *qcs, struct htx *htx,
struct buffer *buf, size_t count)
@ -2147,6 +2159,14 @@ static int h3_resp_data_send(struct qcs *qcs, struct htx *htx,
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)
{
size_t total = 0;
@ -2513,7 +2533,6 @@ static int h3_send_goaway(struct h3c *h3c)
static int h3_init(struct qcc *qcc)
{
struct h3c *h3c;
const struct listener *li = __objt_listener(qcc->conn->target);
TRACE_ENTER(H3_EV_H3C_NEW, qcc->conn);
@ -2530,9 +2549,7 @@ static int h3_init(struct qcc *qcc)
h3c->id_goaway = 0;
qcc->ctx = h3c;
h3c->prx_counters =
EXTRA_COUNTERS_GET(li->bind_conf->frontend->extra_counters_fe,
&h3_stats_module);
h3c->prx_counters = qc_counters(qcc->conn->target, &h3_stats_module);
LIST_INIT(&h3c->buf_wait.list);
TRACE_LEAVE(H3_EV_H3C_NEW, qcc->conn);

View File

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

View File

@ -6,13 +6,15 @@
#include <haproxy/dynbuf.h>
#include <haproxy/htx.h>
#include <haproxy/http.h>
#include <haproxy/istbuf.h>
#include <haproxy/mux_quic.h>
#include <haproxy/qmux_http.h>
#include <haproxy/qmux_trace.h>
#include <haproxy/quic_utils.h>
#include <haproxy/trace.h>
static ssize_t hq_interop_rcv_buf(struct qcs *qcs, struct buffer *b, int fin)
/* HTTP/0.9 request -> HTX. */
static ssize_t hq_interop_rcv_buf_req(struct qcs *qcs, struct buffer *b, int fin)
{
struct htx *htx;
struct htx_sl *sl;
@ -92,12 +94,74 @@ static ssize_t hq_interop_rcv_buf(struct qcs *qcs, struct buffer *b, int fin)
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,
size_t count)
{
enum htx_blk_type btype;
struct htx *htx = NULL;
struct htx_blk *blk;
struct htx_sl *sl = NULL;
int32_t idx;
uint32_t bsize, fsize;
struct buffer *res = NULL;
@ -113,9 +177,25 @@ static size_t hq_interop_snd_buf(struct qcs *qcs, struct buffer *buf,
btype = htx_get_blk_type(blk);
fsize = bsize = htx_get_blksz(blk);
BUG_ON(btype == HTX_BLK_REQ_SL);
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:
res = qcc_get_stream_txbuf(qcs, &err, 0);
if (!res) {

View File

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

View File

@ -134,6 +134,7 @@ static struct qcs *qcs_new(struct qcc *qcc, uint64_t id, enum qcs_type type)
qcs->stream = NULL;
qcs->qcc = qcc;
qcs->sd = NULL;
qcs->flags = QC_SF_NONE;
qcs->st = QC_SS_IDLE;
qcs->ctx = NULL;
@ -3114,10 +3115,10 @@ static void qcc_shutdown(struct qcc *qcc)
TRACE_LEAVE(QMUX_EV_QCC_END, qcc->conn);
}
/* Loop through all qcs from <qcc>. Report error on stream endpoint if
* connection on error and wake them.
/* Loop through all qcs from <qcc> and wake their associated data layer if
* still active. Also report error on it if connection is already in error.
*/
static int qcc_wake_some_streams(struct qcc *qcc)
static void qcc_wake_streams(struct qcc *qcc)
{
struct qcs *qcs;
struct eb64_node *node;
@ -3134,11 +3135,10 @@ static int qcc_wake_some_streams(struct qcc *qcc)
if (qcc->flags & (QC_CF_ERR_CONN|QC_CF_ERRL)) {
TRACE_POINT(QMUX_EV_QCC_WAKE, qcc->conn, qcs);
se_fl_set_error(qcs->sd);
qcs_alert(qcs);
}
}
return 0;
qcs_alert(qcs);
}
}
/* Conduct operations which should be made for <qcc> connection after
@ -3193,7 +3193,7 @@ static int qcc_io_process(struct qcc *qcc)
/* Report error if set on stream endpoint layer. */
if (qcc->flags & (QC_CF_ERR_CONN|QC_CF_ERRL))
qcc_wake_some_streams(qcc);
qcc_wake_streams(qcc);
out:
if (qcc_is_dead(qcc))
@ -3406,6 +3406,7 @@ static int qmux_init(struct connection *conn, struct proxy *prx,
{
struct qcc *qcc;
struct quic_transport_params *lparams, *rparams;
void *conn_ctx = conn->ctx;
TRACE_ENTER(QMUX_EV_QCC_NEW);
@ -3418,7 +3419,7 @@ static int qmux_init(struct connection *conn, struct proxy *prx,
_qcc_init(qcc);
conn->ctx = qcc;
qcc->nb_hreq = qcc->nb_sc = 0;
qcc->flags = 0;
qcc->flags = conn_is_back(conn) ? QC_CF_IS_BACK : 0;
qcc->app_st = QCC_APP_ST_NULL;
qcc->glitches = 0;
qcc->err = quic_err_transport(QC_ERR_NO_ERROR);
@ -3531,27 +3532,48 @@ static int qmux_init(struct connection *conn, struct proxy *prx,
if (qcc->app_ops == &h3_ops && !conn_is_back(conn))
proxy_inc_fe_cum_sess_ver_ctr(sess->listener, prx, 3);
/* Register conn for idle front closing. This is done once everything is allocated. */
if (!conn_is_back(conn))
if (!conn_is_back(conn)) {
/* Register conn for idle front closing. */
LIST_APPEND(&mux_stopping_data[tid].list, &conn->stopping_list);
/* init read cycle */
tasklet_wakeup(qcc->wait_event.tasklet);
/* 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;
/* 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 {
/* 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 stconn *sc = conn_ctx;
qcs = qcc_init_stream_local(qcc, 1);
if (!qcs) {
TRACE_PROTO("Cannot allocate a new locally initiated streeam",
QMUX_EV_QCC_NEW|QMUX_EV_QCC_ERR, conn);
goto err;
}
sc_attach_mux(sc, qcs, conn);
qcs->sd = sc->sedesc;
qcc->nb_sc++;
}
TRACE_LEAVE(QMUX_EV_QCC_NEW, conn);
return 0;
@ -3657,10 +3679,12 @@ 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);
se_fl_set(qcs->sd, SE_FL_EOI);
/* If request EOM is reported to the upper layer, it means the
* QCS now expects data from the opposite side.
*/
se_expect_data(qcs->sd);
if (!(qcc->flags & QC_CF_IS_BACK)) {
/* If request EOM is reported to the upper layer, it means the
* QCS now expects data from the opposite side.
*/
se_expect_data(qcs->sd);
}
}
/* Set end-of-stream on read closed. */
@ -3942,7 +3966,11 @@ static int qmux_wake(struct connection *conn)
goto release;
}
qcc_wake_some_streams(qcc);
/* Wake all streams, unless an error is set as qcc_io_process() has
* already woken them in this case.
*/
if (!(qcc->flags & (QC_CF_ERR_CONN|QC_CF_ERRL)))
qcc_wake_streams(qcc);
qcc_refresh_timeout(qcc);
@ -4149,6 +4177,6 @@ void qcc_show_quic(struct qcc *qcc)
}
static struct mux_proto_list mux_proto_quic =
{ .token = IST("quic"), .mode = PROTO_MODE_HTTP, .side = PROTO_SIDE_FE, .mux = &qmux_ops };
{ .token = IST("quic"), .mode = PROTO_MODE_HTTP, .side = PROTO_SIDE_BOTH, .mux = &qmux_ops };
INITCALL1(STG_REGISTER, register_mux_proto, &mux_proto_quic);

View File

@ -286,11 +286,11 @@ int quic_connect_server(struct connection *conn, int flags)
struct proxy *be;
struct conn_src *src;
struct sockaddr_storage *addr;
struct quic_conn *qc = conn->handle.qc;
BUG_ON(qc->fd != -1);
BUG_ON(!conn->dst);
conn->flags |= CO_FL_WAIT_L4_CONN; /* connection in progress */
switch (obj_type(conn->target)) {
case OBJ_TYPE_PROXY:
be = __objt_proxy(conn->target);
@ -306,7 +306,7 @@ int quic_connect_server(struct connection *conn, int flags)
}
/* perform common checks on obtained socket FD, return appropriate Stream Error Flag in case of failure */
fd = conn->handle.fd = sock_create_server_socket(conn, be, &stream_err);
fd = sock_create_server_socket(conn, be, PROTO_TYPE_DGRAM, SOCK_DGRAM, &stream_err);
if (fd == -1)
return stream_err;
@ -413,72 +413,25 @@ int quic_connect_server(struct connection *conn, int flags)
}
if (global.tune.server_sndbuf)
setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &global.tune.server_sndbuf, sizeof(global.tune.server_sndbuf));
setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &global.tune.server_sndbuf, sizeof(global.tune.server_sndbuf));
if (global.tune.server_rcvbuf)
setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &global.tune.server_rcvbuf, sizeof(global.tune.server_rcvbuf));
setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &global.tune.server_rcvbuf, sizeof(global.tune.server_rcvbuf));
addr = (conn->flags & CO_FL_SOCKS4) ? &srv->socks4_addr : conn->dst;
if (connect(fd, (const struct sockaddr *)addr, get_addr_len(addr)) == -1) {
if (errno == EINPROGRESS || errno == EALREADY) {
/* common case, let's wait for connect status */
conn->flags |= CO_FL_WAIT_L4_CONN;
}
else if (errno == EISCONN) {
/* should normally not happen but if so, indicates that it's OK */
conn->flags &= ~CO_FL_WAIT_L4_CONN;
}
else if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EADDRINUSE || errno == EADDRNOTAVAIL) {
char *msg;
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EADDRNOTAVAIL) {
msg = "no free ports";
conn->err_code = CO_ER_FREE_PORTS;
}
else {
msg = "local address already in use";
conn->err_code = CO_ER_ADDR_INUSE;
}
qfprintf(stderr,"Connect() failed for backend %s: %s.\n", be->id, msg);
port_range_release_port(fdinfo[fd].port_range, fdinfo[fd].local_port);
fdinfo[fd].port_range = NULL;
close(fd);
send_log(be, LOG_ERR, "Connect() failed for backend %s: %s.\n", be->id, msg);
conn->flags |= CO_FL_ERROR;
return SF_ERR_RESOURCE;
} else if (errno == ETIMEDOUT) {
//qfprintf(stderr,"Connect(): ETIMEDOUT");
port_range_release_port(fdinfo[fd].port_range, fdinfo[fd].local_port);
fdinfo[fd].port_range = NULL;
close(fd);
conn->err_code = CO_ER_SOCK_ERR;
conn->flags |= CO_FL_ERROR;
return SF_ERR_SRVTO;
} else {
// (errno == ECONNREFUSED || errno == ENETUNREACH || errno == EACCES || errno == EPERM)
//qfprintf(stderr,"Connect(): %d", errno);
port_range_release_port(fdinfo[fd].port_range, fdinfo[fd].local_port);
fdinfo[fd].port_range = NULL;
close(fd);
conn->err_code = CO_ER_SOCK_ERR;
conn->flags |= CO_FL_ERROR;
return SF_ERR_SRVCL;
}
}
else {
/* connect() == 0, this is great! */
conn->flags &= ~CO_FL_WAIT_L4_CONN;
port_range_release_port(fdinfo[fd].port_range, fdinfo[fd].local_port);
fdinfo[fd].port_range = NULL;
close(fd);
conn->flags |= CO_FL_ERROR;
return SF_ERR_SRVCL;
}
conn_ctrl_init(conn); /* registers the FD */
HA_ATOMIC_OR(&fdtab[fd].state, FD_LINGER_RISK); /* close hard if needed */
if (conn->flags & CO_FL_WAIT_L4_CONN) {
fd_want_send(fd);
fd_cant_send(fd);
fd_cant_recv(fd);
}
qc->fd = fd;
fd_insert(fd, qc, quic_conn_sock_fd_iocb, tgid, ti->ltid_bit);
fd_want_recv(fd);
conn_ctrl_init(conn);
return SF_ERR_NONE; /* connection is OK */
}

View File

@ -397,7 +397,7 @@ int tcp_connect_server(struct connection *conn, int flags)
/* perform common checks on obtained socket FD, return appropriate Stream Error Flag in case of failure */
fd = conn->handle.fd = sock_create_server_socket(conn, be, &stream_err);
fd = conn->handle.fd = sock_create_server_socket(conn, be, PROTO_TYPE_STREAM, SOCK_STREAM, &stream_err);
if (fd == -1)
return stream_err;

View File

@ -330,7 +330,7 @@ static int uxst_connect_server(struct connection *conn, int flags)
}
/* perform common checks on obtained socket FD, return appropriate Stream Error Flag in case of failure */
fd = conn->handle.fd = sock_create_server_socket(conn, be, &stream_err);
fd = conn->handle.fd = sock_create_server_socket(conn, be, PROTO_TYPE_STREAM, SOCK_STREAM, &stream_err);
if (fd == -1)
return stream_err;

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;
chunk_appendf(msg, " qcc=%p(F)", qcc);
chunk_appendf(msg, " qcc=%p(%c)", qcc, (qcc->flags & QC_CF_IS_BACK) ? 'B' : 'F');
if (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",

View File

@ -4,7 +4,7 @@
#include <haproxy/cli.h>
#include <haproxy/list.h>
#include <haproxy/mux_quic.h>
#include <haproxy/quic_conn-t.h>
#include <haproxy/quic_conn.h>
#include <haproxy/quic_tp.h>
#include <haproxy/quic_utils.h>
#include <haproxy/tools.h>
@ -181,9 +181,11 @@ static void dump_quic_oneline(struct show_quic_ctx *ctx, struct quic_conn *qc)
char bufaddr[INET6_ADDRSTRLEN], bufport[6];
int ret;
unsigned char cid_len;
struct listener *l = objt_listener(qc->target);
ret = chunk_appendf(&trash, "%p[%02u]/%-.12s ", qc, ctx->thr,
qc->li->bind_conf->frontend->id);
l ? l->bind_conf->frontend->id : __objt_server(qc->target)->id);
chunk_appendf(&trash, "%*s", 36 - ret, " "); /* align output */
/* State */

View File

@ -119,6 +119,10 @@ const struct quic_version quic_versions[] = {
},
};
const struct quic_version *quic_version_draft_29 = &quic_versions[0];
const struct quic_version *quic_version_1 = &quic_versions[1];
const struct quic_version *quic_version_2 = &quic_versions[2];
/* Function pointers, can be used to compute a hash from first generated CID and to derive new CIDs */
uint64_t (*quic_hash64_from_cid)(const unsigned char *cid, int size, const unsigned char *secret, size_t secretlen) = NULL;
void (*quic_newcid_from_hash64)(unsigned char *cid, int size, uint64_t hash, const unsigned char *secret, size_t secretlen) = NULL;
@ -745,7 +749,7 @@ static struct quic_conn_closed *qc_new_cc_conn(struct quic_conn *qc)
cc_qc->dcid = qc->dcid;
cc_qc->scid = qc->scid;
cc_qc->li = qc->li;
cc_qc->target = qc->target;
cc_qc->cids = qc->cids;
cc_qc->idle_timer_task = qc->idle_timer_task;
@ -771,6 +775,7 @@ struct task *quic_conn_io_cb(struct task *t, void *context, unsigned int state)
struct list send_list = LIST_HEAD_INIT(send_list);
struct quic_enc_level *qel;
int st;
int discard_hpktns = 0;
struct tasklet *tl = (struct tasklet *)t;
TRACE_ENTER(QUIC_EV_CONN_IO_CB, qc);
@ -818,16 +823,30 @@ struct task *quic_conn_io_cb(struct task *t, void *context, unsigned int state)
goto out;
st = qc->state;
if (st >= QUIC_HS_ST_COMPLETE) {
if (!(qc->flags & QUIC_FL_CONN_HPKTNS_DCD)) {
/* Discard the Handshake packet number space. */
TRACE_PROTO("discarding Handshake pktns", QUIC_EV_CONN_PHPKTS, qc);
quic_pktns_discard(qc->hel->pktns, qc);
qc_set_timer(qc);
qc_el_rx_pkts_del(qc->hel);
qc_release_pktns_frms(qc, qc->hel->pktns);
}
if (qc_is_listener(qc)) {
if (st >= QUIC_HS_ST_COMPLETE && !quic_tls_pktns_is_dcd(qc, qc->hpktns))
discard_hpktns = 1;
}
else {
if (st >= QUIC_HS_ST_CONFIRMED && !quic_tls_pktns_is_dcd(qc, qc->hpktns))
discard_hpktns = 1;
}
if (discard_hpktns) {
/* Discard the Handshake packet number space. */
TRACE_PROTO("discarding Handshake pktns", QUIC_EV_CONN_PHPKTS, qc);
quic_pktns_discard(qc->hel->pktns, qc);
qc_set_timer(qc);
qc_el_rx_pkts_del(qc->hel);
qc_release_pktns_frms(qc, qc->hel->pktns);
if (!qc_is_listener(qc)) {
/* I/O callback switch */
qc->wait_event.tasklet->process = quic_conn_app_io_cb;
}
}
if (qc_is_listener(qc) && st >= QUIC_HS_ST_COMPLETE) {
/* Note: if no token for address validation was received
* for a 0RTT connection, some 0RTT packet could still be
* waiting for HP removal AFTER the successful handshake completion.
@ -885,6 +904,26 @@ struct task *quic_conn_io_cb(struct task *t, void *context, unsigned int state)
goto out;
}
/* RFC 9001. 4.9.1. Discarding Initial Keys
*
* The successful use of Handshake packets indicates that no more Initial
* packets need to be exchanged, as these keys can only be produced after
* receiving all CRYPTO frames from Initial packets. Thus, a client MUST
* discard Initial keys when it first sends a Handshake packet...
*/
if (!qc_is_listener(qc) && !quic_tls_pktns_is_dcd(qc, qc->ipktns) &&
qc->hpktns && qc->hpktns->tx.in_flight > 0) {
/* Discard the Initial packet number space. */
TRACE_PROTO("discarding Initial pktns", QUIC_EV_CONN_PRSHPKT, qc);
quic_pktns_discard(qc->ipktns, qc);
qc_set_timer(qc);
qc_el_rx_pkts_del(qc->iel);
qc_release_pktns_frms(qc, qc->ipktns);
}
qc_txb_release(qc);
out:
/* Release the Handshake encryption level and packet number space if
* the Handshake is confirmed and if there is no need to send
@ -1008,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>
* boolean is set to 1 for IPv4 connection, 0 for IPv6. <server> is set to 1
* for QUIC servers (or haproxy listeners).
* for QUIC servers (or haproxy listeners), 0 for QUIC clients.
* <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>
* which is the first CID of this connection but a different internal
@ -1021,6 +1060,9 @@ 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.
* Endpoints addresses are specified via <local_addr> and <peer_addr>.
* 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_cid *dcid, struct quic_cid *scid,
@ -1028,11 +1070,13 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
struct quic_connection_id *conn_id,
struct sockaddr_storage *local_addr,
struct sockaddr_storage *peer_addr,
int server, int token, void *owner)
int token, void *target,
struct connection *conn)
{
struct quic_conn *qc = NULL;
struct listener *l = server ? owner : NULL;
struct proxy *prx = l ? l->bind_conf->frontend : NULL;
struct listener *l = objt_listener(target);
struct server *srv = objt_server(target);
struct proxy *prx = l ? l->bind_conf->frontend : __objt_server(target)->proxy;
struct quic_cc_algo *cc_algo = NULL;
unsigned int next_actconn = 0, next_sslconn = 0, next_handshake = 0;
@ -1051,7 +1095,7 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
goto err;
}
if (server) {
if (l) {
next_handshake = quic_increment_curr_handshake(l);
if (!next_handshake) {
TRACE_STATE("max handshake reached", QUIC_EV_CONN_INIT);
@ -1065,6 +1109,14 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
goto err;
}
qc->cids = pool_alloc(pool_head_quic_cids);
if (!qc->cids) {
TRACE_ERROR("Could not allocate a new CID tree", QUIC_EV_CONN_INIT, qc);
goto err;
}
qc->target = target;
*qc->cids = EB_ROOT;
/* Now that quic_conn instance is allocated, quic_conn_release() will
* ensure global accounting is decremented.
*/
@ -1082,7 +1134,6 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
qc->streams_by_id = EB_ROOT_UNIQUE;
/* Required to call free_quic_conn_cids() from quic_conn_release() */
qc->cids = NULL;
qc->tx.cc_buf_area = NULL;
qc_init_fd(qc);
@ -1096,7 +1147,7 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
qc->idle_timer_task = NULL;
qc->xprt_ctx = NULL;
qc->conn = NULL;
qc->conn = conn;
qc->qcc = NULL;
qc->app_ops = NULL;
qc->path = NULL;
@ -1117,16 +1168,12 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
/* Packet number spaces */
qc->ipktns = qc->hpktns = qc->apktns = NULL;
LIST_INIT(&qc->pktns_list);
/* Required to safely call quic_conn_prx_cntrs_update() from quic_conn_release(). */
qc->prx_counters = NULL;
qc->prx_counters = EXTRA_COUNTERS_GET(prx->extra_counters_fe, &quic_stats_module);
/* QUIC Server (or listener). */
if (server) {
if (l) {
cc_algo = l->bind_conf->quic_cc_algo;
qc->prx_counters = EXTRA_COUNTERS_GET(prx->extra_counters_fe,
&quic_stats_module);
qc->flags = QUIC_FL_CONN_LISTENER;
/* Mark this connection as having not received any token when 0-RTT is enabled. */
if (l->bind_conf->ssl_conf.early_data && !token)
@ -1137,29 +1184,52 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
/* Copy the packet SCID to reuse it as DCID for sending */
qc->dcid = *scid;
qc->tx.buf = BUF_NULL;
qc->li = l;
conn_id->qc = qc;
}
/* QUIC Client (outgoing connection to servers) */
else {
struct quic_connection_id *conn_cid = NULL;
qc->flags = QUIC_FL_CONN_PEER_VALIDATED_ADDR;
qc->state = QUIC_HS_ST_CLIENT_INITIAL;
if (dcid->len)
memcpy(qc->dcid.data, dcid->data, dcid->len);
qc->dcid.len = dcid->len;
qc->li = NULL;
memset(&qc->odcid, 0, sizeof qc->odcid);
qc->odcid.len = 0;
/* This is the original connection ID from the peer server
* point of view.
*/
if (RAND_bytes(qc->dcid.data, sizeof(qc->dcid.data)) != 1)
goto err;
qc->dcid.len = sizeof(qc->dcid.data);
conn_cid = new_quic_cid(qc->cids, qc, NULL, NULL);
if (!conn_cid)
goto err;
_quic_cid_insert(conn_cid);
dcid = &qc->dcid;
conn_id = conn_cid;
qc->tx.buf = BUF_NULL;
qc->next_cid_seq_num = 1;
conn->handle.qc = qc;
}
qc->mux_state = QC_MUX_NULL;
qc->err = quic_err_transport(QC_ERR_NO_ERROR);
/* If connection is instantiated due to an INITIAL packet with an
/* Listener only: if connection is instantiated due to an INITIAL packet with an
* already checked token, consider the peer address as validated.
*/
if (token) {
TRACE_STATE("validate peer address due to initial token",
QUIC_EV_CONN_INIT, qc);
qc->flags |= QUIC_FL_CONN_PEER_VALIDATED_ADDR;
}
else {
HA_ATOMIC_INC(&qc->prx_counters->half_open_conn);
if (l) {
if (token_odcid->len) {
TRACE_STATE("validate peer address due to initial token",
QUIC_EV_CONN_INIT, qc);
qc->flags |= QUIC_FL_CONN_PEER_VALIDATED_ADDR;
}
else {
HA_ATOMIC_INC(&qc->prx_counters->half_open_conn);
}
}
/* Now proceeds to allocation of qc members. */
@ -1169,16 +1239,8 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
goto err;
}
qc->cids = pool_alloc(pool_head_quic_cids);
if (!qc->cids) {
TRACE_ERROR("Could not allocate a new CID tree", QUIC_EV_CONN_INIT, qc);
goto err;
}
*qc->cids = EB_ROOT;
conn_id->qc = qc;
if (HA_ATOMIC_LOAD(&l->rx.quic_mode) == QUIC_SOCK_MODE_CONN &&
/* Listener only */
if (l && HA_ATOMIC_LOAD(&l->rx.quic_mode) == QUIC_SOCK_MODE_CONN &&
(quic_tune.options & QUIC_TUNE_SOCK_PER_CONN) &&
is_addr(local_addr)) {
TRACE_USER("Allocate a socket for QUIC connection", QUIC_EV_CONN_INIT, qc);
@ -1217,6 +1279,10 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
qc->rx.buf = b_make(qc->rx.buf.area, QUIC_CONN_RX_BUFSZ, 0, 0);
qc->rx.stream_max_uni = qc->rx.stream_max_bidi = 0;
qc->max_udp_payload = l ?
l->bind_conf->quic_params.max_udp_payload_size :
srv->quic_params.max_udp_payload_size;
qc->nb_pkt_for_cc = 1;
qc->nb_pkt_since_cc = 0;
@ -1228,17 +1294,24 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
qc->max_ack_delay = 0;
/* Only one path at this time (multipath not supported) */
qc->path = &qc->paths[0];
quic_cc_path_init(qc->path, ipv4, server ? l->bind_conf->max_cwnd : 0,
quic_cc_path_init(qc->path, ipv4, l ? l->bind_conf->max_cwnd : 0,
cc_algo ? cc_algo : default_quic_cc_algo, qc);
memcpy(&qc->local_addr, local_addr, sizeof(qc->local_addr));
if (local_addr)
memcpy(&qc->local_addr, local_addr, sizeof(qc->local_addr));
else
memset(&qc->local_addr, 0, sizeof(qc->local_addr));
memcpy(&qc->peer_addr, peer_addr, sizeof qc->peer_addr);
if (server && !qc_lstnr_params_init(qc, &l->bind_conf->quic_params,
conn_id->stateless_reset_token,
dcid->data, dcid->len,
qc->scid.data, qc->scid.len, token_odcid))
goto err;
if (l) {
qc_lstnr_params_init(qc, &l->bind_conf->quic_params,
conn_id->stateless_reset_token,
qc->dcid.data, qc->dcid.len,
qc->scid.data, qc->scid.len, token_odcid);
}
else {
qc_srv_params_init(qc, &srv->quic_params, qc->scid.data, qc->scid.len);
}
/* Initialize the idle timeout of the connection at the "max_idle_timeout"
* value from local transport parameters.
@ -1259,12 +1332,12 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
qc->wait_event.events = 0;
qc->subs = NULL;
if (qc_alloc_ssl_sock_ctx(qc) ||
if (qc_alloc_ssl_sock_ctx(qc, conn) ||
!quic_conn_init_timer(qc) ||
!quic_conn_init_idle_timer_task(qc, prx))
goto err;
if (!qc_new_isecs(qc, &qc->iel->tls_ctx, qc->original_version, dcid->data, dcid->len, 1))
if (!qc_new_isecs(qc, &qc->iel->tls_ctx, qc->original_version, dcid->data, dcid->len, !!l))
goto err;
/* Counters initialization */
@ -1278,6 +1351,15 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
return qc;
err:
if (!l && !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);
/* Decrement global counters. Done only for errors happening before or
@ -1314,7 +1396,7 @@ int qc_handle_conn_migration(struct quic_conn *qc,
* used during the handshake, unless the endpoint has acted on a
* preferred_address transport parameter from the peer.
*/
if (qc->li->bind_conf->quic_params.disable_active_migration) {
if (__objt_listener(qc->target)->bind_conf->quic_params.disable_active_migration) {
TRACE_ERROR("Active migration was disabled, datagram dropped", QUIC_EV_CONN_LPKT, qc);
goto err;
}
@ -1444,8 +1526,8 @@ int quic_conn_release(struct quic_conn *qc)
*/
if (MT_LIST_INLIST(&qc->accept_list)) {
MT_LIST_DELETE(&qc->accept_list);
BUG_ON(qc->li->rx.quic_curr_accept == 0);
HA_ATOMIC_DEC(&qc->li->rx.quic_curr_accept);
BUG_ON(__objt_listener(qc->target)->rx.quic_curr_accept == 0);
HA_ATOMIC_DEC(&__objt_listener(qc->target)->rx.quic_curr_accept);
}
/* Substract last congestion window from global memory counter. */
@ -1515,8 +1597,8 @@ int quic_conn_release(struct quic_conn *qc)
/* Connection released before handshake completion. */
if (unlikely(qc->state < QUIC_HS_ST_COMPLETE)) {
if (qc_is_listener(qc)) {
BUG_ON(qc->li->rx.quic_curr_handshake == 0);
HA_ATOMIC_DEC(&qc->li->rx.quic_curr_handshake);
BUG_ON(__objt_listener(qc->target)->rx.quic_curr_handshake == 0);
HA_ATOMIC_DEC(&__objt_listener(qc->target)->rx.quic_curr_handshake);
}
}
@ -1924,8 +2006,8 @@ void qc_bind_tid_commit(struct quic_conn *qc, struct listener *new_li)
/* At this point no connection was accounted for yet on this
* listener so it's OK to just swap the pointer.
*/
if (new_li && new_li != qc->li)
qc->li = new_li;
if (new_li && new_li != __objt_listener(qc->target))
qc->target = &new_li->obj_type;
/* Rebind the connection FD. */
if (qc_test_fd(qc)) {

View File

@ -500,11 +500,19 @@ static int quic_parse_new_token_frame(struct quic_frame *frm, struct quic_conn *
{
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 ||
sizeof(new_token_frm->data) < new_token_frm->len)
if (!quic_dec_int(&new_token_frm->len, pos, end) || end - *pos < new_token_frm->len)
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);
#endif
*pos += new_token_frm->len;
return 1;

View File

@ -58,7 +58,7 @@ static int qc_ssl_compat_add_tps_cb(SSL *ssl, unsigned int ext_type, unsigned in
int quic_tls_compat_init(struct bind_conf *bind_conf, SSL_CTX *ctx)
{
/* Ignore non-QUIC connections */
if (bind_conf->xprt != xprt_get(XPRT_QUIC))
if (bind_conf && bind_conf->xprt != xprt_get(XPRT_QUIC))
return 1;
/* This callback is already registered if the TLS keylog is activated for
@ -150,22 +150,22 @@ void quic_tls_compat_keylog_callback(const SSL *ssl, const char *line)
if (sizeof(QUIC_OPENSSL_COMPAT_CLIENT_HANDSHAKE) - 1 == n &&
!strncmp(start, QUIC_OPENSSL_COMPAT_CLIENT_HANDSHAKE, n)) {
level = ssl_encryption_handshake;
write = 0;
write = qc_is_listener(qc) ? 0 : 1;
}
else if (sizeof(QUIC_OPENSSL_COMPAT_SERVER_HANDSHAKE) - 1 == n &&
!strncmp(start, QUIC_OPENSSL_COMPAT_SERVER_HANDSHAKE, n)) {
level = ssl_encryption_handshake;
write = 1;
write = qc_is_listener(qc) ? 1 : 0;
}
else if (sizeof(QUIC_OPENSSL_COMPAT_CLIENT_APPLICATION) - 1 == n &&
!strncmp(start, QUIC_OPENSSL_COMPAT_CLIENT_APPLICATION, n)) {
level = ssl_encryption_application;
write = 0;
write = qc_is_listener(qc) ? 0 : 1;
}
else if (sizeof(QUIC_OPENSSL_COMPAT_SERVER_APPLICATION) - 1 == n &&
!strncmp(start, QUIC_OPENSSL_COMPAT_SERVER_APPLICATION, n)) {
level = ssl_encryption_application;
write = 1;
write = qc_is_listener(qc) ? 1 : 0;
}
else
goto leave;

View File

@ -949,7 +949,11 @@ static int qc_parse_pkt_frms(struct quic_conn *qc, struct quic_rx_packet *pkt,
goto err;
}
else {
/* TODO */
/* TODO NEW_TOKEN not implemented on client side.
* 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;
case QUIC_FT_STREAM_8 ... QUIC_FT_STREAM_F:
@ -1819,8 +1823,8 @@ static struct quic_conn *quic_rx_pkt_retrieve_conn(struct quic_rx_packet *pkt,
goto err;
qc = qc_new_conn(pkt->version, ipv4, &pkt->dcid, &pkt->scid, &token_odcid,
conn_id, &dgram->daddr, &pkt->saddr, 1,
!!pkt->token_len, l);
conn_id, &dgram->daddr, &pkt->saddr,
!!pkt->token_len, l, NULL);
if (qc == NULL) {
pool_free(pool_head_quic_connection_id, conn_id);
goto err;
@ -1877,25 +1881,25 @@ static struct quic_conn *quic_rx_pkt_retrieve_conn(struct quic_rx_packet *pkt,
/* Parse a QUIC packet starting at <pos>. Data won't be read after <end> even
* if the packet is incomplete. This function will populate fields of <pkt>
* instance, most notably its length. <dgram> is the UDP datagram which
* contains the parsed packet. <l> is the listener instance on which it was
* received.
* contains the parsed packet. <o> is the address object type address of the
* object which receives this received packet. <qc> is the QUIC connection,
* only valid for QUIC clients.
*
* Returns 0 on success else non-zero. Packet length is guaranteed to be set to
* the real packet value or to cover all data between <pos> and <end> : this is
* useful to reject a whole datagram.
*/
static int quic_rx_pkt_parse(struct quic_rx_packet *pkt,
static int quic_rx_pkt_parse(struct quic_conn *qc, struct quic_rx_packet *pkt,
unsigned char *pos, const unsigned char *end,
struct quic_dgram *dgram, struct listener *l)
struct quic_dgram *dgram, enum obj_type *o)
{
const unsigned char *beg = pos;
struct proxy *prx;
struct quic_counters *prx_counters;
struct listener *l = objt_listener(o);
TRACE_ENTER(QUIC_EV_CONN_LPKT);
prx = l->bind_conf->frontend;
prx_counters = EXTRA_COUNTERS_GET(prx->extra_counters_fe, &quic_stats_module);
prx_counters = qc_counters(o, &quic_stats_module);
if (end <= pos) {
TRACE_PROTO("Packet dropped", QUIC_EV_CONN_LPKT);
@ -1951,8 +1955,10 @@ static int quic_rx_pkt_parse(struct quic_rx_packet *pkt,
goto drop;
}
/* RFC9000 6. Version Negotiation */
if (!pkt->version) {
/* RFC9000 6. Version Negotiation. A Version Negotiation packet is
* sent only by servers.
*/
if (l && !pkt->version) {
/* unsupported version, send Negotiation packet */
if (send_version_negotiation(l->rx.fd, &dgram->saddr, pkt)) {
TRACE_ERROR("VN packet not sent", QUIC_EV_CONN_LPKT);
@ -1976,6 +1982,13 @@ static int quic_rx_pkt_parse(struct quic_rx_packet *pkt,
goto drop;
}
if (!l && pkt->token_len) {
/* A server must sent Initial packets with a null token length. */
TRACE_PROTO("Packet dropped",
QUIC_EV_CONN_LPKT, NULL, NULL, NULL, pkt->version);
goto drop;
}
pkt->token = pos;
pkt->token_len = token_len;
pos += pkt->token_len;
@ -2001,26 +2014,49 @@ static int quic_rx_pkt_parse(struct quic_rx_packet *pkt,
pkt->pn_offset = pos - beg;
pkt->len = pkt->pn_offset + len;
/* RFC 9000. Initial Datagram Size
*
* A server MUST discard an Initial packet that is carried in a UDP datagram
* with a payload that is smaller than the smallest allowed maximum datagram
* size of 1200 bytes.
*/
if (pkt->type == QUIC_PACKET_TYPE_INITIAL &&
dgram->len < QUIC_INITIAL_PACKET_MINLEN) {
TRACE_PROTO("RX too short datagram with an Initial packet", QUIC_EV_CONN_LPKT);
HA_ATOMIC_INC(&prx_counters->too_short_initial_dgram);
goto drop;
}
/* Interrupt parsing after packet length retrieval : this
* ensures that only the packet is dropped but not the whole
* datagram.
*/
if (pkt->type == QUIC_PACKET_TYPE_0RTT && !l->bind_conf->ssl_conf.early_data) {
TRACE_PROTO("RX 0-RTT packet not supported", QUIC_EV_CONN_LPKT);
goto drop;
if (pkt->type == QUIC_PACKET_TYPE_INITIAL) {
if (l) {
/* RFC 9000. Initial Datagram Size
*
* A server MUST discard an Initial packet that is carried in a UDP datagram
* with a payload that is smaller than the smallest allowed maximum datagram
* size of 1200 bytes.
*/
if (dgram->len < QUIC_INITIAL_PACKET_MINLEN) {
TRACE_PROTO("RX too short datagram with an Initial packet", QUIC_EV_CONN_LPKT);
HA_ATOMIC_INC(&prx_counters->too_short_initial_dgram);
goto drop;
}
}
else {
/* TODO: This is not clear if a too short RX datagram which carries ack-eliciting
* packets must be dropped by a client. If this is the case, this is not from
* here, but after having parsed the datagram frames.
*
* RFC 9000. Initial Datagram Size
*
* Similarly, a server MUST expand the payload of all UDP datagrams carrying
* ack-eliciting Initial packets to at least the smallest allowed maximum
* datagram size of 1200 bytes.
*/
if (!(qc->flags & QUIC_FL_CONN_SCID_RECEIVED)) {
qc->flags |= QUIC_FL_CONN_SCID_RECEIVED;
memcpy(qc->dcid.data, pkt->scid.data, pkt->scid.len);
qc->dcid.len = pkt->scid.len;
}
}
}
else if (pkt->type == QUIC_PACKET_TYPE_0RTT) {
/* O-RTT packet are not sent by servers. */
if (!l || !l->bind_conf->ssl_conf.early_data) {
TRACE_PROTO("RX 0-RTT packet not supported", QUIC_EV_CONN_LPKT);
goto drop;
}
}
}
else {
@ -2242,7 +2278,8 @@ static void qc_rx_pkt_handle(struct quic_conn *qc, struct quic_rx_packet *pkt,
* this function.
*
* If datagram has been received on a quic-conn owned FD, <from_qc> must be set
* to the connection instance. <li> is the attached listener. The caller is
* to the connection instance. <o> is the object type address of the object
* (listener or server) receiving the datagram. The caller is
* responsible to ensure that the first packet is destined to this connection
* by comparing CIDs.
*
@ -2254,7 +2291,7 @@ static void qc_rx_pkt_handle(struct quic_conn *qc, struct quic_rx_packet *pkt,
* the datagram may not have been parsed.
*/
int quic_dgram_parse(struct quic_dgram *dgram, struct quic_conn *from_qc,
struct listener *li)
enum obj_type *o)
{
struct quic_rx_packet *pkt;
struct quic_conn *qc = NULL;
@ -2292,7 +2329,7 @@ int quic_dgram_parse(struct quic_dgram *dgram, struct quic_conn *from_qc,
pkt->flags |= QUIC_FL_RX_PACKET_DGRAM_FIRST;
quic_rx_packet_refinc(pkt);
if (quic_rx_pkt_parse(pkt, pos, end, dgram, li))
if (quic_rx_pkt_parse(from_qc, pkt, pos, end, dgram, o))
goto next;
/* Search quic-conn instance for first packet of the datagram.
@ -2301,6 +2338,7 @@ int quic_dgram_parse(struct quic_dgram *dgram, struct quic_conn *from_qc,
*/
if (!qc) {
int new_tid = -1;
struct listener *li = objt_listener(o);
qc = from_qc ? from_qc : quic_rx_pkt_retrieve_conn(pkt, dgram, li, &new_tid);
/* qc is NULL if receiving a non Initial packet for an

View File

@ -87,13 +87,13 @@ int quic_sock_get_dst(struct connection *conn, struct sockaddr *addr, socklen_t
memcpy(addr, &qc->peer_addr, len);
} else {
struct sockaddr_storage *from;
struct listener *l = objt_listener(qc->target);
/* Return listener address if IP_PKTINFO or friends are not
* supported by the socket.
*/
BUG_ON(!qc->li);
from = is_addr(&qc->local_addr) ? &qc->local_addr :
&qc->li->rx.addr;
BUG_ON(!l);
from = is_addr(&qc->local_addr) ? &qc->local_addr : &l->rx.addr;
if (len > sizeof(*from))
len = sizeof(*from);
memcpy(addr, from, len);
@ -815,36 +815,30 @@ int qc_snd_buf(struct quic_conn *qc, const struct buffer *buf, size_t sz,
int qc_rcv_buf(struct quic_conn *qc)
{
struct sockaddr_storage saddr = {0}, daddr = {0};
struct quic_transport_params *params;
struct quic_dgram *new_dgram = NULL;
struct buffer buf = BUF_NULL;
size_t max_sz;
unsigned char *dgram_buf;
struct listener *l;
ssize_t ret = 0;
struct listener *l = objt_listener(qc->target);
/* Do not call this if quic-conn FD is uninitialized. */
BUG_ON(qc->fd < 0);
TRACE_ENTER(QUIC_EV_CONN_RCV, qc);
l = qc->li;
params = &l->bind_conf->quic_params;
max_sz = params->max_udp_payload_size;
do {
if (!b_alloc(&buf, DB_MUX_RX))
break; /* TODO subscribe for memory again available. */
b_reset(&buf);
BUG_ON(b_contig_space(&buf) < max_sz);
BUG_ON(b_contig_space(&buf) < qc->max_udp_payload);
/* Allocate datagram on first loop or after requeuing. */
if (!new_dgram && !(new_dgram = pool_alloc(pool_head_quic_dgram)))
break; /* TODO subscribe for memory again available. */
dgram_buf = (unsigned char *)b_tail(&buf);
ret = quic_recv(qc->fd, dgram_buf, max_sz,
ret = quic_recv(qc->fd, dgram_buf, qc->max_udp_payload,
(struct sockaddr *)&saddr, sizeof(saddr),
(struct sockaddr *)&daddr, sizeof(daddr),
get_net_port(&qc->local_addr));
@ -876,7 +870,7 @@ int qc_rcv_buf(struct quic_conn *qc)
continue;
}
if (!qc_check_dcid(qc, new_dgram->dcid, new_dgram->dcid_len)) {
if (l && !qc_check_dcid(qc, new_dgram->dcid, new_dgram->dcid_len)) {
/* Datagram received by error on the connection FD, dispatch it
* to its associated quic-conn.
*
@ -934,7 +928,7 @@ int qc_rcv_buf(struct quic_conn *qc)
continue;
}
quic_dgram_parse(new_dgram, qc, qc->li);
quic_dgram_parse(new_dgram, qc, qc->target);
/* A datagram must always be consumed after quic_parse_dgram(). */
BUG_ON(new_dgram->buf);
} while (ret > 0);
@ -956,11 +950,13 @@ int qc_rcv_buf(struct quic_conn *qc)
*
* Return the socket FD or a negative error code. On error, socket is marked as
* uninitialized.
* Note: This function must not be run for backends connection.
*/
void qc_alloc_fd(struct quic_conn *qc, const struct sockaddr_storage *src,
const struct sockaddr_storage *dst)
{
struct bind_conf *bc = qc->li->bind_conf;
struct listener *l = __objt_listener(qc->target);
struct bind_conf *bc = l->bind_conf;
struct proxy *p = bc->frontend;
int fd = -1;
int ret;
@ -1013,7 +1009,7 @@ void qc_alloc_fd(struct quic_conn *qc, const struct sockaddr_storage *src,
}
/* Fallback to listener socket for this receiver instance. */
HA_ATOMIC_STORE(&qc->li->rx.quic_mode, QUIC_SOCK_MODE_LSTNR);
HA_ATOMIC_STORE(&l->rx.quic_mode, QUIC_SOCK_MODE_LSTNR);
}
goto err;
}
@ -1067,13 +1063,14 @@ struct quic_accept_queue *quic_accept_queues;
void quic_accept_push_qc(struct quic_conn *qc)
{
struct quic_accept_queue *queue = &quic_accept_queues[tid];
struct li_per_thread *lthr = &qc->li->per_thr[ti->ltid];
struct listener *l = __objt_listener(qc->target);
struct li_per_thread *lthr = &l->per_thr[ti->ltid];
/* A connection must only be accepted once per instance. */
BUG_ON(qc->flags & QUIC_FL_CONN_ACCEPT_REGISTERED);
BUG_ON(MT_LIST_INLIST(&qc->accept_list));
HA_ATOMIC_INC(&qc->li->rx.quic_curr_accept);
HA_ATOMIC_INC(&l->rx.quic_curr_accept);
qc->flags |= QUIC_FL_CONN_ACCEPT_REGISTERED;
/* 1. insert the listener in the accept queue

View File

@ -17,7 +17,7 @@ DECLARE_POOL(pool_head_quic_ssl_sock_ctx, "quic_ssl_sock_ctx", sizeof(struct ssl
* be set to 1 for a QUIC server, 0 for a client.
* Return 1 if succeeded, 0 if not.
*/
static int qc_ssl_set_quic_transport_params(struct quic_conn *qc,
static int qc_ssl_set_quic_transport_params(SSL *ssl, struct quic_conn *qc,
const struct quic_version *ver, int server)
{
int ret = 0;
@ -40,7 +40,7 @@ static int qc_ssl_set_quic_transport_params(struct quic_conn *qc,
goto leave;
}
if (!SSL_set_quic_transport_params(qc->xprt_ctx->ssl, in, *enclen)) {
if (!SSL_set_quic_transport_params(ssl, in, *enclen)) {
TRACE_ERROR("SSL_set_quic_transport_params() failed", QUIC_EV_CONN_RWSEC);
goto leave;
}
@ -274,16 +274,30 @@ write:
/* Set the transport parameters in the TLS stack. */
if (level == ssl_encryption_handshake && qc_is_listener(qc) &&
!qc_ssl_set_quic_transport_params(qc, ver, 1))
!qc_ssl_set_quic_transport_params(qc->xprt_ctx->ssl, qc, ver, 1))
goto leave;
keyupdate_init:
/* Store the secret provided by the TLS stack, required for keyupdate. */
if (level == ssl_encryption_application) {
struct quic_tls_kp *prv_rx = &qc->ku.prv_rx;
struct quic_tls_kp *nxt_rx = &qc->ku.nxt_rx;
struct quic_tls_kp *nxt_tx = &qc->ku.nxt_tx;
#if !defined(USE_QUIC_OPENSSL_COMPAT) && !defined(HAVE_OPENSSL_QUIC)
if (!qc_is_listener(qc)) {
const unsigned char *tp;
size_t tplen;
SSL_get_peer_quic_transport_params(ssl, &tp, &tplen);
if (!tplen || !quic_transport_params_store(qc, 1,tp, tp + tplen)) {
TRACE_ERROR("Could not parse remote transport paratemers",
QUIC_EV_CONN_RWSEC, qc);
goto leave;
}
}
#endif
/* Store the secret provided by the TLS stack, required for keyupdate. */
if (rx) {
if (!(rx->secret = pool_alloc(pool_head_quic_tls_secret))) {
TRACE_ERROR("Could not allocate RX Application secrete keys", QUIC_EV_CONN_RWSEC, qc);
@ -570,7 +584,7 @@ static int ha_quic_ossl_got_transport_params(SSL *ssl, const unsigned char *para
}
else {
if (!quic_transport_params_store(qc, 0, params, params + params_len) ||
!qc_ssl_set_quic_transport_params(qc, ver, 1))
!qc_ssl_set_quic_transport_params(ssl, qc, ver, 1))
goto err;
}
@ -754,6 +768,38 @@ int ssl_quic_initial_ctx(struct bind_conf *bind_conf)
return cfgerr;
}
/* Allocate a TLS context for a QUIC server.
* Return this context if succeeded, NULL if failed.
*/
SSL_CTX *ssl_quic_srv_new_ssl_ctx(void)
{
SSL_CTX *ctx = NULL;
/* XXX TODO: check this: XXX */
long options =
(SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
SSL_OP_SINGLE_ECDH_USE |
SSL_OP_CIPHER_SERVER_PREFERENCE;
ctx = SSL_CTX_new(TLS_client_method());
if (!ctx)
goto err;
SSL_CTX_set_options(ctx, options);
SSL_CTX_set_min_proto_version(ctx, TLS1_3_VERSION);
SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION);
#ifdef USE_QUIC_OPENSSL_COMPAT
if (!quic_tls_compat_init(NULL, ctx))
goto err;
#endif
leave:
return ctx;
err:
SSL_CTX_free(ctx);
ctx = NULL;
goto leave;
}
/* This function gives the detail of the SSL error. It is used only
* if the debug mode and the verbose mode are activated. It dump all
* the SSL error until the stack was empty.
@ -885,28 +931,54 @@ static int qc_ssl_provide_quic_data(struct ncbuf *ncbuf,
* provided by the stack. This happens after having received the peer
* handshake level CRYPTO data which are validated by the TLS stack.
*/
if (qc->li->bind_conf->ssl_conf.early_data &&
(!qc->ael || !qc->ael->tls_ctx.rx.secret)) {
TRACE_PROTO("SSL handshake in progress",
QUIC_EV_CONN_IO_CB, qc, &state, &ssl_err);
goto out;
}
else {
TRACE_PROTO("SSL handshake OK", QUIC_EV_CONN_IO_CB, qc, &state);
if (qc_is_listener(qc)) {
if (__objt_listener(qc->target)->bind_conf->ssl_conf.early_data &&
(!qc->ael || !qc->ael->tls_ctx.rx.secret)) {
TRACE_PROTO("SSL handshake in progress",
QUIC_EV_CONN_IO_CB, qc, &state, &ssl_err);
goto out;
}
else {
TRACE_PROTO("SSL handshake OK", QUIC_EV_CONN_IO_CB, qc, &state);
}
}
#endif
/* Check the alpn could be negotiated */
if (!qc->app_ops) {
TRACE_ERROR("No negotiated ALPN", QUIC_EV_CONN_IO_CB, qc, &state);
quic_set_tls_alert(qc, SSL_AD_NO_APPLICATION_PROTOCOL);
goto leave;
if (qc_is_listener(qc)) {
if (!qc->app_ops) {
TRACE_ERROR("No negotiated ALPN", QUIC_EV_CONN_IO_CB, qc, &state);
quic_set_tls_alert(qc, SSL_AD_NO_APPLICATION_PROTOCOL);
goto leave;
}
}
else {
const unsigned char *alpn;
size_t alpn_len;
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) ||
!quic_set_app_ops(qc, alpn, alpn_len)) {
TRACE_ERROR("No negotiated ALPN", QUIC_EV_CONN_IO_CB, qc, &state);
quic_set_tls_alert(qc, SSL_AD_NO_APPLICATION_PROTOCOL);
goto leave;
}
if (conn_create_mux(ctx->conn, NULL) < 0) {
TRACE_ERROR("mux creation failed", QUIC_EV_CONN_IO_CB, qc, &state);
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;
}
/* I/O callback switch */
qc->wait_event.tasklet->process = quic_conn_app_io_cb;
qc->flags |= QUIC_FL_CONN_NEED_POST_HANDSHAKE_FRMS;
if (qc_is_listener(ctx->qc)) {
qc->flags |= QUIC_FL_CONN_NEED_POST_HANDSHAKE_FRMS;
struct listener *l = __objt_listener(qc->target);
/* I/O callback switch */
qc->wait_event.tasklet->process = quic_conn_app_io_cb;
qc->state = QUIC_HS_ST_CONFIRMED;
if (!(qc->flags & QUIC_FL_CONN_ACCEPT_REGISTERED)) {
@ -920,8 +992,8 @@ static int qc_ssl_provide_quic_data(struct ncbuf *ncbuf,
tasklet_wakeup(qc->wait_event.tasklet);
}
BUG_ON(qc->li->rx.quic_curr_handshake == 0);
HA_ATOMIC_DEC(&qc->li->rx.quic_curr_handshake);
BUG_ON(l->rx.quic_curr_handshake == 0);
HA_ATOMIC_DEC(&l->rx.quic_curr_handshake);
}
else {
qc->state = QUIC_HS_ST_COMPLETE;
@ -1041,7 +1113,8 @@ int quic_ssl_set_tls_cbs(SSL *ssl)
* Return 0 if succeeded, -1 if not. If failed, sets the ->err_code member of <qc->conn> to
* CO_ER_SSL_NO_MEM.
*/
static int qc_ssl_sess_init(struct quic_conn *qc, SSL_CTX *ssl_ctx, SSL **ssl)
static int qc_ssl_sess_init(struct quic_conn *qc, SSL_CTX *ssl_ctx, SSL **ssl,
struct connection *conn, int server)
{
int retry, ret = -1;
@ -1059,6 +1132,7 @@ static int qc_ssl_sess_init(struct quic_conn *qc, SSL_CTX *ssl_ctx, SSL **ssl)
}
if (!SSL_set_ex_data(*ssl, ssl_qc_app_data_index, qc) ||
(!server && !SSL_set_ex_data(*ssl, ssl_app_data_index, conn)) ||
!quic_ssl_set_tls_cbs(*ssl)) {
SSL_free(*ssl);
*ssl = NULL;
@ -1121,10 +1195,9 @@ static int qc_set_quic_early_data_enabled(struct quic_conn *qc, SSL *ssl)
*
* Returns 0 on success else non-zero.
*/
int qc_alloc_ssl_sock_ctx(struct quic_conn *qc)
int qc_alloc_ssl_sock_ctx(struct quic_conn *qc, struct connection *conn)
{
int ret = 0;
struct bind_conf *bc = qc->li->bind_conf;
struct ssl_sock_ctx *ctx = NULL;
TRACE_ENTER(QUIC_EV_CONN_NEW, qc);
@ -1135,7 +1208,7 @@ int qc_alloc_ssl_sock_ctx(struct quic_conn *qc)
goto err;
}
ctx->conn = NULL;
ctx->conn = conn;
ctx->bio = NULL;
ctx->xprt = NULL;
ctx->xprt_ctx = NULL;
@ -1148,7 +1221,9 @@ int qc_alloc_ssl_sock_ctx(struct quic_conn *qc)
ctx->qc = qc;
if (qc_is_listener(qc)) {
if (qc_ssl_sess_init(qc, bc->initial_ctx, &ctx->ssl) == -1)
struct bind_conf *bc = __objt_listener(qc->target)->bind_conf;
if (qc_ssl_sess_init(qc, bc->initial_ctx, &ctx->ssl, NULL, 1) == -1)
goto err;
#if (HA_OPENSSL_VERSION_NUMBER >= 0x10101000L) && defined(HAVE_SSL_0RTT_QUIC)
/* Enabling 0-RTT */
@ -1158,6 +1233,36 @@ int qc_alloc_ssl_sock_ctx(struct quic_conn *qc)
SSL_set_accept_state(ctx->ssl);
}
else {
int ssl_err;
struct server *srv = __objt_server(ctx->conn->target);
if (qc_ssl_sess_init(qc, srv->ssl_ctx.ctx, &ctx->ssl, conn, 0) == -1)
goto err;
if (!qc_ssl_set_quic_transport_params(ctx->ssl, qc, quic_version_1, 0))
goto err;
SSL_set_connect_state(ctx->ssl);
ssl_err = SSL_do_handshake(ctx->ssl);
TRACE_PROTO("SSL_do_handshake() called", QUIC_EV_CONN_NEW, qc, &ssl_err);
if (ssl_err != 1) {
ssl_err = SSL_get_error(ctx->ssl, ssl_err);
if (ssl_err == SSL_ERROR_WANT_READ || ssl_err == SSL_ERROR_WANT_WRITE) {
TRACE_PROTO("SSL handshake in progress", QUIC_EV_CONN_NEW, qc, &ssl_err);
}
else {
TRACE_ERROR("SSL handshake error", QUIC_EV_CONN_NEW, qc, &ssl_err);
HA_ATOMIC_INC(&qc->prx_counters->hdshk_fail);
qc_ssl_dump_errors(ctx->conn);
ERR_clear_error();
goto err;
}
}
/* Wakeup the handshake I/O handler tasklet asap to send data */
tasklet_wakeup(qc->wait_event.tasklet);
}
ctx->xprt = xprt_get(XPRT_QUIC);

View File

@ -213,9 +213,18 @@ static int quic_transport_param_dec_version_info(struct tp_version_information *
return 0;
}
if (server)
/* TODO: not supported */
if (server) {
int i;
for (i = 0; i < quic_versions_nb; i++) {
if (tp->chosen == quic_versions[i].num) {
tp->negotiated_version = &quic_versions[i];
goto out;
}
}
return 0;
}
for (ver = others; ver < end; ver += 4) {
if (!tp->negotiated_version) {
@ -513,8 +522,21 @@ static int quic_transport_param_enc_version_info(unsigned char **buf,
memcpy(*buf, &ver, sizeof ver);
*buf += sizeof ver;
/* For servers: all supported version, chosen included */
for (i = 0; i < quic_versions_nb; i++) {
ver = htonl(quic_versions[i].num);
if (server) {
for (i = 0; i < quic_versions_nb; i++) {
ver = htonl(quic_versions[i].num);
memcpy(*buf, &ver, sizeof ver);
*buf += sizeof ver;
}
}
else {
ver = htonl(quic_version_1->num);
memcpy(*buf, &ver, sizeof ver);
*buf += sizeof ver;
ver = htonl(quic_version_2->num);
memcpy(*buf, &ver, sizeof ver);
*buf += sizeof ver;
ver = htonl(quic_version_draft_29->num);
memcpy(*buf, &ver, sizeof ver);
*buf += sizeof ver;
}
@ -806,3 +828,21 @@ int qc_lstnr_params_init(struct quic_conn *qc,
return 1;
}
/* QUIC client (or haproxy server) only function.
* Initialize the local transport parameters <rx_params> from <srv_params>
* coming from configuration and source connection ID).
* Never fails.
*/
void qc_srv_params_init(struct quic_conn *qc,
const struct quic_transport_params *srv_params,
const unsigned char *scid, size_t scidlen)
{
struct quic_transport_params *rx_params = &qc->rx.params;
/* Copy the transport parameters. */
*rx_params = *srv_params;
/* Copy the initial source connection ID. */
memcpy(rx_params->initial_source_connection_id.data, scid, scidlen);
rx_params->initial_source_connection_id.len = scidlen;
TRACE_PROTO("\nRX(local) transp. params.", QUIC_EV_TRANSP_PARAMS, qc, rx_params);
}

View File

@ -117,6 +117,13 @@ static void quic_trace(enum trace_level level, uint64_t mask, const struct trace
chunk_appendf(&trace_buf, " : qc@%p idle_timer_task@%p flags=0x%x",
qc, qc->idle_timer_task, qc->flags);
if (mask & QUIC_EV_CONN_NEW) {
const int *ssl_err = a2;
if (ssl_err)
chunk_appendf(&trace_buf, " ssl_err=%d", *ssl_err);
}
if (mask & QUIC_EV_CONN_INIT) {
chunk_appendf(&trace_buf, "\n odcid");
quic_cid_dump(&trace_buf, &qc->odcid);

View File

@ -288,8 +288,10 @@ static int qc_send_ppkts(struct buffer *buf, struct ssl_sock_ctx *ctx)
int ret = 0;
struct quic_conn *qc;
char skip_sendto = 0;
struct listener *l;
qc = ctx->qc;
l = objt_listener(qc->target);
TRACE_ENTER(QUIC_EV_CONN_SPPKTS, qc);
while (b_contig_data(buf, 0)) {
unsigned char *pos;
@ -305,7 +307,11 @@ static int qc_send_ppkts(struct buffer *buf, struct ssl_sock_ctx *ctx)
/* If datagram bigger than MTU, several ones were encoded for GSO usage. */
if (dglen > qc->path->mtu) {
if (likely(!(HA_ATOMIC_LOAD(&qc->li->flags) & LI_F_UDP_GSO_NOTSUPP))) {
/* TODO: note that at this time for connection to backends this
* part is not run because no more than an MTU has been prepared for
* such connections (dglen <= qc->path->mtu). So, here l is not NULL.
*/
if (likely(!(HA_ATOMIC_LOAD(&l->flags) & LI_F_UDP_GSO_NOTSUPP))) {
TRACE_PROTO("send multiple datagrams with GSO", QUIC_EV_CONN_SPPKTS, qc);
gso = qc->path->mtu;
}
@ -327,11 +333,15 @@ static int qc_send_ppkts(struct buffer *buf, struct ssl_sock_ctx *ctx)
int ret = qc_snd_buf(qc, &tmpbuf, tmpbuf.data, 0, gso);
if (ret < 0) {
if (gso && ret == -EIO) {
/* TODO: note that at this time for connection to backends this
* part is not run because no more than an MTU has been
* prepared for such connections (l is not NULL).
*/
/* Disable permanently UDP GSO for this listener.
* Retry standard emission.
*/
TRACE_ERROR("mark listener UDP GSO as unsupported", QUIC_EV_CONN_SPPKTS, qc, first_pkt);
HA_ATOMIC_OR(&qc->li->flags, LI_F_UDP_GSO_NOTSUPP);
HA_ATOMIC_OR(&l->flags, LI_F_UDP_GSO_NOTSUPP);
continue;
}
@ -576,6 +586,7 @@ static int qc_prep_pkts(struct quic_conn *qc, struct buffer *buf,
int dgram_cnt = 0;
/* Restrict GSO emission to comply with sendmsg limitation. See QUIC_MAX_GSO_DGRAMS for more details. */
uchar gso_dgram_cnt = 0;
struct listener *l = objt_listener(qc->target);
TRACE_ENTER(QUIC_EV_CONN_IO_CB, qc);
/* Currently qc_prep_pkts() does not handle buffer wrapping so the
@ -765,11 +776,13 @@ static int qc_prep_pkts(struct quic_conn *qc, struct buffer *buf,
prv_pkt = cur_pkt;
}
else if (!(quic_tune.options & QUIC_TUNE_NO_UDP_GSO) &&
!(HA_ATOMIC_LOAD(&qc->li->flags) & LI_F_UDP_GSO_NOTSUPP) &&
dglen == qc->path->mtu &&
(char *)end < b_wrap(buf) &&
++gso_dgram_cnt < QUIC_MAX_GSO_DGRAMS) {
++gso_dgram_cnt < QUIC_MAX_GSO_DGRAMS &&
l && !(HA_ATOMIC_LOAD(&l->flags) & LI_F_UDP_GSO_NOTSUPP)) {
/* TODO: note that for backends GSO is not used. No more than
* an MTU is prepared.
*/
/* A datagram covering the full MTU has been
* built, use GSO to built next entry. Do not
* reserve extra space for datagram header.

View File

@ -37,6 +37,7 @@
#include <haproxy/protocol.h>
#include <haproxy/proxy.h>
#include <haproxy/queue.h>
#include <haproxy/quic_tp.h>
#include <haproxy/resolvers.h>
#include <haproxy/sample.h>
#include <haproxy/sc_strm.h>
@ -3589,6 +3590,7 @@ static int _srv_parse_init(struct server **srv, char **args, int *cur_arg,
(parse_flags & SRV_PARSE_INITIAL_RESOLVE ? PA_O_RESOLVE : 0) | PA_O_PORT_OK |
(parse_flags & SRV_PARSE_IN_PEER_SECTION ? PA_O_PORT_MAND : PA_O_PORT_OFS) |
PA_O_STREAM | PA_O_DGRAM | PA_O_XPRT);
if (!sk) {
ha_alert("%s\n", errmsg);
err_code |= ERR_ALERT | ERR_FATAL;
@ -3596,6 +3598,21 @@ static int _srv_parse_init(struct server **srv, char **args, int *cur_arg,
goto out;
}
#ifdef USE_QUIC
if (srv_is_quic(newsrv)) {
if (!experimental_directives_allowed) {
ha_alert("QUIC is experimental for server '%s',"
" must be allowed via a global 'expose-experimental-directives'\n",
newsrv->id);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
newsrv->xprt = xprt_get(XPRT_QUIC);
quic_transport_params_init(&newsrv->quic_params, 0);
}
#endif
if (!port1 || !port2) {
if (sk->ss_family != AF_CUST_RHTTP_SRV) {
/* no port specified, +offset, -offset */
@ -3819,6 +3836,15 @@ 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);
return 0;

View File

@ -265,7 +265,8 @@ static int sock_handle_system_err(struct connection *conn, struct proxy *be)
* upper level is set as SF_ERR_NONE; -1 on failure, stream_err is set to
* appropriate value.
*/
int sock_create_server_socket(struct connection *conn, struct proxy *be, int *stream_err)
int sock_create_server_socket(struct connection *conn, struct proxy *be,
enum proto_type proto_type, int sock_type, int *stream_err)
{
const struct netns_entry *ns = NULL;
const struct protocol *proto;
@ -279,9 +280,9 @@ int sock_create_server_socket(struct connection *conn, struct proxy *be, int *st
ns = __objt_server(conn->target)->netns;
}
#endif
proto = protocol_lookup(conn->dst->ss_family, PROTO_TYPE_STREAM, conn->ctrl->sock_prot == IPPROTO_MPTCP);
proto = protocol_lookup(conn->dst->ss_family, proto_type , conn->ctrl->sock_prot == IPPROTO_MPTCP);
BUG_ON(!proto);
sock_fd = my_socketat(ns, proto->fam->sock_domain, SOCK_STREAM, proto->sock_prot);
sock_fd = my_socketat(ns, proto->fam->sock_domain, sock_type, proto->sock_prot);
/* at first, handle common to all proto families system limits and permission related errors */
if (sock_fd == -1) {

View File

@ -2601,8 +2601,9 @@ int ckch_inst_rebuild(struct ckch_store *ckch_store, struct ckch_inst *ckchi,
fcount = ckchi->crtlist_entry->fcount;
}
if (ckchi->is_server_instance)
errcode |= ckch_inst_new_load_srv_store(ckch_store->path, ckch_store, new_inst, err);
if (ckchi->is_server_instance) {
errcode |= ckch_inst_new_load_srv_store(ckch_store->path, ckch_store, new_inst, err, srv_is_quic(ckchi->server));
}
else
errcode |= ckch_inst_new_load_store(ckch_store->path, ckch_store, ckchi->bind_conf, ckchi->ssl_conf, sni_filter, fcount, ckchi->is_default, new_inst, err);

View File

@ -177,7 +177,7 @@ int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg)
s = __objt_listener(conn->target)->bind_conf;
#ifdef USE_QUIC
else if (qc)
s = qc->li->bind_conf;
s = __objt_listener(qc->target)->bind_conf;
#endif /* USE_QUIC */
if (!s) {

View File

@ -127,7 +127,7 @@ int ssl_sock_ocsp_stapling_cbk(SSL *ssl, void *arg)
struct quic_conn *qc = SSL_get_ex_data(ssl, ssl_qc_app_data_index);
/* null if not a listener */
li = qc->li;
li = objt_listener(qc->target);
}
#endif

View File

@ -65,6 +65,7 @@
#include <haproxy/proxy.h>
#include <haproxy/quic_conn.h>
#include <haproxy/quic_openssl_compat.h>
#include <haproxy/quic_ssl.h>
#include <haproxy/quic_tp.h>
#include <haproxy/sample.h>
#include <haproxy/sc_strm.h>
@ -925,7 +926,7 @@ static int ssl_tlsext_ticket_key_cb(SSL *s, unsigned char key_name[16], unsigned
ref = __objt_listener(conn->target)->bind_conf->keys_ref;
#ifdef USE_QUIC
else if (qc)
ref = qc->li->bind_conf->keys_ref;
ref = __objt_listener(qc->target)->bind_conf->keys_ref;
#endif
if (!ref) {
@ -1481,7 +1482,7 @@ int ssl_sock_bind_verifycbk(int ok, X509_STORE_CTX *x_store)
else {
qc = SSL_get_ex_data(ssl, ssl_qc_app_data_index);
BUG_ON(!qc); /* Must never happen */
bind_conf = qc->li->bind_conf;
bind_conf = __objt_listener(qc->target)->bind_conf;
ctx = qc->xprt_ctx;
}
#endif
@ -3039,6 +3040,20 @@ error:
return errcode;
}
#ifdef USE_QUIC
static inline SSL_CTX *ssl_sock_new_ssl_ctx(int is_quic)
{
if (is_quic)
return ssl_quic_srv_new_ssl_ctx();
else
return SSL_CTX_new(SSLv23_client_method());
}
#else
static inline SSL_CTX *ssl_sock_new_ssl_ctx(int is_quic)
{
return SSL_CTX_new(SSLv23_client_method());
}
#endif
/*
* This function allocate a ckch_inst that will be used on the backend side
@ -3050,7 +3065,7 @@ error:
* ERR_WARN if a warning is available into err
*/
int ckch_inst_new_load_srv_store(const char *path, struct ckch_store *ckchs,
struct ckch_inst **ckchi, char **err)
struct ckch_inst **ckchi, char **err, int is_quic)
{
SSL_CTX *ctx;
struct ckch_data *data;
@ -3064,7 +3079,7 @@ int ckch_inst_new_load_srv_store(const char *path, struct ckch_store *ckchs,
data = ckchs->data;
ctx = SSL_CTX_new(SSLv23_client_method());
ctx = ssl_sock_new_ssl_ctx(is_quic);
if (!ctx) {
memprintf(err, "%sunable to allocate SSL context for cert '%s'.\n",
err && *err ? *err : "", path);
@ -3135,7 +3150,8 @@ static int ssl_sock_load_srv_ckchs(const char *path, struct ckch_store *ckchs,
int errcode = 0;
/* we found the ckchs in the tree, we can use it directly */
errcode |= ckch_inst_new_load_srv_store(path, ckchs, ckch_inst, err);
errcode |= ckch_inst_new_load_srv_store(path, ckchs, ckch_inst, err,
srv_is_quic(server));
if (errcode & ERR_CODE)
return errcode;
@ -4398,7 +4414,9 @@ int ssl_sock_prepare_srv_ctx(struct server *srv)
return cfgerr;
}
}
if (srv->use_ssl == 1)
/* The QUIC server xprt has already been set. */
if (srv->use_ssl == 1 && !srv_is_quic(srv))
srv->xprt = &ssl_sock;
if (srv->ssl_ctx.client_crt) {
@ -4425,7 +4443,7 @@ int ssl_sock_prepare_srv_ctx(struct server *srv)
/* The context will be uninitialized if there wasn't any "cert" option
* in the server line. */
if (!ctx) {
ctx = SSL_CTX_new(SSLv23_client_method());
ctx = ssl_sock_new_ssl_ctx(srv_is_quic(srv));
if (!ctx) {
ha_alert("unable to allocate ssl context.\n");
cfgerr++;

View File

@ -111,10 +111,25 @@ static int quic_conn_unsubscribe(struct connection *conn, void *xprt_ctx, int ev
*/
static int qc_conn_init(struct connection *conn, void **xprt_ctx)
{
struct quic_conn *qc = conn->handle.qc;
int ret = -1;
struct quic_conn *qc = NULL;
TRACE_ENTER(QUIC_EV_CONN_NEW, qc);
if (objt_listener(conn->target)) {
qc = conn->handle.qc;
}
else {
int ipv4 = conn->dst->ss_family == AF_INET;
struct server *srv = objt_server(conn->target);
qc = qc_new_conn(quic_version_1, ipv4, NULL, NULL, NULL,
NULL, NULL, &srv->addr, 0, srv, conn);
}
if (!qc)
goto out;
ret = 0;
/* Ensure thread connection migration is finalized ASAP. */
if (qc->flags & QUIC_FL_CONN_TID_REBIND)
qc_finalize_tid_rebind(qc);
@ -128,7 +143,7 @@ static int qc_conn_init(struct connection *conn, void **xprt_ctx)
out:
TRACE_LEAVE(QUIC_EV_CONN_NEW, qc);
return 0;
return ret;
}
/* Start the QUIC transport layer */
@ -140,8 +155,13 @@ static int qc_xprt_start(struct connection *conn, void *ctx)
qc = conn->handle.qc;
TRACE_ENTER(QUIC_EV_CONN_NEW, qc);
/* mux-quic can now be considered ready. */
qc->mux_state = QC_MUX_READY;
if (objt_listener(conn->target)) {
/* mux-quic can now be considered ready. */
qc->mux_state = QC_MUX_READY;
}
else {
conn->flags |= CO_FL_SSL_WAIT_HS | CO_FL_WAIT_L6_CONN;
}
/* Schedule quic-conn to ensure post handshake frames are emitted. This
* is not done for 0-RTT as xprt->start happens before handshake
@ -178,6 +198,8 @@ static struct xprt_ops ssl_quic = {
.start = qc_xprt_start,
.prepare_bind_conf = ssl_sock_prepare_bind_conf,
.destroy_bind_conf = ssl_sock_destroy_bind_conf,
.prepare_srv = ssl_sock_prepare_srv_ctx,
.destroy_srv = ssl_sock_free_srv_ctx,
.get_alpn = ssl_sock_get_alpn,
.get_ssl_sock_ctx = qc_get_ssl_sock_ctx,
.dump_info = qc_xprt_dump_info,