mirror of
https://github.com/haproxy/haproxy.git
synced 2026-04-15 21:59:41 -04:00
In QUIC, TLS handshake messages such as ClientHello are encapsulated in CRYPTO frames. Each QUIC implementation can split the content in several frames of random sizes. In fact, this feature is now used by several clients, based on chrome so-called "Chaos protection" mechanism : https://quiche.googlesource.com/quiche/+/cb6b51054274cb2c939264faf34a1776e0a5bab7 To support this, haproxy uses a ncbuf storage to store received CRYPTO frames before passing it to the SSL library. However, this storage suffers from a limitation as gaps between two filled blocks cannot be smaller than 8 bytes. Thus, depending on the size of received CRYPTO frames and their order, ncbuf may not be sufficient. Over time, several mechanisms were implemented in haproxy QUIC frames parsing to overcome the ncbuf limitation. However, reports recently highlight that with some clients haproxy is not able to deal with CRYPTO frames reception. In particular, this is the case with the latest ngtcp2 release, which implements a similar chaos protection mechanism via the following patch. It also seems that this impacts haproxy interaction with firefox. commit 89c29fd8611d5e6d2f6b1f475c5e3494c376028c Author: Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com> Date: Mon Aug 4 22:48:06 2025 +0900 Crumble Client Initial CRYPTO (aka chaos protection) To fix haproxy CRYPTO frames buffering once and for all, an alternative non-contiguous buffer named ncbmbuf has been recently implemented. This type does not suffer from gaps size limitation, albeit at the cost of a small reduction in the size available for data storage. Thus, the purpose of this current patch is to replace ncbuf with the newer ncbmbuf for QUIC CRYPTO frames parsing. Now, ncbmb_add() is used to buffer received frames which is guaranteed to suceed. The only remaining case of error is if a received frame offset and length exceed the ncbmbuf data storage, which would result in a CRYPTO_BUFFER_EXCEEDED error code. A notable behavior change when switching to ncbmbuf implementation is that NCB_ADD_COMPARE mode cannot be used anymore during add. Instead, crypto frame content received at a similar offset will be overwritten. A final note regarding STREAM frames parsing. For now, it is considered unnecessary to switch from ncbuf in this case. Indeed, QUIC clients does not perform aggressive fragmentation for them. Keeping ncbuf ensure that the data storage size is bigger than the equivalent ncbmbuf area. This should fix github issue #3141. This patch must be backported up to 2.6. It is first necessary to pick the relevant commits for ncbmbuf implementation prior to it.
223 lines
7.4 KiB
C
223 lines
7.4 KiB
C
/*
|
|
* include/haproxy/quic_conn.h
|
|
*
|
|
* Copyright 2020 HAProxy Technologies, Frederic Lecaille <flecaille@haproxy.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation, version 2.1
|
|
* exclusively.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#ifndef _HAPROXY_QUIC_CONN_H
|
|
#define _HAPROXY_QUIC_CONN_H
|
|
#ifdef USE_QUIC
|
|
#ifndef USE_OPENSSL
|
|
#error "Must define USE_OPENSSL"
|
|
#endif
|
|
|
|
#include <inttypes.h>
|
|
|
|
#include <import/eb64tree.h>
|
|
#include <import/ebmbtree.h>
|
|
|
|
#include <haproxy/chunk.h>
|
|
#include <haproxy/dynbuf.h>
|
|
#include <haproxy/ncbmbuf.h>
|
|
#include <haproxy/net_helper.h>
|
|
#include <haproxy/openssl-compat.h>
|
|
#include <haproxy/ticks.h>
|
|
|
|
#include <haproxy/listener.h>
|
|
#include <haproxy/proto_quic.h>
|
|
#include <haproxy/quic_cc.h>
|
|
#include <haproxy/quic_cid.h>
|
|
#include <haproxy/quic_conn-t.h>
|
|
#include <haproxy/quic_enc.h>
|
|
#include <haproxy/quic_frame.h>
|
|
#include <haproxy/quic_loss.h>
|
|
#include <haproxy/quic_pacing.h>
|
|
#include <haproxy/quic_rx.h>
|
|
#include <haproxy/quic_tune.h>
|
|
#include <haproxy/mux_quic.h>
|
|
|
|
#include <openssl/rand.h>
|
|
|
|
extern struct pool_head *pool_head_quic_connection_id;
|
|
|
|
int qc_conn_finalize(struct quic_conn *qc, int server);
|
|
int ssl_quic_initial_ctx(struct bind_conf *bind_conf);
|
|
struct quic_cstream *quic_cstream_new(struct quic_conn *qc);
|
|
void quic_cstream_free(struct quic_cstream *cs);
|
|
void quic_free_arngs(struct quic_conn *qc, struct quic_arngs *arngs);
|
|
struct quic_cstream *quic_cstream_new(struct quic_conn *qc);
|
|
struct task *quic_conn_app_io_cb(struct task *t, void *context, unsigned int state);
|
|
|
|
void quic_conn_closed_err_count_inc(struct quic_conn *qc, struct quic_frame *frm);
|
|
int qc_h3_request_reject(struct quic_conn *qc, uint64_t id);
|
|
struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
|
|
struct quic_cid *dcid, struct quic_cid *scid,
|
|
const struct quic_cid *token_odcid,
|
|
struct quic_connection_id *conn_id,
|
|
struct sockaddr_storage *local_addr,
|
|
struct sockaddr_storage *peer_addr,
|
|
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);
|
|
void qc_set_timer(struct quic_conn *qc);
|
|
void qc_detach_th_ctx_list(struct quic_conn *qc, int closing);
|
|
void qc_idle_timer_do_rearm(struct quic_conn *qc, int arm_ack);
|
|
void qc_idle_timer_rearm(struct quic_conn *qc, int read, int arm_ack);
|
|
void qc_check_close_on_released_mux(struct quic_conn *qc);
|
|
int quic_stateless_reset_token_cpy(unsigned char *pos, size_t len,
|
|
const unsigned char *salt, size_t saltlen);
|
|
|
|
/* Returns true if <qc> is used on the backed side (as a client). */
|
|
static inline int qc_is_back(const struct quic_conn *qc)
|
|
{
|
|
return qc->flags & QUIC_FL_CONN_IS_BACK;
|
|
}
|
|
|
|
/* Free the CIDs attached to <conn> QUIC connection. */
|
|
static inline void free_quic_conn_cids(struct quic_conn *conn)
|
|
{
|
|
struct eb64_node *node;
|
|
|
|
if (!conn->cids)
|
|
return;
|
|
|
|
node = eb64_first(conn->cids);
|
|
while (node) {
|
|
struct quic_connection_id *conn_id;
|
|
|
|
conn_id = eb64_entry(node, struct quic_connection_id, seq_num);
|
|
|
|
/* remove the CID from the receiver tree */
|
|
quic_cid_delete(conn_id);
|
|
|
|
/* remove the CID from the quic_conn tree */
|
|
node = eb64_next(node);
|
|
eb64_delete(&conn_id->seq_num);
|
|
pool_free(pool_head_quic_connection_id, conn_id);
|
|
}
|
|
}
|
|
|
|
/* Move all the connection IDs from <conn> QUIC connection to <cc_conn> */
|
|
static inline void quic_conn_mv_cids_to_cc_conn(struct quic_conn_closed *cc_conn,
|
|
struct quic_conn *conn)
|
|
{
|
|
struct eb64_node *node;
|
|
|
|
node = eb64_first(conn->cids);
|
|
while (node) {
|
|
struct quic_connection_id *conn_id;
|
|
|
|
conn_id = eb64_entry(node, struct quic_connection_id, seq_num);
|
|
conn_id->qc = (struct quic_conn *)cc_conn;
|
|
node = eb64_next(node);
|
|
}
|
|
|
|
}
|
|
|
|
/* Allocate the underlying required memory for <ncbuf> non-contiguous buffer.
|
|
* Does nothing if buffer is already allocated.
|
|
*
|
|
* Returns the buffer instance or NULL on allocation failure.
|
|
*/
|
|
static inline struct ncbmbuf *quic_get_ncbuf(struct ncbmbuf *ncbuf)
|
|
{
|
|
struct buffer buf = BUF_NULL;
|
|
|
|
if (!ncbmb_is_null(ncbuf))
|
|
return ncbuf;
|
|
|
|
if (!b_alloc(&buf, DB_MUX_RX))
|
|
return NULL;
|
|
|
|
*ncbuf = ncbmb_make(buf.area, buf.size, 0);
|
|
ncbmb_init(ncbuf, 0);
|
|
|
|
return ncbuf;
|
|
}
|
|
|
|
/* Release the underlying memory use by <ncbuf> non-contiguous buffer */
|
|
static inline void quic_free_ncbuf(struct ncbmbuf *ncbuf)
|
|
{
|
|
struct buffer buf;
|
|
|
|
if (ncbmb_is_null(ncbuf))
|
|
return;
|
|
|
|
buf = b_make(ncbuf->area, ncbuf->size, 0, 0);
|
|
b_free(&buf);
|
|
offer_buffers(NULL, 1);
|
|
|
|
*ncbuf = NCBMBUF_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;
|
|
struct server *s;
|
|
|
|
if (!o)
|
|
return NULL;
|
|
|
|
l = objt_listener(o);
|
|
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);
|
|
int quic_set_app_ops(struct quic_conn *qc, const unsigned char *alpn, size_t alpn_len);
|
|
int qc_check_dcid(struct quic_conn *qc, unsigned char *dcid, size_t dcid_len);
|
|
|
|
void qc_notify_err(struct quic_conn *qc);
|
|
int qc_notify_send(struct quic_conn *qc);
|
|
|
|
void qc_check_close_on_released_mux(struct quic_conn *qc);
|
|
|
|
int quic_conn_release(struct quic_conn *qc);
|
|
|
|
void qc_kill_conn(struct quic_conn *qc);
|
|
|
|
int qc_parse_hd_form(struct quic_rx_packet *pkt,
|
|
unsigned char **buf, const unsigned char *end);
|
|
|
|
int qc_bind_tid_prep(struct quic_conn *qc, uint new_tid);
|
|
void qc_bind_tid_commit(struct quic_conn *qc, struct listener *new_li);
|
|
void qc_bind_tid_reset(struct quic_conn *qc);
|
|
void qc_finalize_tid_rebind(struct quic_conn *qc);
|
|
|
|
int qc_handle_conn_migration(struct quic_conn *qc,
|
|
const struct sockaddr_storage *peer_addr,
|
|
const struct sockaddr_storage *local_addr);
|
|
|
|
/* Function pointer that can be used to compute a hash from first generated CID (derived from ODCID) */
|
|
extern uint64_t (*quic_hash64_from_cid)(const unsigned char *cid, int size, const unsigned char *secret, size_t secretlen);
|
|
/* Function pointer that can be used to derive a new CID from the previously computed hash */
|
|
extern void (*quic_newcid_from_hash64)(unsigned char *cid, int size, uint64_t hash, const unsigned char *secret, size_t secretlen);
|
|
|
|
#endif /* USE_QUIC */
|
|
#endif /* _HAPROXY_QUIC_CONN_H */
|