From 0c4e3b09b044b568ff344de044d98c03b42c54a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20L=C3=A9caille?= Date: Mon, 23 Nov 2020 14:10:37 +0100 Subject: [PATCH] MINOR: quic: Add definitions for QUIC protocol. This patch imports all the definitions for QUIC protocol with few modifications from 20200720-quic branch of quic-dev repository found at https://github.com/haproxytech/quic-dev. --- include/haproxy/quic_cc-t.h | 101 +++ include/haproxy/quic_cc.h | 73 ++ include/haproxy/quic_frame-t.h | 235 +++++++ include/haproxy/quic_frame.h | 43 ++ include/haproxy/quic_loss-t.h | 54 ++ include/haproxy/quic_loss.h | 190 +++++ include/haproxy/quic_sock.h | 5 + include/haproxy/quic_tls-t.h | 110 +++ include/haproxy/quic_tls.h | 441 ++++++++++++ include/haproxy/xprt_quic-t.h | 640 +++++++++++++++++ include/haproxy/xprt_quic.h | 1209 ++++++++++++++++++++++++++++++++ 11 files changed, 3101 insertions(+) create mode 100644 include/haproxy/quic_cc-t.h create mode 100644 include/haproxy/quic_cc.h create mode 100644 include/haproxy/quic_frame-t.h create mode 100644 include/haproxy/quic_frame.h create mode 100644 include/haproxy/quic_loss-t.h create mode 100644 include/haproxy/quic_loss.h create mode 100644 include/haproxy/quic_tls-t.h create mode 100644 include/haproxy/quic_tls.h create mode 100644 include/haproxy/xprt_quic-t.h create mode 100644 include/haproxy/xprt_quic.h diff --git a/include/haproxy/quic_cc-t.h b/include/haproxy/quic_cc-t.h new file mode 100644 index 000000000..2c11011f4 --- /dev/null +++ b/include/haproxy/quic_cc-t.h @@ -0,0 +1,101 @@ +/* + * include/haproxy/quic_cc-t.h + * This file contains definitions for QUIC congestion control. + * + * Copyright 2020 HAProxy Technologies, Frédéric Lécaille + * + * 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_CC_H +#define _HAPROXY_QUIC_CC_H +#ifdef USE_QUIC +#ifndef USE_OPENSSL +#error "Must define USE_OPENSSL" +#endif + +#include /* size_t */ +#include + +#include + +#define QUIC_CC_INFINITE_SSTHESH ((uint64_t)-1) + +extern struct quic_cc_algo quic_cc_algo_nr; +extern struct quic_cc_algo *default_quic_cc_algo; + +enum quic_cc_algo_state_type { + /* Slow start. */ + QUIC_CC_ST_SS, + /* Congestion avoidance. */ + QUIC_CC_ST_CA, +}; + +enum quic_cc_event_type { + /* ACK receipt. */ + QUIC_CC_EVT_ACK, + /* Packet loss. */ + QUIC_CC_EVT_LOSS, + /* ECN-CE. */ + QUIC_CC_EVT_ECN_CE, +}; + +struct quic_cc_event { + enum quic_cc_event_type type; + union { + struct ack { + uint64_t acked; + unsigned int time_sent; + } ack; + struct loss { + unsigned int now_ms; + unsigned int max_ack_delay; + size_t lost_bytes; + unsigned int newest_time_sent; + unsigned int period; + } loss; + }; +}; + +enum quic_cc_algo_type { + QUIC_CC_ALGO_TP_NEWRENO, +}; + +union quic_cc_algo_state { + /* NewReno */ + struct nr { + enum quic_cc_algo_state_type state; + uint64_t cwnd; + uint64_t ssthresh; + uint64_t recovery_start_time; + } nr; +}; + +struct quic_cc { + /* is there ony for debugging purpose. */ + struct quic_conn *qc; + struct quic_cc_algo *algo; + union quic_cc_algo_state algo_state; +}; + +struct quic_cc_algo { + enum quic_cc_algo_type type; + int (*init)(struct quic_cc *cc); + void (*event)(struct quic_cc *cc, struct quic_cc_event *ev); + void (*state_trace)(struct buffer *buf, const struct quic_cc *cc); +}; + +#endif /* USE_QUIC */ +#endif /* _HAPROXY_QUIC_CC_H */ diff --git a/include/haproxy/quic_cc.h b/include/haproxy/quic_cc.h new file mode 100644 index 000000000..d250af970 --- /dev/null +++ b/include/haproxy/quic_cc.h @@ -0,0 +1,73 @@ +/* + * include/proto/quic_cc.h + * This file contains prototypes for QUIC congestion control. + * + * Copyright 2019 HAProxy Technologies, Frédéric Lécaille + * + * 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 _PROTO_QUIC_CC_H +#define _PROTO_QUIC_CC_H +#ifdef USE_QUIC +#ifndef USE_OPENSSL +#error "Must define USE_OPENSSL" +#endif + +#include +#include +#include +#include + +void quic_cc_init(struct quic_cc *cc, struct quic_cc_algo *algo, struct quic_conn *qc); +void quic_cc_event(struct quic_cc *cc, struct quic_cc_event *ev); +void quic_cc_state_trace(struct buffer *buf, const struct quic_cc *cc); + +static inline const char *quic_cc_state_str(enum quic_cc_algo_state_type state) +{ + switch (state) { + case QUIC_CC_ST_SS: + return "ss"; + case QUIC_CC_ST_CA: + return "ca"; + default: + return "unknown"; + } +} + +/* Return a human readable string from control congestion event type. */ +static inline void quic_cc_event_trace(struct buffer *buf, const struct quic_cc_event *ev) +{ + chunk_appendf(buf, " event type="); + switch (ev->type) { + case QUIC_CC_EVT_ACK: + chunk_appendf(buf, "ack acked=%llu time_sent:%u", + (unsigned long long)ev->ack.acked, ev->ack.time_sent); + break; + case QUIC_CC_EVT_LOSS: + chunk_appendf(buf, "loss now_ms=%u max_ack_delay=%u lost_bytes=%llu" + " time_sent=%u period=%u", + ev->loss.now_ms, ev->loss.max_ack_delay, + (unsigned long long)ev->loss.lost_bytes, + ev->loss.newest_time_sent, ev->loss.period); + break; + case QUIC_CC_EVT_ECN_CE: + chunk_appendf(buf, "ecn_ce"); + break; + } +} + +#endif /* USE_QUIC */ +#endif /* _PROTO_QUIC_CC_H */ diff --git a/include/haproxy/quic_frame-t.h b/include/haproxy/quic_frame-t.h new file mode 100644 index 000000000..a2404efc9 --- /dev/null +++ b/include/haproxy/quic_frame-t.h @@ -0,0 +1,235 @@ +/* + * include/types/quic_frame.h + * This file contains QUIC frame definitions. + * + * Copyright 2019 HAProxy Technologies, Frédéric Lécaille + * + * 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 _TYPES_QUIC_FRAME_H +#define _TYPES_QUIC_FRAME_H +#ifdef USE_QUIC +#ifndef USE_OPENSSL +#error "Must define USE_OPENSSL" +#endif + +#include +#include + +#include + +/* QUIC frame types. */ +enum quic_frame_type { + QUIC_FT_PADDING = 0x00, + QUIC_FT_PING = 0x01, + QUIC_FT_ACK = 0x02, + QUIC_FT_ACK_ECN = 0x03, + QUIC_FT_RESET_STREAM = 0x04, + QUIC_FT_STOP_SENDING = 0x05, + QUIC_FT_CRYPTO = 0x06, + QUIC_FT_NEW_TOKEN = 0x07, + + QUIC_FT_STREAM_8 = 0x08, + QUIC_FT_STREAM_9 = 0x09, + QUIC_FT_STREAM_A = 0x0a, + QUIC_FT_STREAM_B = 0x0b, + QUIC_FT_STREAM_C = 0x0c, + QUIC_FT_STREAM_D = 0x0d, + QUIC_FT_STREAM_E = 0x0e, + QUIC_FT_STREAM_F = 0x0f, + + QUIC_FT_MAX_DATA = 0x10, + QUIC_FT_MAX_STREAM_DATA = 0x11, + QUIC_FT_MAX_STREAMS_BIDI = 0x12, + QUIC_FT_MAX_STREAMS_UNI = 0x13, + QUIC_FT_DATA_BLOCKED = 0x14, + QUIC_FT_STREAM_DATA_BLOCKED = 0x15, + QUIC_FT_STREAMS_BLOCKED_BIDI = 0x16, + QUIC_FT_STREAMS_BLOCKED_UNI = 0x17, + QUIC_FT_NEW_CONNECTION_ID = 0x18, + QUIC_FT_RETIRE_CONNECTION_ID = 0x19, + QUIC_FT_PATH_CHALLENGE = 0x1a, + QUIC_FT_PATH_RESPONSE = 0x1b, + QUIC_FT_CONNECTION_CLOSE = 0x1c, + QUIC_FT_CONNECTION_CLOSE_APP = 0x1d, + QUIC_FT_HANDSHAKE_DONE = 0x1e, + /* Do not insert enums after the following one. */ + QUIC_FT_MAX +}; + +#define QUIC_FT_PKT_TYPE_I_BITMASK (1 << QUIC_PACKET_TYPE_INITIAL) +#define QUIC_FT_PKT_TYPE_0_BITMASK (1 << QUIC_PACKET_TYPE_0RTT) +#define QUIC_FT_PKT_TYPE_H_BITMASK (1 << QUIC_PACKET_TYPE_HANDSHAKE) +#define QUIC_FT_PKT_TYPE_1_BITMASK (1 << QUIC_PACKET_TYPE_SHORT) + +#define QUIC_FT_PKT_TYPE_IH01_BITMASK \ + (QUIC_FT_PKT_TYPE_I_BITMASK | QUIC_FT_PKT_TYPE_H_BITMASK | \ + QUIC_FT_PKT_TYPE_0_BITMASK | QUIC_FT_PKT_TYPE_1_BITMASK) + +#define QUIC_FT_PKT_TYPE_IH_1_BITMASK \ + (QUIC_FT_PKT_TYPE_I_BITMASK | QUIC_FT_PKT_TYPE_H_BITMASK | \ + QUIC_FT_PKT_TYPE_1_BITMASK) + +#define QUIC_FT_PKT_TYPE___01_BITMASK \ + (QUIC_FT_PKT_TYPE_0_BITMASK | QUIC_FT_PKT_TYPE_1_BITMASK) + +#define QUIC_FT_PKT_TYPE____1_BITMASK QUIC_FT_PKT_TYPE_1_BITMASK + +#define QUIC_STREAM_FRAME_FIN_BIT 0x01 +#define QUIC_STREAM_FRAME_LEN_BIT 0x02 +#define QUIC_STREAM_FRAME_OFF_BIT 0x04 + +#define QUIC_PATH_CHALLENGE_LEN 8 + +struct quic_padding { + size_t len; +}; + +struct quic_ack { + uint64_t largest_ack; + uint64_t ack_delay; + uint64_t ack_range_num; + uint64_t first_ack_range; +}; + +/* Structure used when emitting ACK frames. */ +struct quic_tx_ack { + uint64_t ack_delay; + struct quic_ack_ranges *ack_ranges; +}; + +struct quic_reset_stream { + uint64_t id; + uint64_t app_error_code; + uint64_t final_size; +}; + +struct quic_stop_sending_frame { + uint64_t id; + uint64_t app_error_code; +}; + +struct quic_crypto { + uint64_t offset; + uint64_t len; + const struct quic_enc_level *qel; + const unsigned char *data; +}; + +struct quic_new_token { + uint64_t len; + const unsigned char *data; +}; + +struct quic_stream { + uint64_t id; + uint64_t offset; + uint64_t len; + const unsigned char *data; +}; + +struct quic_max_data { + uint64_t max_data; +}; + +struct quic_max_stream_data { + uint64_t id; + uint64_t max_stream_data; +}; + +struct quic_max_streams { + uint64_t max_streams; +}; + +struct quic_data_blocked { + uint64_t limit; +}; + +struct quic_stream_data_blocked { + uint64_t id; + uint64_t limit; +}; + +struct quic_streams_blocked { + uint64_t limit; +}; + +struct quic_new_connection_id { + uint64_t seq_num; + uint64_t retire_prior_to; + struct { + unsigned char len; + const unsigned char *data; + } cid; + const unsigned char *stateless_reset_token; +}; + +struct quic_retire_connection_id { + uint64_t seq_num; +}; + +struct quic_path_challenge { + unsigned char data[QUIC_PATH_CHALLENGE_LEN]; +}; + +struct quic_path_challenge_response { + unsigned char data[QUIC_PATH_CHALLENGE_LEN]; +}; + +struct quic_connection_close { + uint64_t error_code; + uint64_t frame_type; + uint64_t reason_phrase_len; + unsigned char *reason_phrase; +}; + +struct quic_connection_close_app { + uint64_t error_code; + uint64_t reason_phrase_len; + unsigned char *reason_phrase; +}; + +struct quic_frame { + struct list list; + unsigned char type; + union { + struct quic_padding padding; + struct quic_ack ack; + struct quic_tx_ack tx_ack; + struct quic_crypto crypto; + struct quic_reset_stream reset_stream; + struct quic_stop_sending_frame stop_sending_frame; + struct quic_new_token new_token; + struct quic_stream stream; + struct quic_max_data max_data; + struct quic_max_stream_data max_stream_data; + struct quic_max_streams max_streams_bidi; + struct quic_max_streams max_streams_uni; + struct quic_data_blocked data_blocked; + struct quic_stream_data_blocked stream_data_blocked; + struct quic_streams_blocked streams_blocked_bidi; + struct quic_streams_blocked streams_blocked_uni; + struct quic_new_connection_id new_connection_id; + struct quic_retire_connection_id retire_connection_id; + struct quic_path_challenge path_challenge; + struct quic_path_challenge_response path_challenge_response; + struct quic_connection_close connection_close; + struct quic_connection_close_app connection_close_app; + }; +}; + +#endif /* USE_QUIC */ +#endif /* _TYPES_QUIC_FRAME_H */ diff --git a/include/haproxy/quic_frame.h b/include/haproxy/quic_frame.h new file mode 100644 index 000000000..123f508aa --- /dev/null +++ b/include/haproxy/quic_frame.h @@ -0,0 +1,43 @@ +/* + * include/haproxy/quic_frame.h + * This file contains prototypes for QUIC frames. + * + * Copyright 2020 HAProxy Technologies, Frédéric Lécaille + * + * 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_FRAME_H +#define _HAPROXY_QUIC_FRAME_H +#ifdef USE_QUIC +#ifndef USE_OPENSSL +#error "Must define USE_OPENSSL" +#endif + +#include +#include + +const char *quic_frame_type_string(enum quic_frame_type ft); + +int qc_build_frm(unsigned char **buf, const unsigned char *end, + struct quic_frame *frm, struct quic_tx_packet *pkt, + struct quic_conn *conn); + +int qc_parse_frm(struct quic_frame *frm, struct quic_rx_packet *pkt, + const unsigned char **buf, const unsigned char *end, + struct quic_conn *conn); + +#endif /* USE_QUIC */ +#endif /* _HAPROXY_QUIC_FRAME_H */ diff --git a/include/haproxy/quic_loss-t.h b/include/haproxy/quic_loss-t.h new file mode 100644 index 000000000..5ca9b3a5e --- /dev/null +++ b/include/haproxy/quic_loss-t.h @@ -0,0 +1,54 @@ +/* + * include/types/quic_loss.h + * This file contains definitions for QUIC loss detection. + * + * Copyright 2019 HAProxy Technologies, Frédéric Lécaille + * + * 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 _TYPES_QUIC_LOSS_H +#define _TYPES_QUIC_LOSS_H +#ifdef USE_QUIC +#ifndef USE_OPENSSL +#error "Must define USE_OPENSSL" +#endif + +#include + +/* Maximum reordering in packets. */ +#define QUIC_LOSS_PACKET_THRESHOLD 3 +#define QUIC_TIMER_GRANULARITY 1U /* 1ms */ +#define QUIC_LOSS_INITIAL_RTT 333U /* 333ms */ + +/* Note that all the unit of variables for QUIC LOSS dectections + * is the tick. + */ + +struct quic_loss { + /* The most recent RTT measurement. */ + unsigned int latest_rtt; + /* Smoothed RTT << 4*/ + unsigned int srtt; + /* RTT variation << 2 */ + unsigned int rtt_var; + /* Minimum RTT. */ + unsigned int rtt_min; + /* Number of NACKed sent PTO. */ + unsigned int pto_count; +}; + +#endif /* USE_QUIC */ +#endif /* _TYPES_QUIC_LOSS_H */ diff --git a/include/haproxy/quic_loss.h b/include/haproxy/quic_loss.h new file mode 100644 index 000000000..ee7649c53 --- /dev/null +++ b/include/haproxy/quic_loss.h @@ -0,0 +1,190 @@ +/* + * include/proto/quic_loss.h + * This file provides interface definition for QUIC loss detection. + * + * Copyright 2019 HAProxy Technologies, Frédéric Lécaille + * + * 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 _PROTO_QUIC_LOSS_H +#define _PROTO_QUIC_LOSS_H +#ifdef USE_QUIC +#ifndef USE_OPENSSL +#error "Must define USE_OPENSSL" +#endif + +#include +#include +#include + +#include + +#define TRACE_SOURCE &trace_quic + +static inline void quic_loss_init(struct quic_loss *ql) +{ + ql->srtt = QUIC_LOSS_INITIAL_RTT << 4; + ql->rtt_var = (QUIC_LOSS_INITIAL_RTT >> 1) << 2; + ql->rtt_min = 0; + ql->pto_count = 0; +} + +/* Update QUIC loss information with new measurement and + * on ACK frame receipt which MUST be min(ack->ack_delay, max_ack_delay) for + * non handshake packets. + */ +static inline void quic_loss_srtt_update(struct quic_loss *ql, + unsigned int rtt, unsigned int ack_delay, + struct quic_conn *conn) +{ + TRACE_PROTO("Loss info update", QUIC_EV_CONN_RTTUPDT, conn->conn, &rtt, &ack_delay, ql); + ql->latest_rtt = rtt; + if (!ql->rtt_min) { + /* No previous measurement. */ + ql->srtt = rtt << 3; + /* rttval <- rtt / 2 or 4*rttval <- 2*rtt. */ + ql->rtt_var = rtt << 1; + ql->rtt_min = rtt; + } + else { + int diff; + + ql->rtt_min = QUIC_MIN(rtt, ql->rtt_min); + /* Specific to QUIC (RTT adjustment). */ + if (ack_delay && rtt > ql->rtt_min + ack_delay) + rtt -= ack_delay; + diff = ql->srtt - rtt; + if (diff < 0) + diff = -diff; + /* 4*rttvar = 3*rttvar + |diff| */ + ql->rtt_var += diff - (ql->rtt_var >> 2); + /* 8*srtt = 7*srtt + rtt */ + ql->srtt += rtt - (ql->srtt >> 3); + } + TRACE_PROTO("Loss info update", QUIC_EV_CONN_RTTUPDT, conn->conn,,, ql); +} + +/* Return 1 if a persitent congestion is observed for a list of + * lost packets sent during period depending on loss information, + * the current time and the maximum ACK delay of the connection + * experiencing a packet loss. Return 0 on the contrary. + */ +static inline int quic_loss_persistent_congestion(struct quic_loss *ql, + unsigned int period, + unsigned int now_us, + unsigned int max_ack_delay) +{ + unsigned int congestion_period; + + if (!period) + return 0; + + congestion_period = (ql->srtt >> 3) + + QUIC_MAX(ql->rtt_var, QUIC_TIMER_GRANULARITY) + max_ack_delay; + congestion_period *= QUIC_LOSS_PACKET_THRESHOLD; + + return period >= congestion_period; +} + +/* Returns for QUIC connection the first packet number space which + * experienced packet loss, if any or a packet number space with + * TICK_ETERNITY as packet loss time if not. + */ +static inline struct quic_pktns *quic_loss_pktns(struct quic_conn *qc) +{ + enum quic_tls_pktns i; + struct quic_pktns *pktns; + + pktns = &qc->pktns[QUIC_TLS_PKTNS_INITIAL]; + for (i = QUIC_TLS_PKTNS_01RTT; i < QUIC_TLS_PKTNS_MAX; i++) + if (qc->pktns[i].tx.loss_time < pktns->tx.loss_time) + pktns = &qc->pktns[i]; + + return pktns; +} + +/* Returns for QUIC connection the first packet number space to + * arm the PTO for if any or a packet number space with TICK_ETERNITY + * as PTO value if not. + */ +static inline struct quic_pktns *quic_pto_pktns(struct quic_conn *qc, + int handshake_completed, + unsigned int *pto) +{ + int i; + unsigned int duration, lpto, time_of_last_eliciting; + struct quic_loss *ql = &qc->path->loss; + struct quic_pktns *pktns; + + TRACE_ENTER(QUIC_EV_CONN_SPTO, qc->conn); + duration = + (ql->srtt >> 3) + + (QUIC_MAX(ql->rtt_var, QUIC_TIMER_GRANULARITY) << ql->pto_count); + + if (!qc->path->in_flight) { + struct quic_enc_level *hel; + + hel = &qc->els[QUIC_TLS_ENC_LEVEL_HANDSHAKE]; + if (hel->tls_ctx.tx.flags & QUIC_FL_TLS_SECRETS_SET) { + pktns = &qc->pktns[QUIC_TLS_PKTNS_HANDSHAKE]; + } + else { + pktns = &qc->pktns[QUIC_TLS_PKTNS_INITIAL]; + } + lpto = tick_add(now_ms, duration); + goto out; + } + + lpto = TICK_ETERNITY; + pktns = &qc->pktns[QUIC_TLS_PKTNS_INITIAL]; + + for (i = QUIC_TLS_PKTNS_INITIAL; i < QUIC_TLS_PKTNS_MAX; i++) { + unsigned int tmp_pto; + struct quic_pktns *p; + + p = &qc->pktns[i]; + if (!p->tx.in_flight) + continue; + + if (i == QUIC_TLS_PKTNS_01RTT) { + if (!handshake_completed) { + pktns = p; + goto out; + } + + duration += qc->max_ack_delay << ql->pto_count; + } + + time_of_last_eliciting = p->tx.time_of_last_eliciting; + tmp_pto = + tick_first(lpto, tick_add(time_of_last_eliciting, duration)); + if (!tick_isset(lpto) || tmp_pto < lpto) { + lpto = tmp_pto; + pktns = p; + } + TRACE_PROTO("pktns", QUIC_EV_CONN_SPTO, qc->conn, p); + } + + out: + if (pto) + *pto = lpto; + TRACE_LEAVE(QUIC_EV_CONN_SPTO, qc->conn, pktns, &duration); + + return pktns; +} + +#endif /* USE_QUIC */ +#endif /* _PROTO_QUIC_LOSS_H */ diff --git a/include/haproxy/quic_sock.h b/include/haproxy/quic_sock.h index 78623e817..672255ed3 100644 --- a/include/haproxy/quic_sock.h +++ b/include/haproxy/quic_sock.h @@ -21,6 +21,10 @@ #ifndef _HAPROXY_QUIC_SOCK_H #define _HAPROXY_QUIC_SOCK_H +#ifdef USE_QUIC +#ifndef USE_OPENSSL +#error "Must define USE_OPENSSL" +#endif #include #include @@ -33,6 +37,7 @@ int quic_sock_accepting_conn(const struct receiver *rx); struct connection *quic_sock_accept_conn(struct listener *l, int *status); void quic_sock_fd_iocb(int fd); +#endif /* USE_QUIC */ #endif /* _HAPROXY_QUIC_SOCK_H */ /* diff --git a/include/haproxy/quic_tls-t.h b/include/haproxy/quic_tls-t.h new file mode 100644 index 000000000..20efc28b2 --- /dev/null +++ b/include/haproxy/quic_tls-t.h @@ -0,0 +1,110 @@ +/* + * include/types/quic_tls.h + * This file provides definitions for QUIC-TLS. + * + * Copyright 2019 HAProxy Technologies, Frédéric Lécaille + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#ifndef _TYPES_QUIC_TLS_H +#define _TYPES_QUIC_TLS_H +#ifdef USE_QUIC +#ifndef USE_OPENSSL +#error "Must define USE_OPENSSL" +#endif + +#include + +/* It seems TLS 1.3 ciphersuites macros differ between openssl and boringssl */ + +#if defined(OPENSSL_IS_BORINGSSL) +#if !defined(TLS1_3_CK_AES_128_GCM_SHA256) +#define TLS1_3_CK_AES_128_GCM_SHA256 TLS1_CK_AES_128_GCM_SHA256 +#endif +#if !defined(TLS1_3_CK_AES_256_GCM_SHA384) +#define TLS1_3_CK_AES_256_GCM_SHA384 TLS1_CK_AES_256_GCM_SHA384 +#endif +#if !defined(TLS1_3_CK_CHACHA20_POLY1305_SHA256) +#define TLS1_3_CK_CHACHA20_POLY1305_SHA256 TLS1_CK_CHACHA20_POLY1305_SHA256 +#endif +#if !defined(TLS1_3_CK_AES_128_CCM_SHA256) +/* Note that TLS1_CK_AES_128_CCM_SHA256 is not defined in boringssl */ +#define TLS1_3_CK_AES_128_CCM_SHA256 0x03001304 +#endif +#endif + +/* The TLS extension (enum) for QUIC transport parameters */ +#define TLS_EXTENSION_QUIC_TRANSPORT_PARAMETERS 0xffa5 + +/* QUIC handshake states for both clients and servers. */ +enum quic_handshake_state { + QUIC_HS_ST_CLIENT_INITIAL, + QUIC_HS_ST_CLIENT_HANDSHAKE, + QUIC_HS_ST_CLIENT_HANDSHAKE_FAILED, + + QUIC_HS_ST_SERVER_INITIAL, + QUIC_HS_ST_SERVER_HANDSHAKE, + QUIC_HS_ST_SERVER_HANDSHAKE_FAILED, + + /* Common to servers and clients */ + QUIC_HS_ST_COMPLETE, + QUIC_HS_ST_CONFIRMED, +}; + +/* QUIC TLS level encryption */ +enum quic_tls_enc_level { + QUIC_TLS_ENC_LEVEL_NONE = -1, + QUIC_TLS_ENC_LEVEL_INITIAL, + QUIC_TLS_ENC_LEVEL_EARLY_DATA, + QUIC_TLS_ENC_LEVEL_HANDSHAKE, + QUIC_TLS_ENC_LEVEL_APP, + /* Please do not insert any value after this following one */ + QUIC_TLS_ENC_LEVEL_MAX, +}; + +/* QUIC packet number spaces */ +enum quic_tls_pktns { + QUIC_TLS_PKTNS_INITIAL, + QUIC_TLS_PKTNS_01RTT, + QUIC_TLS_PKTNS_HANDSHAKE, + /* Please do not insert any value after this following one */ + QUIC_TLS_PKTNS_MAX, +}; + +/* The ciphersuites for AEAD QUIC-TLS have 16-bytes authentication tag */ +#define QUIC_TLS_TAG_LEN 16 + +extern unsigned char initial_salt[20]; + +/* Flag to be used when TLS secrets have been set. */ +#define QUIC_FL_TLS_SECRETS_SET (1 << 0) +/* Flag to be used when TLS secrets have been discarded. */ +#define QUIC_FL_TLS_SECRETS_DCD (1 << 1) + +struct quic_tls_secrets { + const EVP_CIPHER *aead; + const EVP_MD *md; + const EVP_CIPHER *hp; + unsigned char key[32]; + unsigned char iv[12]; + /* Header protection key. + * Note: the header protection is applied after packet protection. + * As the header belong to the data, its protection must be removed before removing + * the packet protection. + */ + unsigned char hp_key[32]; + char flags; +}; + +struct quic_tls_ctx { + struct quic_tls_secrets rx; + struct quic_tls_secrets tx; +}; + +#endif /* USE_QUIC */ +#endif /* _TYPES_QUIC_TLS_H */ + diff --git a/include/haproxy/quic_tls.h b/include/haproxy/quic_tls.h new file mode 100644 index 000000000..94b978bc3 --- /dev/null +++ b/include/haproxy/quic_tls.h @@ -0,0 +1,441 @@ +/* + * include/proto/quic_tls.h + * This file provides definitions for QUIC-TLS. + * + * Copyright 2019 HAProxy Technologies, Frédéric Lécaille + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#ifndef _PROTO_QUIC_TLS_H +#define _PROTO_QUIC_TLS_H +#ifdef USE_QUIC +#ifndef USE_OPENSSL +#error "Must define USE_OPENSSL" +#endif + +#define TRACE_SOURCE &trace_quic + +#include +#include + +#include +#include +#include +#include + +void quic_tls_keys_hexdump(struct buffer *buf, struct quic_tls_secrets *secs); + +void quic_tls_secret_hexdump(struct buffer *buf, + const unsigned char *secret, size_t secret_len); + +int quic_derive_initial_secret(const EVP_MD *md, + unsigned char *initial_secret, size_t initial_secret_sz, + const unsigned char *secret, size_t secret_sz); + +int quic_tls_derive_initial_secrets(const EVP_MD *md, + unsigned char *rx, size_t rx_sz, + unsigned char *tx, size_t tx_sz, + const unsigned char *secret, size_t secret_sz, + int server); + +int quic_tls_encrypt(unsigned char *buf, size_t len, + const unsigned char *aad, size_t aad_len, + const EVP_CIPHER *aead, + const unsigned char *key, const unsigned char *iv); + +int quic_tls_decrypt(unsigned char *buf, size_t len, + unsigned char *aad, size_t aad_len, + const EVP_CIPHER *aead, + const unsigned char *key, const unsigned char *iv); + +int quic_tls_derive_keys(const EVP_CIPHER *aead, const EVP_CIPHER *hp, + const EVP_MD *md, + unsigned char *key, size_t keylen, + unsigned char *iv, size_t ivlen, + unsigned char *hp_key, size_t hp_keylen, + const unsigned char *secret, size_t secretlen); + +int quic_aead_iv_build(unsigned char *iv, size_t ivlen, + unsigned char *aead_iv, size_t aead_ivlen, uint64_t pn); + +static inline const EVP_CIPHER *tls_aead(const SSL_CIPHER *cipher) +{ + switch (SSL_CIPHER_get_id(cipher)) { + case TLS1_3_CK_AES_128_GCM_SHA256: + return EVP_aes_128_gcm(); + case TLS1_3_CK_AES_256_GCM_SHA384: + return EVP_aes_256_gcm(); +#ifndef OPENSSL_IS_BORINGSSL + /* XXX TO DO XXX */ + /* Note that for chacha20_poly1305, there exists EVP_AEAD_chacha20_poly135() function + * which returns a pointer to const EVP_AEAD. + */ + case TLS1_3_CK_CHACHA20_POLY1305_SHA256: + return EVP_chacha20_poly1305(); + case TLS1_3_CK_AES_128_CCM_SHA256: + return EVP_aes_128_ccm(); +#endif + default: + return NULL; + } +} + +static inline const EVP_MD *tls_md(const SSL_CIPHER *cipher) +{ + switch (SSL_CIPHER_get_id(cipher)) { + case TLS1_3_CK_AES_128_GCM_SHA256: +#ifndef OPENSSL_IS_BORINGSSL + /* XXX TO DO XXX */ + /* Note that for chacha20_poly1305, there exists EVP_AEAD_chacha20_poly135() function + * which returns a pointer to const EVP_AEAD. + */ + case TLS1_3_CK_AES_128_CCM_SHA256: + case TLS1_3_CK_CHACHA20_POLY1305_SHA256: +#endif + return EVP_sha256(); + case TLS1_3_CK_AES_256_GCM_SHA384: + return EVP_sha384(); + default: + return NULL; + } +} + +static inline const EVP_CIPHER *tls_hp(const SSL_CIPHER *cipher) +{ + switch (SSL_CIPHER_get_id(cipher)) { +#ifndef OPENSSL_IS_BORINGSSL + /* XXX TO DO XXX */ + /* Note that for chacha20_poly1305, there exists EVP_AEAD_chacha20_poly135() function + * which returns a pointer to const EVP_AEAD. + */ + case TLS1_3_CK_CHACHA20_POLY1305_SHA256: + return EVP_chacha20(); + case TLS1_3_CK_AES_128_CCM_SHA256: +#endif + case TLS1_3_CK_AES_128_GCM_SHA256: + return EVP_aes_128_ctr(); + case TLS1_3_CK_AES_256_GCM_SHA384: + return EVP_aes_256_ctr(); + default: + return NULL; + } + +} + +/* These following functions map TLS implementation encryption level to ours */ +static inline enum quic_tls_enc_level ssl_to_quic_enc_level(enum ssl_encryption_level_t level) +{ + switch (level) { + case ssl_encryption_initial: + return QUIC_TLS_ENC_LEVEL_INITIAL; + case ssl_encryption_early_data: + return QUIC_TLS_ENC_LEVEL_EARLY_DATA; + case ssl_encryption_handshake: + return QUIC_TLS_ENC_LEVEL_HANDSHAKE; + case ssl_encryption_application: + return QUIC_TLS_ENC_LEVEL_APP; + default: + return -1; + } +} + +/* These two following functions map our encryption level to the TLS implementation ones. */ +static inline enum quic_tls_enc_level quic_to_ssl_enc_level(enum quic_tls_enc_level level) +{ + switch (level) { + case QUIC_TLS_ENC_LEVEL_INITIAL: + return ssl_encryption_initial; + case QUIC_TLS_ENC_LEVEL_EARLY_DATA: + return ssl_encryption_early_data; + case QUIC_TLS_ENC_LEVEL_HANDSHAKE: + return ssl_encryption_handshake; + case QUIC_TLS_ENC_LEVEL_APP: + return ssl_encryption_application; + default: + return -1; + } +} + +/* Return a human readable string from QUIC handshake state of NULL + * for unknown state values (for debug purpose). + */ +static inline char *quic_hdshk_state_str(const enum quic_handshake_state state) +{ + switch (state) { + case QUIC_HS_ST_CLIENT_INITIAL: + return "CI"; + case QUIC_HS_ST_CLIENT_HANDSHAKE: + return "CH"; + case QUIC_HS_ST_CLIENT_HANDSHAKE_FAILED: + return "CF"; + case QUIC_HS_ST_SERVER_INITIAL: + return "SI"; + case QUIC_HS_ST_SERVER_HANDSHAKE: + return "SH"; + case QUIC_HS_ST_SERVER_HANDSHAKE_FAILED: + return "SF"; + case QUIC_HS_ST_COMPLETE: + return "CP"; + case QUIC_HS_ST_CONFIRMED: + return "CF"; + } + + return NULL; +} + +/* Return a human readable string from SSL error (returned from + * SSL_get_error()) + */ +static inline const char *ssl_error_str(int err) +{ + switch (err) { + case SSL_ERROR_NONE: + return "NONE"; + case SSL_ERROR_SSL: + return "SSL"; + case SSL_ERROR_WANT_READ: + return "WANT_READ"; + case SSL_ERROR_WANT_WRITE: + return "WANT_WRITE"; + case SSL_ERROR_WANT_X509_LOOKUP: + return "X509_LOOKUP"; + case SSL_ERROR_SYSCALL: + return "SYSCALL"; + case SSL_ERROR_ZERO_RETURN: + return "ZERO_RETURN"; + case SSL_ERROR_WANT_CONNECT: + return "WANT_CONNECT"; + case SSL_ERROR_WANT_ACCEPT: + return "WANT_ACCEPT"; +#ifndef OPENSSL_IS_BORINGSSL + case SSL_ERROR_WANT_ASYNC: + return "WANT_ASYNC"; + case SSL_ERROR_WANT_ASYNC_JOB: + return "WANT_ASYNC_JOB"; + case SSL_ERROR_WANT_CLIENT_HELLO_CB: + return "WANT_CLIENT_HELLO_CB"; +#endif + default: + return "UNKNWON"; + } +} + + +/* Return a character identifying the encryption level from QUIC TLS + * encryption level (for debug purpose). + * Initial -> 'I', Early Data -> 'E', Handshake -> 'H', Application -> 'A' and + * '-' if undefined. + */ +static inline char quic_enc_level_char(enum quic_tls_enc_level level) +{ + switch (level) { + case QUIC_TLS_ENC_LEVEL_INITIAL: + return 'I'; + case QUIC_TLS_ENC_LEVEL_EARLY_DATA: + return 'E'; + case QUIC_TLS_ENC_LEVEL_HANDSHAKE: + return 'H'; + case QUIC_TLS_ENC_LEVEL_APP: + return 'A'; + default: + return '-'; + } +} + +/* Return a character identifying encryption level from QUIC connection + * (for debug purpose). + * Initial -> 'I', Early Data -> 'E', Handshake -> 'H', Application -> 'A' and + * '-' if undefined. + */ +static inline char quic_enc_level_char_from_qel(const struct quic_enc_level *qel, + const struct quic_conn *qc) +{ + if (qel == &qc->els[QUIC_TLS_ENC_LEVEL_INITIAL]) + return 'I'; + else if (qel == &qc->els[QUIC_TLS_ENC_LEVEL_EARLY_DATA]) + return 'E'; + else if (qel == &qc->els[QUIC_TLS_ENC_LEVEL_HANDSHAKE]) + return 'H'; + else if (qel == &qc->els[QUIC_TLS_ENC_LEVEL_APP]) + return 'A'; + return '-'; +} + +/* Return a character identifying the encryption level of a packet depending on + * its type, and its header length (for debug purpose). + * Initial -> 'I', ORTT -> '0', Handshake -> 'H', Application -> 'A' and + * '-' if undefined. + */ +static inline char quic_packet_type_enc_level_char(int packet_type) +{ + switch (packet_type) { + case QUIC_PACKET_TYPE_INITIAL: + return 'I'; + case QUIC_PACKET_TYPE_0RTT: + return '0'; + case QUIC_PACKET_TYPE_HANDSHAKE: + return 'H'; + case QUIC_PACKET_TYPE_SHORT: + return 'A'; + default: + return '-'; + } +} + +/* Return the TLS encryption level to be used for + * QUIC packet type. + * Returns -1 if there is no TLS encryption level for + * packet type. + */ +static inline enum quic_tls_enc_level quic_packet_type_enc_level(enum quic_pkt_type packet_type) +{ + switch (packet_type) { + case QUIC_PACKET_TYPE_INITIAL: + return QUIC_TLS_ENC_LEVEL_INITIAL; + case QUIC_PACKET_TYPE_0RTT: + return QUIC_TLS_ENC_LEVEL_EARLY_DATA; + case QUIC_PACKET_TYPE_HANDSHAKE: + return QUIC_TLS_ENC_LEVEL_HANDSHAKE; + case QUIC_PACKET_TYPE_RETRY: + return QUIC_TLS_ENC_LEVEL_NONE; + case QUIC_PACKET_TYPE_SHORT: + return QUIC_TLS_ENC_LEVEL_APP; + default: + return QUIC_TLS_ENC_LEVEL_NONE; + } +} + +static inline enum quic_tls_pktns quic_tls_pktns(enum quic_tls_enc_level level) +{ + switch (level) { + case QUIC_TLS_ENC_LEVEL_INITIAL: + return QUIC_TLS_PKTNS_INITIAL; + case QUIC_TLS_ENC_LEVEL_EARLY_DATA: + case QUIC_TLS_ENC_LEVEL_APP: + return QUIC_TLS_PKTNS_01RTT; + case QUIC_TLS_ENC_LEVEL_HANDSHAKE: + return QUIC_TLS_PKTNS_HANDSHAKE; + default: + return -1; + } +} + +/* Initialize a TLS cryptographic context for the Initial encryption level. */ +static inline void quic_initial_tls_ctx_init(struct quic_tls_ctx *ctx) +{ + ctx->rx.aead = ctx->tx.aead = EVP_aes_128_gcm(); + ctx->rx.md = ctx->tx.md = EVP_sha256(); + ctx->rx.hp = ctx->tx.hp = EVP_aes_128_ctr(); +} + +static inline int quic_tls_level_pkt_type(enum quic_tls_enc_level level) +{ + switch (level) { + case QUIC_TLS_ENC_LEVEL_INITIAL: + return QUIC_PACKET_TYPE_INITIAL; + case QUIC_TLS_ENC_LEVEL_EARLY_DATA: + return QUIC_PACKET_TYPE_0RTT; + case QUIC_TLS_ENC_LEVEL_HANDSHAKE: + return QUIC_PACKET_TYPE_HANDSHAKE; + default: + return -1; + } +} + +/* Set <*level> and <*next_level> depending on QUIC handshake state. */ +static inline int quic_get_tls_enc_levels(enum quic_tls_enc_level *level, + enum quic_tls_enc_level *next_level, + enum quic_handshake_state state) +{ + switch (state) { + case QUIC_HS_ST_SERVER_INITIAL: + case QUIC_HS_ST_CLIENT_INITIAL: + *level = QUIC_TLS_ENC_LEVEL_INITIAL; + *next_level = QUIC_TLS_ENC_LEVEL_HANDSHAKE; + break; + case QUIC_HS_ST_SERVER_HANDSHAKE: + case QUIC_HS_ST_CLIENT_HANDSHAKE: + case QUIC_HS_ST_COMPLETE: + case QUIC_HS_ST_CONFIRMED: + *level = QUIC_TLS_ENC_LEVEL_HANDSHAKE; + *next_level = QUIC_TLS_ENC_LEVEL_APP; + break; + default: + return 0; + } + + return 1; +} + +/* Flag the keys at encryption level as discarded. */ +static inline void quic_tls_discard_keys(struct quic_enc_level *qel) +{ + qel->tls_ctx.rx.flags |= QUIC_FL_TLS_SECRETS_DCD; + qel->tls_ctx.tx.flags |= QUIC_FL_TLS_SECRETS_DCD; +} + +/* Derive the initial secrets with as QUIC TLS context which is the + * cryptographic context for the first encryption level (Initial) from + * connection ID with as length (in bytes) for a server or not + * depending on boolean value. + * Return 1 if succeeded or 0 if not. + */ +static inline int qc_new_isecs(struct connection *conn, + const unsigned char *cid, size_t cidlen, int server) +{ + unsigned char initial_secret[32]; + /* Initial secret to be derived for incoming packets */ + unsigned char rx_init_sec[32]; + /* Initial secret to be derived for outgoing packets */ + unsigned char tx_init_sec[32]; + struct quic_tls_secrets *rx_ctx, *tx_ctx; + struct quic_tls_ctx *ctx; + + TRACE_ENTER(QUIC_EV_CONN_ISEC, conn); + ctx = &conn->qc->els[QUIC_TLS_ENC_LEVEL_INITIAL].tls_ctx; + quic_initial_tls_ctx_init(ctx); + if (!quic_derive_initial_secret(ctx->rx.md, + initial_secret, sizeof initial_secret, + cid, cidlen)) + goto err; + + if (!quic_tls_derive_initial_secrets(ctx->rx.md, + rx_init_sec, sizeof rx_init_sec, + tx_init_sec, sizeof tx_init_sec, + initial_secret, sizeof initial_secret, server)) + goto err; + + rx_ctx = &ctx->rx; + tx_ctx = &ctx->tx; + if (!quic_tls_derive_keys(ctx->rx.aead, ctx->rx.hp, ctx->rx.md, + rx_ctx->key, sizeof rx_ctx->key, + rx_ctx->iv, sizeof rx_ctx->iv, + rx_ctx->hp_key, sizeof rx_ctx->hp_key, + rx_init_sec, sizeof rx_init_sec)) + goto err; + + rx_ctx->flags |= QUIC_FL_TLS_SECRETS_SET; + if (!quic_tls_derive_keys(ctx->tx.aead, ctx->tx.hp, ctx->tx.md, + tx_ctx->key, sizeof tx_ctx->key, + tx_ctx->iv, sizeof tx_ctx->iv, + tx_ctx->hp_key, sizeof tx_ctx->hp_key, + tx_init_sec, sizeof tx_init_sec)) + goto err; + + tx_ctx->flags |= QUIC_FL_TLS_SECRETS_SET; + TRACE_LEAVE(QUIC_EV_CONN_ISEC, conn, rx_init_sec, tx_init_sec); + + return 1; + + err: + TRACE_DEVEL("leaving in error", QUIC_EV_CONN_EISEC, conn); + return 0; +} + +#endif /* USE_QUIC */ +#endif /* _PROTO_QUIC_TLS_H */ + diff --git a/include/haproxy/xprt_quic-t.h b/include/haproxy/xprt_quic-t.h new file mode 100644 index 000000000..b25c3eb45 --- /dev/null +++ b/include/haproxy/xprt_quic-t.h @@ -0,0 +1,640 @@ +/* + * include/haproxy/xprt_quic-t.h + * This file contains applet function prototypes + * + * Copyright 2019 HAProxy Technologies, Frédéric Lécaille + * + * 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_XPRT_QUIC_T_H +#define _HAPROXY_XPRT_QUIC_T_H +#ifdef USE_QUIC +#ifndef USE_OPENSSL +#error "Must define USE_OPENSSL" +#endif + +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#define QUIC_PROTOCOL_VERSION_DRAFT_28 0xff00001c /* draft-28 */ + +#define QUIC_INITIAL_IPV4_MTU 1252 /* (bytes) */ +#define QUIC_INITIAL_IPV6_MTU 1232 +/* XXX TO DO XXX */ +/* Maximum packet length during handshake */ +#define QUIC_PACKET_MAXLEN QUIC_INITIAL_IPV4_MTU + +/* The minimum length of Initial packets. */ +#define QUIC_INITIAL_PACKET_MINLEN 1200 + +/* + * QUIC CID lengths. This the length of the connection IDs for this QUIC + * implementation. + */ +#define QUIC_CID_LEN 8 + +/* Common definitions for short and long QUIC packet headers. */ +/* QUIC connection ID maximum length for version 1. */ +#define QUIC_CID_MAXLEN 20 /* bytes */ +/* + * All QUIC packets with long headers are made of at least (in bytes): + * flags(1), version(4), DCID length(1), DCID(0..20), SCID length(1), SCID(0..20) + */ +#define QUIC_LONG_PACKET_MINLEN 7 +/* + * All QUIC packets with short headers are made of at least (in bytes): + * flags(1), DCID length(1), DCID(0..20) + */ +#define QUIC_SHORT_PACKET_MINLEN 2 +/* Byte 0 of QUIC packets. */ +#define QUIC_PACKET_LONG_HEADER_BIT 0x80 /* Long header format if set, short if not. */ +#define QUIC_PACKET_FIXED_BIT 0x40 /* Must always be set for all the headers. */ + +/* + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+ + * |1|1|T|T|X|X|X|X| + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Version (32) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | DCID Len (8) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Destination Connection ID (0..160) ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SCID Len (8) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Source Connection ID (0..160) ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * Long Header Packet Format + */ + +/* Two bits (T) for QUIC packet types. */ +#define QUIC_PACKET_TYPE_BITMASK 0x03 +#define QUIC_PACKET_TYPE_SHIFT 4 + +enum quic_pkt_type { + QUIC_PACKET_TYPE_INITIAL, + QUIC_PACKET_TYPE_0RTT, + QUIC_PACKET_TYPE_HANDSHAKE, + QUIC_PACKET_TYPE_RETRY, + /* + * The following one is not defined by the RFC but we define it for our + * own convenience. + */ + QUIC_PACKET_TYPE_SHORT, +}; + +/* Packet number field length. */ +#define QUIC_PACKET_PNL_BITMASK 0x03 +#define QUIC_PACKET_PN_MAXLEN 4 + +/* + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+ + * |0|1|S|R|R|K|P|P| + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Destination Connection ID (0..160) ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Packet Number (8/16/24/32) ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Protected Payload (*) ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * Short Header Packet Format + */ + +/* Bit (S) of short header. */ +#define QUIC_PACKET_SPIN_BIT 0x20 + +/* Reserved Bits (R): The next two bits of byte 0 are reserved. + * These bits are protected using header protection + * (see Section 5.4 of [QUIC-TLS]). The value included + * prior to protection MUST be set to 0. An endpoint MUST treat + * receipt of a packet that has a non-zero value for these bits, + * after removing both packet and header protection, as a connection + * error of type PROTOCOL_VIOLATION. Discarding such a packet after + * only removing header protection can expose the endpoint to attacks + * (see Section 9.3 of [QUIC-TLS]). + */ +#define QUIC_PACKET_RESERVED_BITS 0x18 /* (protected) */ + +#define QUIC_PACKET_KEY_PHASE_BIT 0x04 /* (protected) */ + +/* + * Tranport level error codes. + */ +#define NO_ERROR 0x00 +#define INTERNAL_ERROR 0x01 +#define CONNECTION_REFUSED_ERROR 0x02 +#define FLOW_CONTROL_ERROR 0x03 +#define STREAM_LIMIT_ERROR 0x04 +#define STREAM_STATE_ERROR 0x05 +#define FINAL_SIZE_ERROR 0x06 +#define FRAME_ENCODING_ERROR 0x07 +#define TRANSPORT_PARAMETER_ERROR 0x08 +#define CONNECTION_ID_LIMIT_ERROR 0x09 +#define PROTOCOL_VIOLATION 0x0a +#define INVALID_TOKEN 0x0b +#define APPLICATION_ERROR 0x0c +#define CRYPTO_BUFFER_EXCEEDED 0x0d + +/* XXX TODO: check/complete this remaining part (256 crypto reserved errors). */ +#define CRYPTO_ERROR 0x100 + +/* The maximum number of QUIC packets stored by the fd I/O handler by QUIC + * connection. Must be a power of two. + */ +#define QUIC_CONN_MAX_PACKET 64 + +#define QUIC_STATELESS_RESET_TOKEN_LEN 16 + +#define QUIC_EV_CONN_NEW (1ULL << 0) +#define QUIC_EV_CONN_INIT (1ULL << 1) +#define QUIC_EV_CONN_ISEC (1ULL << 2) +#define QUIC_EV_CONN_RSEC (1ULL << 3) +#define QUIC_EV_CONN_WSEC (1ULL << 4) +#define QUIC_EV_CONN_RWSEC (1ULL << 5) +#define QUIC_EV_CONN_LPKT (1ULL << 6) +#define QUIC_EV_CONN_SPKT (1ULL << 7) +#define QUIC_EV_CONN_CHPKT (1ULL << 8) +#define QUIC_EV_CONN_HPKT (1ULL << 9) +#define QUIC_EV_CONN_PAPKT (1ULL << 10) +#define QUIC_EV_CONN_PAPKTS (1ULL << 11) +#define QUIC_EV_CONN_HDSHK (1ULL << 12) +#define QUIC_EV_CONN_RMHP (1ULL << 13) +#define QUIC_EV_CONN_PRSHPKT (1ULL << 14) +#define QUIC_EV_CONN_PRSAPKT (1ULL << 15) +#define QUIC_EV_CONN_PRSFRM (1ULL << 16) +#define QUIC_EV_CONN_PRSAFRM (1ULL << 17) +#define QUIC_EV_CONN_BFRM (1ULL << 18) +#define QUIC_EV_CONN_PHPKTS (1ULL << 19) +#define QUIC_EV_CONN_TRMHP (1ULL << 20) +#define QUIC_EV_CONN_ELRMHP (1ULL << 21) +#define QUIC_EV_CONN_ELRXPKTS (1ULL << 22) +#define QUIC_EV_CONN_SSLDATA (1ULL << 23) +#define QUIC_EV_CONN_RXCDATA (1ULL << 24) +#define QUIC_EV_CONN_ADDDATA (1ULL << 25) +#define QUIC_EV_CONN_FFLIGHT (1ULL << 26) +#define QUIC_EV_CONN_SSLALERT (1ULL << 27) +#define QUIC_EV_CONN_CPAPKT (1ULL << 28) +#define QUIC_EV_CONN_RTTUPDT (1ULL << 29) +#define QUIC_EV_CONN_CC (1ULL << 30) +#define QUIC_EV_CONN_SPPKTS (1ULL << 31) +#define QUIC_EV_CONN_PKTLOSS (1ULL << 32) +#define QUIC_EV_CONN_STIMER (1ULL << 33) +#define QUIC_EV_CONN_PTIMER (1ULL << 34) +#define QUIC_EV_CONN_SPTO (1ULL << 35) + +#define QUIC_EV_CONN_ENEW (1ULL << 32) +#define QUIC_EV_CONN_EISEC (1ULL << 33) +#define QUIC_EV_CONN_ERSEC (1ULL << 34) +#define QUIC_EV_CONN_EWSEC (1ULL << 35) +#define QUIC_EV_CONN_ELPKT (1ULL << 36) +#define QUIC_EV_CONN_ESPKT (1ULL << 37) +#define QUIC_EV_CONN_ECHPKT (1ULL << 38) +#define QUIC_EV_CONN_EHPKT (1ULL << 39) +#define QUIC_EV_CONN_EPAPKT (1ULL << 40) + +/* Similar to kernel min()/max() definitions. */ +#define QUIC_MIN(a, b) ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + (void) (&_a == &_b); \ + _a < _b ? _a : _b; }) + +#define QUIC_MAX(a, b) ({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + (void) (&_a == &_b); \ + _a > _b ? _a : _b; }) + +extern struct trace_source trace_quic; +extern struct pool_head *pool_head_quic_rx_packet; +extern struct pool_head *pool_head_quic_tx_packet; +extern struct pool_head *pool_head_quic_tx_frm; + +/* + * This struct is used by ebmb_node structs as last member of flexible arrays. + * So do not change the order of the member of quic_cid struct. + * member must be the first one. + */ +struct quic_cid { + unsigned char data[QUIC_CID_MAXLEN + sizeof(in_port_t) + sizeof(struct in6_addr)]; + unsigned char len; +}; + +/* The data structure used to build a set of connection IDs for each connection. */ +struct quic_connection_id { + struct eb64_node seq_num; + uint64_t retire_prior_to; + struct quic_cid cid; + unsigned char stateless_reset_token[QUIC_STATELESS_RESET_TOKEN_LEN]; +}; + +struct preferred_address { + uint16_t ipv4_port; + uint16_t ipv6_port; + uint8_t ipv4_addr[4]; + uint8_t ipv6_addr[16]; + struct quic_cid cid; + uint8_t stateless_reset_token[QUIC_STATELESS_RESET_TOKEN_LEN]; +}; + +/* Default values for some of transport parameters */ +#define QUIC_DFLT_MAX_PACKET_SIZE 65527 +#define QUIC_DFLT_ACK_DELAY_COMPONENT 3 /* milliseconds */ +#define QUIC_DFLT_MAX_ACK_DELAY 25 /* milliseconds */ + +/* Types of QUIC transport parameters */ +#define QUIC_TP_ORIGINAL_DESTINATION_CONNECTION_ID 0 +#define QUIC_TP_IDLE_TIMEOUT 1 +#define QUIC_TP_STATELESS_RESET_TOKEN 2 +#define QUIC_TP_MAX_PACKET_SIZE 3 +#define QUIC_TP_INITIAL_MAX_DATA 4 +#define QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL 5 +#define QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE 6 +#define QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI 7 +#define QUIC_TP_INITIAL_MAX_STREAMS_BIDI 8 +#define QUIC_TP_INITIAL_MAX_STREAMS_UNI 9 +#define QUIC_TP_ACK_DELAY_EXPONENT 10 +#define QUIC_TP_MAX_ACK_DELAY 11 +#define QUIC_TP_DISABLE_ACTIVE_MIGRATION 12 +#define QUIC_TP_PREFERRED_ADDRESS 13 +#define QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT 14 +#define QUIC_TP_INITIAL_SOURCE_CONNECTION_ID 15 + +/* + * These defines are not for transport parameter type, but the maximum accepted value for + * transport parameter types. + */ +#define QUIC_TP_ACK_DELAY_EXPONENT_LIMIT 20 +#define QUIC_TP_MAX_ACK_DELAY_LIMIT (1UL << 14) + +/* The maximum length of encoded transport parameters for any QUIC peer. */ +#define QUIC_TP_MAX_ENCLEN 128 +/* + * QUIC transport parameters. + * Note that forbidden parameters sent by clients MUST generate TRANSPORT_PARAMETER_ERROR errors. + */ +struct quic_transport_params { + uint64_t idle_timeout; + uint64_t max_packet_size; /* Default: 65527 (max of UDP payload for IPv6) */ + uint64_t initial_max_data; + uint64_t initial_max_stream_data_bidi_local; + uint64_t initial_max_stream_data_bidi_remote; + uint64_t initial_max_stream_data_uni; + uint64_t initial_max_streams_bidi; + uint64_t initial_max_streams_uni; + uint64_t ack_delay_exponent; /* Default: 3, max: 20 */ + uint64_t max_ack_delay; /* Default: 3ms, max: 2^14ms*/ + uint64_t active_connection_id_limit; + + /* Booleans */ + uint8_t disable_active_migration; + uint8_t with_stateless_reset_token; + uint8_t with_preferred_address; + uint8_t original_destination_connection_id_present; + uint8_t initial_source_connection_id_present; + + uint8_t stateless_reset_token[QUIC_STATELESS_RESET_TOKEN_LEN]; /* Forbidden for clients */ + /* + * MUST be sent by servers. + * When received by clients, must be set to 1 if present. + */ + struct quic_cid original_destination_connection_id; /* Forbidden for clients */ + /* MUST be present both for servers and clients. */ + struct quic_cid initial_source_connection_id; + struct preferred_address preferred_address; /* Forbidden for clients */ +}; + +/* Structure for ACK ranges sent in ACK frames. */ +struct quic_ack_range { + struct list list; + int64_t first; + int64_t last; +}; + +struct quic_ack_ranges { + /* list of ACK ranges. */ + struct list list; + /* The number of ACK ranges is this lists */ + size_t sz; + /* The number of bytes required to encode this ACK ranges lists. */ + size_t enc_sz; +}; + +/* Flag the packet number space as requiring an ACK frame to be sent. */ +#define QUIC_FL_PKTNS_ACK_REQUIRED (1UL << 0) +#define QUIC_FL_PKTNS_ACK_RECEIVED (1UL << 1) + +/* The maximum number of dgrams which may be sent upon PTO expirations. */ +#define QUIC_MAX_NB_PTO_DGRAMS 2 + +/* QUIC packet number space */ +struct quic_pktns { + struct { + /* List of frames to send. */ + struct list frms; + /* Next packet number to use for transmissions. */ + int64_t next_pn; + /* Largest acked sent packet. */ + int64_t largest_acked_pn; + /* The packet which has been sent. */ + struct eb_root pkts; + /* The time the most recent ack-eliciting packer was sent. */ + unsigned int time_of_last_eliciting; + /* The time this packet number space has experienced packet loss. */ + unsigned int loss_time; + /* Boolean to denote if we must send probe packet. */ + unsigned int pto_probe; + /* In flight bytes for this packet number space. */ + size_t in_flight; + } tx; + struct { + /* Largest packet number */ + int64_t largest_pn; + /* Number of ack-eliciting packets. */ + size_t nb_ack_eliciting; + struct quic_ack_ranges ack_ranges; + } rx; + unsigned int flags; +}; + +/* The QUIC packet numbers are 62-bits integers */ +#define QUIC_MAX_PACKET_NUM ((1ULL << 62) - 1) + +/* Default QUIC connection transport parameters */ +extern struct quic_transport_params quic_dflt_transport_params; + +/* Flag a received packet as being an ack-eliciting packet. */ +#define QUIC_FL_RX_PACKET_ACK_ELICITING (1UL << 0) + +struct quic_rx_packet { + struct list list; + struct list rx_list; + struct quic_conn *qc; + unsigned char type; + uint32_t version; + /* Initial desctination connection ID. */ + struct quic_cid dcid; + struct quic_cid scid; + size_t odcid_len; + size_t pn_offset; + /* Packet number */ + int64_t pn; + /* Packet number length */ + uint32_t pnl; + uint64_t token_len; + /* Packet length */ + uint64_t len; + /* Additional authenticated data length */ + size_t aad_len; + unsigned char data[QUIC_PACKET_MAXLEN]; + struct eb64_node pn_node; + volatile unsigned int refcnt; + /* Source address of this packet. */ + struct sockaddr_storage saddr; + unsigned int flags; +}; + +/* UDP datagram context used by the I/O handler receiver callbacks. + * Useful to store the connection + */ +struct quic_dgram_ctx { + struct quic_conn *qc; + struct ebmb_node *dcid_node; + void *owner; +}; + +/* QUIC packet reader. */ +typedef ssize_t qpkt_read_func(unsigned char **buf, + const unsigned char *end, + struct quic_rx_packet *qpkt, + struct quic_dgram_ctx *dgram_ctx, + struct sockaddr_storage *saddr); + +/* Structure to store enough information about the RX CRYPTO frames. */ +struct quic_rx_crypto_frm { + struct eb64_node offset_node; + uint64_t len; + const unsigned char *data; + struct quic_rx_packet *pkt; +}; + +/* Flag a sent packet as being an ack-eliciting packet. */ +#define QUIC_FL_TX_PACKET_ACK_ELICITING (1UL << 0) +/* Flag a sent packet as containing a PADDING frame. */ +#define QUIC_FL_TX_PACKET_PADDING (1UL << 1) +/* Flag a sent packet as being in flight. */ +#define QUIC_FL_TX_PACKET_IN_FLIGHT (QUIC_FL_TX_PACKET_ACK_ELICITING | QUIC_FL_TX_PACKET_PADDING) + +/* Structure to store enough information about TX QUIC packets. */ +struct quic_tx_packet { + /* List entry point. */ + struct list list; + /* This is not the packet length but the length of outstanding data + * for in flight TX packet. + */ + size_t in_flight_len; + struct eb64_node pn_node; + /* The number of bytes of CRYPTO data in this packet. */ + unsigned int cdata_len; + /* The list of frames of this packet. */ + struct list frms; + /* The time this packet was sent (usec). */ + unsigned int time_sent; + /* Packet number spakce. */ + struct quic_pktns *pktns; + /* Flags. */ + unsigned int flags; +}; + +/* Structure to stora enough information about the TX frames. */ +struct quic_tx_frm { + struct list list; + unsigned char type; + union { + struct quic_crypto crypto; + struct quic_new_connection_id new_connection_id; + }; +}; + + +#define QUIC_CRYPTO_BUF_SHIFT 10 +#define QUIC_CRYPTO_BUF_MASK ((1UL << QUIC_CRYPTO_BUF_SHIFT) - 1) +/* The maximum allowed size of CRYPTO data buffer provided by the TLS stack. */ +#define QUIC_CRYPTO_BUF_SZ (1UL << QUIC_CRYPTO_BUF_SHIFT) /* 1 KB */ + +/* The maximum number of bytes of CRYPTO data in flight during handshakes. */ +#define QUIC_CRYPTO_IN_FLIGHT_MAX 4096 + +/* + * CRYPTO buffer struct. + * Such buffers are used to send CRYPTO data. + */ +struct quic_crypto_buf { + unsigned char data[QUIC_CRYPTO_BUF_SZ]; + size_t sz; +}; + +/* QUIC buffer structure used to build outgoing packets. */ +struct q_buf { + /* Points to the data in this buffer. */ + unsigned char *area; + /* Points to the current position to write into this buffer. */ + unsigned char *pos; + /* Point to the end of this buffer past one. */ + const unsigned char *end; + /* The number of data bytes in this buffer. */ + size_t data; + /* The list of packets attached to this buffer which have not been already sent. */ + struct list pkts; +}; + +struct quic_enc_level { + enum ssl_encryption_level_t level; + struct quic_tls_ctx tls_ctx; + struct { + /* The packets received by the listener I/O handler + with header protection removed. */ + struct eb_root pkts; + /* Liste of QUIC packets with protected header. */ + struct list pqpkts; + /* Crypto frames */ + struct { + uint64_t offset; + struct eb_root frms; /* XXX TO CHECK XXX */ + } crypto; + } rx; + struct { + struct { + struct quic_crypto_buf **bufs; + /* The number of element in use in the previous array. */ + size_t nb_buf; + /* The total size of the CRYPTO data stored in the CRYPTO buffers. */ + size_t sz; + /* The offset of the CRYPT0 data stream. */ + uint64_t offset; + } crypto; + } tx; + struct quic_pktns *pktns; +}; + +struct quic_path { + /* Control congestion. */ + struct quic_cc cc; + /* Packet loss detection information. */ + struct quic_loss loss; + + /* MTU. */ + size_t mtu; + /* Congestion window. */ + uint64_t cwnd; + /* Minimum congestion window. */ + uint64_t min_cwnd; + /* Prepared data to be sent (in bytes). */ + uint64_t prep_in_flight; + /* Outstanding data (in bytes). */ + uint64_t in_flight; + /* Number of in flight ack-eliciting packets. */ + uint64_t in_flight_ae_pkts; +}; + +/* The number of buffers for outgoing packets (must be a power of two). */ +#define QUIC_CONN_TX_BUFS_NB 8 +#define QUIC_CONN_TX_BUF_SZ QUIC_PACKET_MAXLEN + +struct quic_conn { + uint32_t version; + + /* Transport parameters. */ + struct quic_transport_params params; + unsigned char enc_params[QUIC_TP_MAX_ENCLEN]; /* encoded QUIC transport parameters */ + size_t enc_params_len; + + /* + * Original Destination Connection ID (comming with first client Initial packets). + * Used only by servers. + */ + struct ebmb_node odcid_node; + struct quic_cid odcid; + + struct quic_cid dcid; + struct ebmb_node scid_node; + struct quic_cid scid; + struct eb_root cids; + + struct quic_enc_level els[QUIC_TLS_ENC_LEVEL_MAX]; + + struct quic_transport_params rx_tps; + + struct quic_pktns pktns[QUIC_TLS_PKTNS_MAX]; + + /* Used only to reach the tasklet for the I/O handler from this quic_conn object. */ + struct connection *conn; + /* Output buffer used during the handshakes. */ + struct { + unsigned char data[QUIC_PACKET_MAXLEN]; + unsigned char *pos; + } obuf; + + struct { + /* The remaining frames to send. */ + struct list frms_to_send; + + /* Array of buffers. */ + struct q_buf **bufs; + /* The size of the previous array. */ + size_t nb_buf; + /* Writer index. */ + int wbuf; + /* Reader index. */ + int rbuf; + /* Number of sent bytes. */ + uint64_t bytes; + /* The number of datagrams which may be sent + * when sending probe packets. + */ + int nb_pto_dgrams; + } tx; + struct { + /* Number of received bytes. */ + uint64_t bytes; + } rx; + /* In flight CRYPTO data counter. */ + size_t ifcdata; + unsigned int max_ack_delay; + struct quic_path paths[1]; + struct quic_path *path; + + struct task *timer_task; + unsigned int timer; +}; + +#endif /* USE_QUIC */ +#endif /* _HAPROXY_XPRT_QUIC_T_H */ diff --git a/include/haproxy/xprt_quic.h b/include/haproxy/xprt_quic.h new file mode 100644 index 000000000..5b31c6716 --- /dev/null +++ b/include/haproxy/xprt_quic.h @@ -0,0 +1,1209 @@ +/* + * include/haproxy/xprt_quic.h + * This file contains QUIC xprt function prototypes + * + * Copyright 2020 HAProxy Technologies, Frédéric Lécaille + * + * 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_XPRT_QUIC_H +#define _HAPROXY_XPRT_QUIC_H +#ifdef USE_QUIC +#ifndef USE_OPENSSL +#error "Must define USE_OPENSSL" +#endif + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +extern struct pool_head *pool_head_quic_connection_id; + +int ssl_quic_initial_ctx(struct bind_conf *bind_conf); + +/* Returns the required length in bytes to encode QUIC connection ID. */ +static inline size_t sizeof_quic_cid(const struct quic_cid *cid) +{ + return sizeof cid->len + cid->len; +} + +/* Copy QUIC CID to . + * This is the responsability of the caller to check there is enough room in + * to copy . + * Always succeeds. + */ +static inline void quic_cid_cpy(struct quic_cid *dst, const struct quic_cid *src) +{ + memcpy(dst->data, src->data, src->len); + dst->len = src->len; +} + +/* Concatenate the port and address of to QUIC connection ID. + * Returns the number of bytes concatenated to . + */ +static inline size_t quic_cid_saddr_cat(struct quic_cid *cid, struct sockaddr_storage *saddr) +{ + void *port, *addr; + size_t port_len, addr_len; + + if (saddr->ss_family == AF_INET6) { + port = &((struct sockaddr_in6 *)saddr)->sin6_port; + addr = &((struct sockaddr_in6 *)saddr)->sin6_addr; + port_len = sizeof ((struct sockaddr_in6 *)saddr)->sin6_port; + addr_len = sizeof ((struct sockaddr_in6 *)saddr)->sin6_addr; + } + else { + port = &((struct sockaddr_in *)saddr)->sin_port; + addr = &((struct sockaddr_in *)saddr)->sin_addr; + port_len = sizeof ((struct sockaddr_in *)saddr)->sin_port; + addr_len = sizeof ((struct sockaddr_in *)saddr)->sin_addr; + } + memcpy(cid->data + cid->len, port, port_len); + cid->len += port_len; + memcpy(cid->data + cid->len, addr, addr_len); + cid->len += addr_len; + + return port_len + addr_len; +} + + +/* Dump the QUIC connection ID value if present (non null length). Used only for + * debugging purposes. + * Always succeeds. + */ +static inline void quic_cid_dump(struct buffer *buf, struct quic_cid *cid) +{ + int i; + + chunk_appendf(buf, "(%d", cid->len); + if (cid->len) + chunk_appendf(buf, ","); + for (i = 0; i < cid->len; i++) + chunk_appendf(buf, "%02x", cid->data[i]); + chunk_appendf(buf, ")"); +} + +/* Free the CIDs attached to QUIC connection. + * Always succeeds. + */ +static inline void free_quic_conn_cids(struct quic_conn *conn) +{ + struct eb64_node *node; + + node = eb64_first(&conn->cids); + while (node) { + struct quic_connection_id *cid; + + cid = eb64_entry(&node->node, struct quic_connection_id, seq_num); + node = eb64_next(node); + eb64_delete(&cid->seq_num); + pool_free(pool_head_quic_connection_id, cid); + } +} + +/* Copy new connection ID information to NEW_CONNECTION_ID frame data. + * Always succeeds. + */ +static inline void quic_connection_id_to_frm_cpy(struct quic_frame *dst, + struct quic_connection_id *src) +{ + struct quic_new_connection_id *to = &dst->new_connection_id; + + dst->type = QUIC_FT_NEW_CONNECTION_ID; + to->seq_num = src->seq_num.key; + to->retire_prior_to = src->retire_prior_to; + to->cid.len = src->cid.len; + to->cid.data = src->cid.data; + to->stateless_reset_token = src->stateless_reset_token; +} + +/* Allocate a new CID with as sequence number and attach it to + * ebtree. + * Returns the new CID if succedded, NULL if not. + */ +static inline struct quic_connection_id *new_quic_cid(struct eb_root *root, + int seq_num) +{ + struct quic_connection_id *cid; + + cid = pool_alloc(pool_head_quic_connection_id); + if (!cid) + return NULL; + + cid->cid.len = QUIC_CID_LEN; + if (RAND_bytes(cid->cid.data, cid->cid.len) != 1 || + RAND_bytes(cid->stateless_reset_token, + sizeof cid->stateless_reset_token) != 1) { + fprintf(stderr, "Could not generate %d random bytes\n", cid->cid.len); + goto err; + } + + cid->seq_num.key = seq_num; + cid->retire_prior_to = 0; + eb64_insert(root, &cid->seq_num); + + return cid; + + err: + pool_free(pool_head_quic_connection_id, cid); + return NULL; +} + +/* The maximum size of a variable-length QUIC integer encoded with 1 byte */ +#define QUIC_VARINT_1_BYTE_MAX ((1UL << 6) - 1) +/* The maximum size of a variable-length QUIC integer encoded with 2 bytes */ +#define QUIC_VARINT_2_BYTE_MAX ((1UL << 14) - 1) +/* The maximum size of a variable-length QUIC integer encoded with 4 bytes */ +#define QUIC_VARINT_4_BYTE_MAX ((1UL << 30) - 1) +/* The maximum size of a variable-length QUIC integer encoded with 8 bytes */ +#define QUIC_VARINT_8_BYTE_MAX ((1ULL << 62) - 1) + +/* The maximum size of a variable-length QUIC integer */ +#define QUIC_VARINT_MAX_SIZE 8 + +/* The two most significant bits of byte #0 from a QUIC packet gives the 2 + * logarithm of the length of a variable length encoded integer. + */ +#define QUIC_VARINT_BYTE_0_BITMASK 0x3f +#define QUIC_VARINT_BYTE_0_SHIFT 6 + +/* Return a 32-bits integer in from QUIC packet with as address. + * Makes point to the data after this 32-bits value if succeeded. + * Note that these 32-bits integers are network bytes ordered. + * Returns 0 if failed (not enough data in the buffer), 1 if succeeded. + */ +static inline int quic_read_uint32(uint32_t *val, + const unsigned char **buf, + const unsigned char *end) +{ + if (end - *buf < sizeof *val) + return 0; + + *val = ntohl(*(uint32_t *)*buf); + *buf += sizeof *val; + + return 1; +} + +/* Write a 32-bits integer to a buffer with as address. + * Make point to the data after this 32-buts value if succeeded. + * Note that thes 32-bits integers are networkg bytes ordered. + * Returns 0 if failed (not enough room in the buffer), 1 if succeeded. + */ +static inline int quic_write_uint32(unsigned char **buf, + const unsigned char *end, uint32_t val) +{ + if (end - *buf < sizeof val) + return 0; + + *(uint32_t *)*buf = htonl(val); + *buf += sizeof val; + + return 1; +} + + +/* Returns enough log2 of first powers of two to encode QUIC variable length + * integers. + * Returns -1 if if out of the range of lengths supported by QUIC. + */ +static inline int quic_log2(unsigned int val) +{ + switch (val) { + case 8: + return 3; + case 4: + return 2; + case 2: + return 1; + case 1: + return 0; + default: + return -1; + } +} + +/* Returns the size in bytes required to encode a 64bits integer if + * not out of range (< (1 << 62)), or 0 if out of range. + */ +static inline size_t quic_int_getsize(uint64_t val) +{ + switch (val) { + case 0 ... QUIC_VARINT_1_BYTE_MAX: + return 1; + case QUIC_VARINT_1_BYTE_MAX + 1 ... QUIC_VARINT_2_BYTE_MAX: + return 2; + case QUIC_VARINT_2_BYTE_MAX + 1 ... QUIC_VARINT_4_BYTE_MAX: + return 4; + case QUIC_VARINT_4_BYTE_MAX + 1 ... QUIC_VARINT_8_BYTE_MAX: + return 8; + default: + return 0; + } +} + +/* Return the difference between the encoded length of and the encoded + * length of . + */ +static inline size_t quic_incint_size_diff(uint64_t val) +{ + switch (val) { + case QUIC_VARINT_1_BYTE_MAX: + return 1; + case QUIC_VARINT_2_BYTE_MAX: + return 2; + case QUIC_VARINT_4_BYTE_MAX: + return 4; + default: + return 0; + } +} + +/* Return the difference between the encoded length of and the encoded + * length of . + */ +static inline size_t quic_decint_size_diff(uint64_t val) +{ + switch (val) { + case QUIC_VARINT_1_BYTE_MAX + 1: + return 1; + case QUIC_VARINT_2_BYTE_MAX + 1: + return 2; + case QUIC_VARINT_4_BYTE_MAX + 1: + return 4; + default: + return 0; + } +} + + +/* Returns the maximum value of a QUIC variable-length integer with as size */ +static inline uint64_t quic_max_int(size_t sz) +{ + switch (sz) { + case 1: + return QUIC_VARINT_1_BYTE_MAX; + case 2: + return QUIC_VARINT_2_BYTE_MAX; + case 4: + return QUIC_VARINT_4_BYTE_MAX; + case 8: + return QUIC_VARINT_8_BYTE_MAX; + } + + return -1; +} + +/* Return the maximum number of bytes we must use to completely fill a + * buffer with as size for a data field of bytes prefixed by its QUIC + * variable-length (may be 0). + * Also put in <*len_sz> the size of this QUIC variable-length. + * So after returning from this function we have : <*len_sz> + = . + */ +static inline size_t max_available_room(size_t sz, size_t *len_sz) +{ + size_t sz_sz, ret; + size_t diff; + + sz_sz = quic_int_getsize(sz); + if (sz <= sz_sz) + return 0; + + ret = sz - sz_sz; + *len_sz = quic_int_getsize(ret); + /* Difference between the two sizes. Note that >= <*len_sz>. */ + diff = sz_sz - *len_sz; + if (unlikely(diff > 0)) + ret += diff; + + return ret; +} + +/* This function computes the maximum data we can put into a buffer with as + * size prefixed with a variable-length field "Length" whose value is the + * remaining data length, already filled of bytes which must be taken + * into an account by "Length" field, and finally followed by the data we want + * to put in this buffer prefixed again by a variable-length field. + * is the size of the buffer to fill. + * the number of bytes already put after the "Length" field. + * the number of bytes we want to at most put in the buffer. + * Also set <*dlen_sz> to the size of the data variable-length we want to put in + * the buffer. This is typically this function which must be used to fill as + * much as possible a QUIC packet made of only one CRYPTO or STREAM frames. + * Returns this computed size if there is enough room in the buffer, 0 if not. + */ +static inline size_t max_stream_data_size(size_t sz, size_t ilen, size_t dlen) +{ + size_t ret, len_sz, dlen_sz; + + /* + * The length of variable-length QUIC integers are powers of two. + * Look for the first 3length" field value which match our need. + * As we must put bytes in our buffer, the minimum value for + * is the number of bytes required to encode . + */ + for (len_sz = quic_int_getsize(ilen); + len_sz <= QUIC_VARINT_MAX_SIZE; + len_sz <<= 1) { + if (sz < len_sz + ilen) + return 0; + + ret = max_available_room(sz - len_sz - ilen, &dlen_sz); + if (!ret) + return 0; + + /* Check that <*len_sz> matches value */ + if (len_sz + ilen + dlen_sz + ret <= quic_max_int(len_sz)) + return ret < dlen ? ret : dlen; + } + + return 0; +} + +/* Decode a QUIC variable-length integer from buffer into . + * Note that the result is a 64-bits integer but with the less significant + * 62 bits as relevant information. The most significant 2 remaining bits encode + * the length of the integer. + * Returns 1 if succeeded there was enough data in ), 0 if not. + */ +static inline int quic_dec_int(uint64_t *val, + const unsigned char **buf, + const unsigned char *end) +{ + size_t len; + + if (*buf >= end) + return 0; + + len = 1 << (**buf >> QUIC_VARINT_BYTE_0_SHIFT); + if (*buf + len > end) + return 0; + + *val = *(*buf)++ & QUIC_VARINT_BYTE_0_BITMASK; + while (--len) + *val = (*val << 8) | *(*buf)++; + + return 1; +} + +/* Encode a QUIC variable-length integer from into buffer with as first + * byte address after the end of this buffer. + * Returns 1 if succeeded (there was enough room in buf), 0 if not. + */ +static inline int quic_enc_int(unsigned char **buf, const unsigned char *end, uint64_t val) +{ + size_t len; + unsigned int shift; + unsigned char size_bits, *head; + + len = quic_int_getsize(val); + if (!len || end - *buf < len) + return 0; + + shift = (len - 1) * 8; + /* set the bits of byte#0 which gives the length of the encoded integer */ + size_bits = quic_log2(len) << QUIC_VARINT_BYTE_0_SHIFT; + head = *buf; + while (len--) { + *(*buf)++ = val >> shift; + shift -= 8; + } + *head |= size_bits; + + return 1; +} + +/* Return the length in bytes of packet number depending on + * the largest ackownledged packet number. + */ +static inline size_t quic_packet_number_length(int64_t pn, + int64_t largest_acked_pn) +{ + int64_t max_nack_pkts; + + /* About packet number encoding, the RFC says: + * The sender MUST use a packet number size able to represent more than + * twice as large a range than the difference between the largest + * acknowledged packet and packet number being sent. + */ + max_nack_pkts = 2 * (pn - largest_acked_pn) + 1; + if (max_nack_pkts > 0xffffff) + return 4; + if (max_nack_pkts > 0xffff) + return 3; + if (max_nack_pkts > 0xff) + return 2; + + return 1; +} + +/* Encode packet number with as length in byte into a buffer with + * as current copy address and as pointer to one past the end of + * this buffer. This is the responsability of the caller to check there is + * enough room in the buffer to copy bytes. + * Never fails. + */ +static inline void quic_packet_number_encode(unsigned char **buf, + const unsigned char *end, + uint64_t pn, size_t pn_len) +{ + /* Encode the packet number. */ + switch (pn_len) { + case 1: + **buf = pn; + break; + case 2: + write_n16(*buf, pn); + break; + case 3: + (*buf)[0] = pn >> 16; + (*buf)[1] = pn >> 8; + (*buf)[2] = pn; + break; + case 4: + write_n32(*buf, pn); + break; + } + *buf += pn_len; +} + +/* Returns the field value from ACK frame for + * QUIC connection. + */ +static inline unsigned int quic_ack_delay_ms(struct quic_ack *ack_frm, + struct quic_conn *conn) +{ + return ack_frm->ack_delay << conn->rx_tps.ack_delay_exponent; +} + +/* Initialize transport parameters from . + * Never fails. + */ +static inline void quic_dflt_transport_params_cpy(struct quic_transport_params *dst) +{ + dst->max_packet_size = quic_dflt_transport_params.max_packet_size; + dst->ack_delay_exponent = quic_dflt_transport_params.ack_delay_exponent; + dst->max_ack_delay = quic_dflt_transport_params.max_ack_delay; +} + +/* Initialize

