Compare commits

...

32 Commits

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

config : proxy 'xxxx' : MUX protocol 'quic' is not usable for server 'yyyy'
2025-06-11 12:10:41 +02:00
Frederic Lecaille
24b0109acb 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 12:10:17 +02:00
Frederic Lecaille
a80e93402e 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 12:10:16 +02:00
Frederic Lecaille
09335f9778 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 12:10:15 +02:00
Frederic Lecaille
2d986e6f01 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 12:10:15 +02:00
Frederic Lecaille
779cc1ea3d 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 12:09:39 +02:00
Frederic Lecaille
81593648cf 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 11:39:49 +02:00
Frederic Lecaille
32f9a389b8 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 11:39:48 +02:00
Frederic Lecaille
78023111ee 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 11:39:47 +02:00
Frederic Lecaille
a00d559eab 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 11:39:46 +02:00
Frederic Lecaille
8890d4ae86 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 11:39:25 +02:00
Frederic Lecaille
32831b42e1 MINOR: h3-be: Correctly retrieve h3 counters
This is done using qc_counters() function which supports also QUIC servers.
2025-06-11 11:39:24 +02:00
Frederic Lecaille
766150f128 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 11:39:23 +02:00
Frederic Lecaille
8059d5c998 MINOR: quic-be: Mux initialization
Reset the flags which denotes that the connection is waiting for a TLS handshake
completion. This must be accomplished before the mux creation.
Check the alpn has been successfully negotiated.
Then, finally create the mux. Note that the ->mux_proto of the server must be
set before calling conn_create_mux().
2025-06-11 11:39:22 +02:00
Frederic Lecaille
c507d3d8c0 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 11:39:02 +02:00
Frederic Lecaille
fb8c49a7dc 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 11:39:01 +02:00
Frederic Lecaille
228b8ec44b 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 11:38:44 +02:00
Frederic Lecaille
ab26309488 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 11:38:43 +02:00
Frederic Lecaille
568309131c 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 11:38:39 +02:00
Frederic Lecaille
3ebda4e143 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 11:37:14 +02:00
Frederic Lecaille
61454f5a52 MINOR: quic-be: Prevent the MUX to send/receive data
Such actions must be interrupted until the handshake completion.
2025-06-11 11:26:32 +02:00
Frederic Lecaille
3afe646523 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 11:26:32 +02:00
Frederic Lecaille
86d862a426 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 11:26:32 +02:00
Frederic Lecaille
a743400717 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 11:26:32 +02:00
Frederic Lecaille
d273ceba9b 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 10:47:43 +02:00
Frederic Lecaille
cc5ae9a29b MINOR: quic-be: ssl_sock contexts allocation and misc adaptations
Implement ssl_sock_new_ssl_ctx() to allocate a SSL server context as this is currently
done for TCP servers and also for QUIC servers depending on the <is_quic> boolean value
passed as new parameter. For QUIC servers, this function calls ssl_quic_srv_new_ssl_ctx()
which is specific to QUIC.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
e46e8e2bd3 MINOR: quic-be: Correct the QUIC protocol lookup
From connect_server(), QUIC protocol could not be retreived by protocol_lookup()
because of the PROTO_TYPE_STREAM default passed as argument. In place to support
QUIC srv->addr_type.proto_type may be safely passed.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
efbac62ded MINOR: quic-be: Add a function for the TLS context allocations
Implement ssl_quic_srv_new_ssl_ctx() whose aim is to allocate a TLS context
for QUIC servers.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
02f85dd6af MINOR: quic-be: QUIC server xprt already set when preparing their CTXs
The QUIC servers xprts have already been set at server line parsing time.
This patch prevents the QUIC servers xprts to be reset to <ssl_sock> value which is
the value used for SSL/TCP connections.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
e124a66c90 MINOR: quic-be: QUIC backend XPRT and transport parameters init during parsing
Add ->quic_params new member to server struct.
Add inlined function srv_is_quic() which to identifies the servers which are QUIC servers.
Also set the ->xprt member of the server being initialized and initialize asap its
transport parameters from _srv_parse_init().
Force the use of "expose-experimental-directives" global parameter to use QUIC
addresses on "server" lines.
2025-06-10 10:57:25 +02:00
Frederic Lecaille
438bb236cf MINOR: quic-be: Call ->prepare_srv() callback at parsing time
This XPRT callback is called from check_config_validity() after the configuration
has been parsed to initialize all the SSL server contexts.

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

View File

@ -144,6 +144,7 @@ struct qc_stream_rxbuf {
struct qcs {
struct qcc *qcc;
struct sedesc *sd;
struct session *sess;
uint32_t flags; /* QC_SF_* */
enum qcs_state st; /* QC_SS_* state */
void *ctx; /* app-ops context */

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;
}

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) &&

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>
@ -2513,7 +2514,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 +2530,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

@ -134,6 +134,8 @@ 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->sess = NULL;
qcs->flags = QC_SF_NONE;
qcs->st = QC_SS_IDLE;
qcs->ctx = NULL;
@ -3406,6 +3408,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);
@ -3534,6 +3537,22 @@ static int qmux_init(struct connection *conn, struct proxy *prx,
/* Register conn for idle front closing. This is done once everything is allocated. */
if (!conn_is_back(conn))
LIST_APPEND(&mux_stopping_data[tid].list, &conn->stopping_list);
else {
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;
qcs->sess = sess;
qcc->nb_sc++;
}
/* init read cycle */
tasklet_wakeup(qcc->wait_event.tasklet);
@ -4149,6 +4168,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,71 +413,23 @@ 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);
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

@ -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
@ -1028,11 +1067,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 +1092,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 +1106,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 +1131,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 +1144,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 +1165,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 +1181,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 +1236,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 +1276,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 +1291,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 +1329,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 +1348,7 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
return qc;
err:
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 +1385,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 +1515,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 +1586,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 +1995,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

@ -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

@ -1819,8 +1819,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 +1877,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 +1951,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 +1978,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 +2010,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 +2274,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 +2287,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 +2325,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 +2334,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,44 @@ 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;
TRACE_ENTER(QUIC_EV_CONN_NEW);
ctx = SSL_CTX_new(TLS_client_method());
if (!ctx) {
TRACE_ERROR("Could not allocate a new TLS context", QUIC_EV_CONN_NEW);
goto leave;
}
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:
TRACE_LEAVE(QUIC_EV_CONN_NEW);
return ctx;
err:
SSL_CTX_free(ctx);
ctx = NULL;
TRACE_DEVEL("leaving on error", QUIC_EV_CONN_NEW);
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 +937,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;
struct server *s = objt_server(ctx->conn->target);
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;
}
s->mux_proto = get_mux_proto(ist("quic"));
if (conn_create_mux(ctx->conn, NULL) < 0) {
TRACE_ERROR("mux creation failed", QUIC_EV_CONN_IO_CB, qc, &state);
goto leave;
}
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 +998,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 +1119,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 +1138,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 +1201,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 +1214,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 +1227,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 +1239,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 */

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,