transport parameters depending boolean value which + * must be set to 1 for a server (haproxy listener), 0 for a client (connection + * to haproxy server). + * Never fails. + */ +static inline void quic_transport_params_init(struct quic_transport_params *p, + int server) +{ + quic_dflt_transport_params_cpy(p); + + p->idle_timeout = 30000; + + p->initial_max_data = 1 * 1024 * 1024; + p->initial_max_stream_data_bidi_local = 256 * 1024; + p->initial_max_stream_data_bidi_remote = 256 * 1024; + p->initial_max_stream_data_uni = 256 * 1024; + p->initial_max_streams_bidi = 100; + p->initial_max_streams_uni = 3; + + if (server) + p->with_stateless_reset_token = 1; + p->active_connection_id_limit = 8; + +} + +/* Encode preferred address transport parameter in without its + * "type+len" prefix. Note that the IP addresses must be encoded in network byte + * order. + * So ->ipv4_addr and ->ipv6_addr, which are buffers, must contained values + * already encoded in network byte order. + * It is the responsability of the caller to check there is enough room in to encode + * this address. + * Never fails. + */ +static inline void quic_transport_param_enc_pref_addr_val(unsigned char **buf, + const unsigned char *end, + struct preferred_address *addr) +{ + write_n16(*buf, addr->ipv4_port); + *buf += sizeof addr->ipv4_port; + + memcpy(*buf, addr->ipv4_addr, sizeof addr->ipv4_addr); + *buf += sizeof addr->ipv4_addr; + + write_n16(*buf, addr->ipv6_port); + *buf += sizeof addr->ipv6_port; + + memcpy(*buf, addr->ipv6_addr, sizeof addr->ipv6_addr); + *buf += sizeof addr->ipv6_addr; + + *(*buf)++ = addr->cid.len; + if (addr->cid.len) { + memcpy(*buf, addr->cid.data, addr->cid.len); + *buf += addr->cid.len; + } + + memcpy(*buf, addr->stateless_reset_token, sizeof addr->stateless_reset_token); + *buf += sizeof addr->stateless_reset_token; +} + +/* Decode into preferred address transport parameter found in <*buf> buffer. + * Returns 1 if succeeded, 0 if not. + */ +static inline int quic_transport_param_dec_pref_addr(struct preferred_address *addr, + const unsigned char **buf, + const unsigned char *end) +{ + ssize_t addr_len; + + addr_len = sizeof addr->ipv4_port + sizeof addr->ipv4_addr; + addr_len += sizeof addr->ipv6_port + sizeof addr->ipv6_addr; + addr_len += sizeof addr->cid.len; + + if (end - *buf < addr_len) + return 0; + + addr->ipv4_port = read_n16(*buf); + *buf += sizeof addr->ipv4_port; + + memcpy(addr->ipv4_addr, *buf, sizeof addr->ipv4_addr); + *buf += sizeof addr->ipv4_addr; + + addr->ipv6_port = read_n16(*buf); + *buf += sizeof addr->ipv6_port; + + memcpy(addr->ipv6_addr, *buf, sizeof addr->ipv6_addr); + *buf += sizeof addr->ipv6_addr; + + addr->cid.len = *(*buf)++; + if (addr->cid.len) { + if (end - *buf > addr->cid.len || addr->cid.len > sizeof addr->cid.data) + return 0; + memcpy(addr->cid.data, *buf, addr->cid.len); + *buf += addr->cid.len; + } + + if (end - *buf != sizeof addr->stateless_reset_token) + return 0; + + memcpy(addr->stateless_reset_token, *buf, end - *buf); + *buf += sizeof addr->stateless_reset_token; + + return *buf == end; +} + +/* Decode into

struct a transport parameter found in <*buf> buffer with + * as type and as length, depending on boolean value which + * must be set to 1 for a server (haproxy listener) or 0 for a client (connection + * to an haproxy server). + */ +static inline int quic_transport_param_decode(struct quic_transport_params *p, + int server, uint64_t type, + const unsigned char **buf, size_t len) +{ + const unsigned char *end = *buf + len; + + switch (type) { + case QUIC_TP_ORIGINAL_DESTINATION_CONNECTION_ID: + if (!server || len >= sizeof p->original_destination_connection_id.data) + return 0; + + if (len) + memcpy(p->original_destination_connection_id.data, *buf, len); + p->original_destination_connection_id.len = len; + *buf += len; + p->original_destination_connection_id_present = 1; + break; + case QUIC_TP_INITIAL_SOURCE_CONNECTION_ID: + if (len >= sizeof p->initial_source_connection_id.data) + return 0; + + if (len) + memcpy(p->initial_source_connection_id.data, *buf, len); + p->initial_source_connection_id.len = len; + *buf += len; + p->initial_source_connection_id_present = 1; + break; + case QUIC_TP_STATELESS_RESET_TOKEN: + if (!server || len != sizeof p->stateless_reset_token) + return 0; + memcpy(p->stateless_reset_token, *buf, len); + *buf += len; + p->with_stateless_reset_token = 1; + break; + case QUIC_TP_PREFERRED_ADDRESS: + if (!server) + return 0; + if (!quic_transport_param_dec_pref_addr(&p->preferred_address, buf, *buf + len)) + return 0; + p->with_preferred_address = 1; + break; + case QUIC_TP_IDLE_TIMEOUT: + if (!quic_dec_int(&p->idle_timeout, buf, end)) + return 0; + break; + case QUIC_TP_MAX_PACKET_SIZE: + if (!quic_dec_int(&p->max_packet_size, buf, end)) + return 0; + break; + case QUIC_TP_INITIAL_MAX_DATA: + if (!quic_dec_int(&p->initial_max_data, buf, end)) + return 0; + break; + case QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL: + if (!quic_dec_int(&p->initial_max_stream_data_bidi_local, buf, end)) + return 0; + break; + case QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE: + if (!quic_dec_int(&p->initial_max_stream_data_bidi_remote, buf, end)) + return 0; + break; + case QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI: + if (!quic_dec_int(&p->initial_max_stream_data_uni, buf, end)) + return 0; + break; + case QUIC_TP_INITIAL_MAX_STREAMS_BIDI: + if (!quic_dec_int(&p->initial_max_streams_bidi, buf, end)) + return 0; + break; + case QUIC_TP_INITIAL_MAX_STREAMS_UNI: + if (!quic_dec_int(&p->initial_max_streams_uni, buf, end)) + return 0; + break; + case QUIC_TP_ACK_DELAY_EXPONENT: + if (!quic_dec_int(&p->ack_delay_exponent, buf, end) || + p->ack_delay_exponent > QUIC_TP_ACK_DELAY_EXPONENT_LIMIT) + return 0; + break; + case QUIC_TP_MAX_ACK_DELAY: + if (!quic_dec_int(&p->max_ack_delay, buf, end) || + p->max_ack_delay > QUIC_TP_MAX_ACK_DELAY_LIMIT) + return 0; + break; + case QUIC_TP_DISABLE_ACTIVE_MIGRATION: + /* Zero-length parameter type. */ + if (len != 0) + return 0; + p->disable_active_migration = 1; + break; + case QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT: + if (!quic_dec_int(&p->active_connection_id_limit, buf, end)) + return 0; + break; + default: + *buf += len; + }; + + return *buf == end; +} + +/* Encode and variable length values in . + * Returns 1 if succeeded, 0 if not. + */ +static inline int quic_transport_param_encode_type_len(unsigned char **buf, + const unsigned char *end, + uint64_t type, uint64_t len) +{ + return quic_enc_int(buf, end, type) && quic_enc_int(buf, end, len); +} + +/* Decode variable length type and length values of a QUIC transport parameter + * into and found in <*buf> buffer. + * Returns 1 if succeeded, 0 if not. + */ +static inline int quic_transport_param_decode_type_len(uint64_t *type, uint64_t *len, + const unsigned char **buf, + const unsigned char *end) +{ + return quic_dec_int(type, buf, end) && quic_dec_int(len, buf, end); +} + +/* Encode bytes stream with as type and as length into buf. + * Returns 1 if succeded, 0 if not. + */ +static inline int quic_transport_param_enc_mem(unsigned char **buf, const unsigned char *end, + uint64_t type, void *param, uint64_t length) +{ + if (!quic_transport_param_encode_type_len(buf, end, type, length)) + return 0; + + if (end - *buf < length) + return 0; + + if (length) + memcpy(*buf, param, length); + *buf += length; + + return 1; +} + +/* Encode 64-bits value as variable length integer into . + * Returns 1 if succeeded, 0 if not. + */ +static inline int quic_transport_param_enc_int(unsigned char **buf, + const unsigned char *end, + uint64_t type, uint64_t val) +{ + size_t len; + + len = quic_int_getsize(val); + + return len && quic_transport_param_encode_type_len(buf, end, type, len) && + quic_enc_int(buf, end, val); +} + +/* Encode preferred address into . + * Note that the IP addresses must be encoded in network byte order. + * So ->ipv4_addr and ->ipv6_addr, which are buffers, must contained + * values already encoded in network byte order. + * Returns 1 if succeeded, 0 if not. + */ +static inline int quic_transport_param_enc_pref_addr(unsigned char **buf, + const unsigned char *end, + struct preferred_address *addr) +{ + uint64_t addr_len = 0; + + addr_len += sizeof addr->ipv4_port + sizeof addr->ipv4_addr; + addr_len += sizeof addr->ipv6_port + sizeof addr->ipv6_addr; + addr_len += sizeof_quic_cid(&addr->cid); + addr_len += sizeof addr->stateless_reset_token; + + if (!quic_transport_param_encode_type_len(buf, end, QUIC_TP_PREFERRED_ADDRESS, addr_len)) + return 0; + + if (end - *buf < addr_len) + return 0; + + quic_transport_param_enc_pref_addr_val(buf, end, addr); + + return 1; +} + +/* Encode

transport parameter into depending on value which + * must be set to 1 for a server (haproxy listener) or 0 for a client + * (connection to a haproxy server). + * Return the number of bytes consumed if succeeded, 0 if not. + */ +static inline int quic_transport_params_encode(unsigned char *buf, + const unsigned char *end, + struct quic_transport_params *p, + int server) +{ + unsigned char *head; + unsigned char *pos; + + head = pos = buf; + if (server) { + if (!quic_transport_param_enc_mem(&pos, end, + QUIC_TP_ORIGINAL_DESTINATION_CONNECTION_ID, + p->original_destination_connection_id.data, + p->original_destination_connection_id.len)) + return 0; + if (p->with_stateless_reset_token && + !quic_transport_param_enc_mem(&pos, end, QUIC_TP_STATELESS_RESET_TOKEN, + p->stateless_reset_token, + sizeof p->stateless_reset_token)) + return 0; + if (p->with_preferred_address && + !quic_transport_param_enc_pref_addr(&pos, end, &p->preferred_address)) + return 0; + } + + if (!quic_transport_param_enc_mem(&pos, end, + QUIC_TP_INITIAL_SOURCE_CONNECTION_ID, + p->initial_source_connection_id.data, + p->initial_source_connection_id.len)) + return 0; + + if (p->idle_timeout && + !quic_transport_param_enc_int(&pos, end, QUIC_TP_IDLE_TIMEOUT, p->idle_timeout)) + return 0; + + /* + * "max_packet_size" transport parameter must be transmitted only if different + * of the default value. + */ + if (p->max_packet_size != QUIC_DFLT_MAX_PACKET_SIZE && + !quic_transport_param_enc_int(&pos, end, QUIC_TP_MAX_PACKET_SIZE, p->max_packet_size)) + return 0; + + if (p->initial_max_data && + !quic_transport_param_enc_int(&pos, end, QUIC_TP_INITIAL_MAX_DATA, p->initial_max_data)) + return 0; + + if (p->initial_max_stream_data_bidi_local && + !quic_transport_param_enc_int(&pos, end, QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, + p->initial_max_stream_data_bidi_local)) + return 0; + + if (p->initial_max_stream_data_bidi_remote && + !quic_transport_param_enc_int(&pos, end, QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, + p->initial_max_stream_data_bidi_remote)) + return 0; + + if (p->initial_max_stream_data_uni && + !quic_transport_param_enc_int(&pos, end, QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI, + p->initial_max_stream_data_uni)) + return 0; + + if (p->initial_max_streams_bidi && + !quic_transport_param_enc_int(&pos, end, QUIC_TP_INITIAL_MAX_STREAMS_BIDI, + p->initial_max_streams_bidi)) + return 0; + + if (p->initial_max_streams_uni && + !quic_transport_param_enc_int(&pos, end, QUIC_TP_INITIAL_MAX_STREAMS_UNI, + p->initial_max_streams_uni)) + return 0; + + /* + * "ack_delay_exponent" transport parameter must be transmitted only if different + * of the default value. + */ + if (p->ack_delay_exponent != QUIC_DFLT_ACK_DELAY_COMPONENT && + !quic_transport_param_enc_int(&pos, end, QUIC_TP_ACK_DELAY_EXPONENT, p->ack_delay_exponent)) + return 0; + + /* + * "max_ack_delay" transport parameter must be transmitted only if different + * of the default value. + */ + if (p->max_ack_delay != QUIC_DFLT_MAX_ACK_DELAY && + !quic_transport_param_enc_int(&pos, end, QUIC_TP_MAX_ACK_DELAY, p->max_ack_delay)) + return 0; + + /* 0-length value */ + if (p->disable_active_migration && + !quic_transport_param_encode_type_len(&pos, end, QUIC_TP_DISABLE_ACTIVE_MIGRATION, 0)) + return 0; + + if (p->active_connection_id_limit && + !quic_transport_param_enc_int(&pos, end, QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, + p->active_connection_id_limit)) + return 0; + + return pos - head; +} + +/* Decode transport parameters found in buffer into

, depending on + * boolean value which must be set to 1 for a server (haproxy listener) + * or 0 for a client (connection to a haproxy server). + * Returns 1 if succeeded, 0 if not. + */ +static inline int quic_transport_params_decode(struct quic_transport_params *p, int server, + const unsigned char *buf, + const unsigned char *end) +{ + const unsigned char *pos; + + pos = buf; + + quic_transport_params_init(p, server); + while (pos != end) { + uint64_t type, len; + + if (!quic_transport_param_decode_type_len(&type, &len, &pos, end)) + return 0; + + if (end - pos < len) + return 0; + + if (!quic_transport_param_decode(p, server, type, &pos, len)) + return 0; + } + + /* + * A server MUST send original_destination_connection_id transport parameter. + * initial_source_connection_id must be present both for server and client. + */ + if ((server && !p->original_destination_connection_id_present) || + !p->initial_source_connection_id_present) + return 0; + + return 1; +} + +/* Store transport parameters found in buffer into QUIC connection + * depending on value which must be 1 for a server (haproxy listener) + * or 0 for a client (connection to a haproxy server). + * Returns 1 if succeeded, 0 if not. + */ +static inline int quic_transport_params_store(struct quic_conn *conn, int server, + const unsigned char *buf, + const unsigned char *end) +{ + if (!quic_transport_params_decode(&conn->rx_tps, server, buf, end)) + return 0; + + if (conn->rx_tps.max_ack_delay) + conn->max_ack_delay = conn->rx_tps.max_ack_delay; + + return 1; +} + +/* Initialize a QUIC packet number space. + * Never fails. + */ +static inline void quic_pktns_init(struct quic_pktns *pktns) +{ + LIST_INIT(&pktns->tx.frms); + pktns->tx.next_pn = -1; + pktns->tx.pkts = EB_ROOT_UNIQUE; + pktns->tx.largest_acked_pn = -1; + pktns->tx.time_of_last_eliciting = 0; + pktns->tx.loss_time = TICK_ETERNITY; + pktns->tx.in_flight = 0; + + pktns->rx.largest_pn = -1; + pktns->rx.nb_ack_eliciting = 0; + LIST_INIT(&pktns->rx.ack_ranges.list); + pktns->rx.ack_ranges.sz = 0; + pktns->rx.ack_ranges.enc_sz = 0; + + pktns->flags = 0; +} + +/* Discard packet number space attached to QUIC connection. + * Its loss information are reset. Deduce the outstanding bytes for this + * packet number space from the outstanding bytes for the path of this + * connection§. + * Note that all the non acknowledged TX packets and their frames are freed. + * Always succeeds. + */ +static inline void quic_pktns_discard(struct quic_pktns *pktns, + struct quic_conn *qc) +{ + struct eb64_node *node; + + pktns->tx.time_of_last_eliciting = 0; + pktns->tx.loss_time = TICK_ETERNITY; + pktns->tx.pto_probe = 0; + pktns->tx.in_flight = 0; + qc->path->loss.pto_count = 0; + qc->path->in_flight -= pktns->tx.in_flight; + + node = eb64_first(&pktns->tx.pkts); + while (node) { + struct quic_tx_packet *pkt; + struct quic_tx_frm *frm, *frmbak; + + pkt = eb64_entry(&node->node, struct quic_tx_packet, pn_node); + node = eb64_next(node); + list_for_each_entry_safe(frm, frmbak, &pkt->frms, list) { + LIST_DEL(&frm->list); + pool_free(pool_head_quic_tx_frm, frm); + } + eb64_delete(&pkt->pn_node); + pool_free(pool_head_quic_tx_packet, pkt); + } +} + +/* Initialize

QUIC network path depending on boolean + * which is true for an IPv4 path, if not false for an IPv6 path. + */ +static inline void quic_path_init(struct quic_path *path, int ipv4, + struct quic_cc_algo *algo, struct quic_conn *qc) +{ + unsigned int max_dgram_sz; + + max_dgram_sz = ipv4 ? QUIC_INITIAL_IPV4_MTU : QUIC_INITIAL_IPV6_MTU; + quic_loss_init(&path->loss); + path->mtu = max_dgram_sz; + path->cwnd = QUIC_MIN(10 * max_dgram_sz, QUIC_MAX(max_dgram_sz << 1, 14720U)); + path->min_cwnd = max_dgram_sz << 1; + path->in_flight = 0; + path->in_flight_ae_pkts = 0; + quic_cc_init(&path->cc, algo, qc); +} + +/* Return 1 if matches with the Application packet number space of + * connection which is common to the 0-RTT and 1-RTT encryption levels, 0 + * if not (handshake packets). + */ +static inline int quic_application_pktns(struct quic_pktns *pktns, struct quic_conn *conn) +{ + return pktns == &conn->pktns[QUIC_TLS_PKTNS_01RTT]; +} + +/* CRYPTO data buffer handling functions. */ +static inline unsigned char *c_buf_getpos(struct quic_enc_level *qel, uint64_t offset) +{ + int idx; + unsigned char *data; + + idx = offset >> QUIC_CRYPTO_BUF_SHIFT; + data = qel->tx.crypto.bufs[idx]->data; + return data + (offset & QUIC_CRYPTO_BUF_MASK); +} + +/* Returns 1 if the CRYPTO buffer at encryption level has been + * consumed (sent to the peer), 0 if not. + */ +static inline int c_buf_consumed(struct quic_enc_level *qel) +{ + return qel->tx.crypto.offset == qel->tx.crypto.sz; +} + + +/* QUIC buffer handling functions */ + +/* Returns the current buffer which may be used to build outgoing packets. */ +static inline struct q_buf *q_wbuf(struct quic_conn *qc) +{ + return qc->tx.bufs[qc->tx.wbuf]; +} + +static inline struct q_buf *q_rbuf(struct quic_conn *qc) +{ + return qc->tx.bufs[qc->tx.rbuf]; +} + +/* Returns the next buffer to be used to send packets from. */ +static inline struct q_buf *q_next_rbuf(struct quic_conn *qc) +{ + qc->tx.rbuf = (qc->tx.rbuf + 1) & (QUIC_CONN_TX_BUFS_NB - 1); + return q_rbuf(qc); +} + +/* Return the next buffer which may be used to build outgoing packets. + * Also decrement by one the number of remaining probing datagrams + * which may be sent. + */ +static inline struct q_buf *q_next_wbuf(struct quic_conn *qc) +{ + qc->tx.wbuf = (qc->tx.wbuf + 1) & (QUIC_CONN_TX_BUFS_NB - 1); + /* Decrement the number of prepared datagrams (only when probing). */ + if (qc->tx.nb_pto_dgrams) + --qc->tx.nb_pto_dgrams; + return q_wbuf(qc); +} + +/* Return the position of buffer to be used to write outgoing packets. */ +static inline unsigned char *q_buf_getpos(struct q_buf *buf) +{ + return buf->pos; +} + +/* Return the pointer to one past the end of buffer. */ +static inline const unsigned char *q_buf_end(struct q_buf *buf) +{ + return buf->end; +} + +/* Set the position of buffer to value. */ +static inline void q_buf_setpos(struct q_buf *buf, unsigned char *pos) +{ + buf->pos = pos; +} + +/* Returns the remaining amount of room left in buffer. */ +static inline ssize_t q_buf_room(struct q_buf *buf) +{ + return q_buf_end(buf) - q_buf_getpos(buf); +} + +/* Reset (or empty) buffer to prepare it for the next writting. */ +static inline void q_buf_reset(struct q_buf *buf) +{ + buf->pos = buf->area; + buf->data = 0; +} + +/* Returns 1 if is empty, 0 if not. */ +static inline int q_buf_empty(struct q_buf *buf) +{ + return !buf->data; +} + +/* Return 1 if header form is long, 0 if not. */ +static inline int qc_pkt_long(const struct quic_rx_packet *pkt) +{ + return pkt->type != QUIC_PACKET_TYPE_SHORT; +} + +/* Increment the reference counter of */ +static inline void quic_rx_packet_refinc(struct quic_rx_packet *pkt) +{ + pkt->refcnt++; +} + +/* Decrement the reference counter of */ +static inline void quic_rx_packet_refdec(struct quic_rx_packet *pkt) +{ + if (!--pkt->refcnt) + pool_free(pool_head_quic_rx_packet, pkt); +} + +/* Add RX packet to , incrementing its reference counter. */ +static inline void quic_rx_packet_list_addq(struct list *list, + struct quic_rx_packet *pkt) +{ + LIST_ADDQ(list, &pkt->list); + quic_rx_packet_refinc(pkt); +} + +/* Remove RX packet from , decrementing its reference counter. */ +static inline void quic_rx_packet_list_del(struct quic_rx_packet *pkt) +{ + LIST_DEL(&pkt->list); + quic_rx_packet_refdec(pkt); +} + +/* Add RX packet to tree, incrementing its reference counter. */ +static inline void quic_rx_packet_eb64_insert(struct eb_root *root, + struct eb64_node *node) +{ + eb64_insert(root, node); + quic_rx_packet_refinc(eb64_entry(node, struct quic_rx_packet, pn_node)); +} + +/* Delete RX packet from tree, decrementing its reference counter. */ +static inline void quic_rx_packet_eb64_delete(struct eb64_node *node) +{ + eb64_delete(node); + quic_rx_packet_refdec(eb64_entry(node, struct quic_rx_packet, pn_node)); +} + +/* Release the memory allocated for RX packet. */ +static inline void free_quic_rx_packet(struct quic_rx_packet *pkt) +{ + quic_rx_packet_refdec(pkt); +} + +int qc_new_conn_init(struct quic_conn *conn, int ipv4, + struct eb_root *quic_initial_clients, + struct eb_root *quic_clients, + unsigned char *dcid, size_t dcid_len, + unsigned char *scid, size_t scid_len); +ssize_t quic_lstnr_dgram_read(char *buf, size_t len, void *owner, + struct sockaddr_storage *saddr); +ssize_t quic_srv_dgram_read(char *buf, size_t len, void *owner, + struct sockaddr_storage *saddr); +#endif /* USE_QUIC */ +#endif /* _HAPROXY_XPRT_QUIC_H */