From: Frédéric Lécaille Date: Tue, 14 Jun 2022 15:40:39 +0000 (+0200) Subject: MEDIUM: quic: Compatible version negotiation implementation (draft-08) X-Git-Tag: v2.7-dev1~38 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=301425b880aa4f68cbc2b8346cecc775215ff4e6;p=thirdparty%2Fhaproxy.git MEDIUM: quic: Compatible version negotiation implementation (draft-08) At this time haproxy supported only incompatible version negotiation feature which consists in sending a Version Negotiation packet after having received a long packet without compatible value in its version field. This version value is the version use to build the current packet. This patch does not modify this behavior. This patch adds the support for compatible version negotiation feature which allows endpoints to negotiate during the first flight or packets sent by the client the QUIC version to use for the connection (or after the first flight). This is done thanks to "version_information" parameter sent by both endpoints. To be short, the client offers a list of supported versions by preference order. The server (or haproxy listener) chooses the first version it also supported as negotiated version. This implementation has an impact on the tranport parameters handling (in both direcetions). Indeed, the server must sent its version information, but only after received and parsed the client transport parameters). So we cannot encode these parameters at the same time we instantiated a new connection. Add QUIC_TP_DRAFT_VERSION_INFORMATION(0xff73db) new transport parameter. Add tp_version_information new C struct to handle this new parameter. Implement quic_transport_param_enc_version_info() (resp. quic_transport_param_dec_version_info()) to encode (resp. decode) this parameter. Add qc_conn_finalize() which encodes the transport parameters and configure the TLS stack to send them. Add ->negotiated_ictx quic_conn C struct new member to store the Initial QUIC TLS context for the negotiated version. The Initial secrets derivation is version dependent. Rename ->version to ->original_version and add ->negotiated_version to this C struct to reflect the QUIC-VN RFC denomination. Modify most of the QUIC TLS API functions to pass a version as parameter. Export the QUIC version definitions to be reused at least from quic_tp.c (transport parameters. Move the token check after the QUIC connection lookup. As this is the original version which is sent into a Retry packet, and because this original version is stored into the connection, we must check the token after having retreived this connection. Add packet version to traces. See https://datatracker.ietf.org/doc/html/draft-ietf-quic-version-negotiation-08 for more information about this new feature. --- diff --git a/include/haproxy/quic_tls.h b/include/haproxy/quic_tls.h index 244afe817f..02f36657fc 100644 --- a/include/haproxy/quic_tls.h +++ b/include/haproxy/quic_tls.h @@ -71,7 +71,7 @@ int quic_tls_encrypt(unsigned char *buf, size_t len, const unsigned char *key, const unsigned char *iv); int quic_tls_decrypt2(unsigned char *out, - unsigned char *in, size_t ilen, + const unsigned char *in, size_t ilen, unsigned char *aad, size_t aad_len, EVP_CIPHER_CTX *ctx, const EVP_CIPHER *aead, const unsigned char *key, const unsigned char *iv); @@ -505,6 +505,7 @@ static inline void quic_tls_discard_keys(struct quic_enc_level *qel) * Return 1 if succeeded or 0 if not. */ static inline int qc_new_isecs(struct quic_conn *qc, + struct quic_tls_ctx *ctx, const struct quic_version *ver, const unsigned char *cid, size_t cidlen, int server) { unsigned char initial_secret[32]; @@ -513,15 +514,13 @@ static inline int qc_new_isecs(struct quic_conn *qc, /* 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); - ctx = &qc->els[QUIC_TLS_ENC_LEVEL_INITIAL].tls_ctx; if (!quic_initial_tls_ctx_init(ctx)) goto err; if (!quic_derive_initial_secret(ctx->rx.md, - qc->version->initial_salt, qc->version->initial_salt_len, + ver->initial_salt, ver->initial_salt_len, initial_secret, sizeof initial_secret, cid, cidlen)) goto err; @@ -534,7 +533,7 @@ static inline int qc_new_isecs(struct quic_conn *qc, rx_ctx = &ctx->rx; tx_ctx = &ctx->tx; - if (!quic_tls_derive_keys(ctx->rx.aead, ctx->rx.hp, ctx->rx.md, qc->version, + if (!quic_tls_derive_keys(ctx->rx.aead, ctx->rx.hp, ctx->rx.md, ver, rx_ctx->key, rx_ctx->keylen, rx_ctx->iv, rx_ctx->ivlen, rx_ctx->hp_key, sizeof rx_ctx->hp_key, @@ -544,7 +543,7 @@ static inline int qc_new_isecs(struct quic_conn *qc, if (!quic_tls_rx_ctx_init(&rx_ctx->ctx, rx_ctx->aead, rx_ctx->key)) goto err; - if (!quic_tls_derive_keys(ctx->tx.aead, ctx->tx.hp, ctx->tx.md, qc->version, + if (!quic_tls_derive_keys(ctx->tx.aead, ctx->tx.hp, ctx->tx.md, ver, tx_ctx->key, tx_ctx->keylen, tx_ctx->iv, tx_ctx->ivlen, tx_ctx->hp_key, sizeof tx_ctx->hp_key, diff --git a/include/haproxy/quic_tp-t.h b/include/haproxy/quic_tp-t.h index 47301db48c..5180dfcd83 100644 --- a/include/haproxy/quic_tp-t.h +++ b/include/haproxy/quic_tp-t.h @@ -26,6 +26,12 @@ struct tp_preferred_address { uint8_t stateless_reset_token[QUIC_STATELESS_RESET_TOKEN_LEN]; }; +struct tp_version_information { + uint32_t choosen; + const uint32_t *others; + const struct quic_version *negotiated_version; +}; + /* Default values for the absent transport parameters */ #define QUIC_TP_DFLT_MAX_UDP_PAYLOAD_SIZE 65527 /* bytes */ #define QUIC_TP_DFLT_ACK_DELAY_COMPONENT 3 /* milliseconds */ @@ -39,23 +45,24 @@ struct tp_preferred_address { #define QUIC_TP_DFLT_BACK_MAX_IDLE_TIMEOUT 30000 /* milliseconds */ /* Types of QUIC transport parameters */ -#define QUIC_TP_ORIGINAL_DESTINATION_CONNECTION_ID 0 -#define QUIC_TP_MAX_IDLE_TIMEOUT 1 -#define QUIC_TP_STATELESS_RESET_TOKEN 2 -#define QUIC_TP_MAX_UDP_PAYLOAD_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 -#define QUIC_TP_RETRY_SOURCE_CONNECTION_ID 16 +#define QUIC_TP_ORIGINAL_DESTINATION_CONNECTION_ID 0x00 +#define QUIC_TP_MAX_IDLE_TIMEOUT 0x01 +#define QUIC_TP_STATELESS_RESET_TOKEN 0x02 +#define QUIC_TP_MAX_UDP_PAYLOAD_SIZE 0x03 +#define QUIC_TP_INITIAL_MAX_DATA 0x04 +#define QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL 0x05 +#define QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE 0x06 +#define QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI 0x07 +#define QUIC_TP_INITIAL_MAX_STREAMS_BIDI 0x08 +#define QUIC_TP_INITIAL_MAX_STREAMS_UNI 0x09 +#define QUIC_TP_ACK_DELAY_EXPONENT 0x0a +#define QUIC_TP_MAX_ACK_DELAY 0x0b +#define QUIC_TP_DISABLE_ACTIVE_MIGRATION 0x0c +#define QUIC_TP_PREFERRED_ADDRESS 0x0d +#define QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT 0x0e +#define QUIC_TP_INITIAL_SOURCE_CONNECTION_ID 0x0f +#define QUIC_TP_RETRY_SOURCE_CONNECTION_ID 0x10 +#define QUIC_TP_DRAFT_VERSION_INFORMATION 0xff73db /* * These defines are not for transport parameter type, but the maximum accepted value for @@ -103,6 +110,7 @@ struct quic_transport_params { /* MUST be present both for servers and clients. */ struct tp_cid initial_source_connection_id; struct tp_preferred_address preferred_address; /* Forbidden for clients */ + struct tp_version_information version_information; }; #endif /* USE_QUIC */ diff --git a/include/haproxy/quic_tp.h b/include/haproxy/quic_tp.h index 5343e25762..b96c9f9c70 100644 --- a/include/haproxy/quic_tp.h +++ b/include/haproxy/quic_tp.h @@ -13,6 +13,7 @@ void quic_transport_params_init(struct quic_transport_params *p, int server); int quic_transport_params_encode(unsigned char *buf, const unsigned char *end, struct quic_transport_params *p, + const struct quic_version *choosen_version, int server); int quic_transport_params_store(struct quic_conn *conn, int server, diff --git a/include/haproxy/xprt_quic-t.h b/include/haproxy/xprt_quic-t.h index d472f62d5b..af16656c4e 100644 --- a/include/haproxy/xprt_quic-t.h +++ b/include/haproxy/xprt_quic-t.h @@ -286,6 +286,10 @@ struct quic_version { const unsigned char *retry_tag_nonce; }; +extern const struct quic_version quic_versions[]; +extern const size_t quic_versions_nb; +extern const struct quic_version *preferred_version; + /* QUIC connection id data. * * This struct is used by ebmb_node structs as last member of flexible arrays. @@ -625,7 +629,10 @@ enum qc_mux_state { #define QUIC_FL_CONN_DRAINING (1U << 30) #define QUIC_FL_CONN_IMMEDIATE_CLOSE (1U << 31) struct quic_conn { - const struct quic_version *version; + const struct quic_version *original_version; + const struct quic_version *negotiated_version; + /* Negotiated version Initial TLS context */ + struct quic_tls_ctx negotiated_ictx; /* QUIC transport parameters TLS extension */ int tps_tls_ext; /* Thread ID this connection is attached to */ diff --git a/include/haproxy/xprt_quic.h b/include/haproxy/xprt_quic.h index bbfeb04be1..cf9e4ee7ad 100644 --- a/include/haproxy/xprt_quic.h +++ b/include/haproxy/xprt_quic.h @@ -50,6 +50,7 @@ extern struct pool_head *pool_head_quic_connection_id; +int qc_conn_finalize(struct quic_conn *qc, int server); int ssl_quic_initial_ctx(struct bind_conf *bind_conf); /* Return the long packet type matching with version and */ diff --git a/src/quic_tp.c b/src/quic_tp.c index 69c654114a..f7f8d7ddd6 100644 --- a/src/quic_tp.c +++ b/src/quic_tp.c @@ -155,6 +155,66 @@ static int quic_transport_param_dec_pref_addr(struct tp_preferred_address *addr, return *buf == end; } +/* Decode into version information received transport parameters from <*buf> + * buffer. must be set to 1 for QUIC clients which receive server + * transport parameters, and 0 for QUIC servers which receive client transport + * parameters. + * Also set the QUIC negotiated version into . + * Return 1 if succeeded, 0 if not. + */ +static int quic_transport_param_dec_version_info(struct tp_version_information *tp, + const unsigned char **buf, + const unsigned char *end, int server) +{ + size_t tp_len = end - *buf; + const uint32_t *ver; + + /* must be a multiple of sizeof(uint32_t) */ + if (tp_len < sizeof tp->choosen || (tp_len & 0x3)) + return 0; + + tp->choosen = ntohl(*(uint32_t *)*buf); + /* Must not be null */ + if (!tp->choosen) + return 0; + + *buf += sizeof tp->choosen; + tp->others = (const uint32_t *)*buf; + + /* Others versions must not be null */ + for (ver = tp->others; ver < (const uint32_t *)end; ver++) { + if (!*ver) + return 0; + } + + if (server) + /* TODO: not supported */ + return 0; + + for (ver = tp->others; ver < (const uint32_t *)end; ver++) { + if (!tp->negotiated_version) { + int i; + + for (i = 0; i < quic_versions_nb; i++) { + if (ntohl(*ver) == quic_versions[i].num) { + tp->negotiated_version = &quic_versions[i]; + break; + } + } + } + + if (preferred_version && ntohl(*ver) == preferred_version->num) { + tp->negotiated_version = preferred_version; + goto out; + } + } + + out: + *buf = end; + + return 1; +} + /* 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 @@ -253,6 +313,11 @@ static int quic_transport_param_decode(struct quic_transport_params *p, if (!quic_dec_int(&p->active_connection_id_limit, buf, end)) return 0; break; + case QUIC_TP_DRAFT_VERSION_INFORMATION: + if (!quic_transport_param_dec_version_info(&p->version_information, + buf, *buf + len, server)) + return 0; + break; default: *buf += len; }; @@ -349,6 +414,42 @@ static int quic_transport_param_enc_pref_addr(unsigned char **buf, return 1; } +/* Encode version information transport parameters with as choosen + * version. + * Return 1 if succeeded, 0 if not. + */ +static int quic_transport_param_enc_version_info(unsigned char **buf, + const unsigned char *end, + const struct quic_version *choosen_version, + int server) +{ + int i; + uint64_t tp_len; + uint32_t ver; + + tp_len = sizeof choosen_version->num + quic_versions_nb * sizeof(uint32_t); + if (!quic_transport_param_encode_type_len(buf, end, + QUIC_TP_DRAFT_VERSION_INFORMATION, + tp_len)) + return 0; + + if (end - *buf < tp_len) + return 0; + + /* First: choosen version */ + ver = htonl(choosen_version->num); + memcpy(*buf, &ver, sizeof ver); + *buf += sizeof ver; + /* For servers: all supported version, choosen included */ + for (i = 0; i < quic_versions_nb; i++) { + ver = htonl(quic_versions[i].num); + memcpy(*buf, &ver, sizeof ver); + *buf += sizeof ver; + } + + 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). @@ -357,6 +458,7 @@ static int quic_transport_param_enc_pref_addr(unsigned char **buf, int quic_transport_params_encode(unsigned char *buf, const unsigned char *end, struct quic_transport_params *p, + const struct quic_version *choosen_version, int server) { unsigned char *head; @@ -462,6 +564,9 @@ int quic_transport_params_encode(unsigned char *buf, p->active_connection_id_limit)) return 0; + if (!quic_transport_param_enc_version_info(&pos, end, choosen_version, server)) + return 0; + return pos - head; } @@ -515,7 +620,6 @@ int quic_transport_params_store(struct quic_conn *qc, int server, const unsigned char *end) { struct quic_transport_params *tx_params = &qc->tx.params; - struct quic_transport_params *rx_params = &qc->rx.params; /* initialize peer TPs to RFC default value */ quic_dflt_transport_params_cpy(tx_params); @@ -523,18 +627,6 @@ int quic_transport_params_store(struct quic_conn *qc, int server, if (!quic_transport_params_decode(tx_params, server, buf, end)) return 0; - if (tx_params->max_ack_delay) - qc->max_ack_delay = tx_params->max_ack_delay; - - if (tx_params->max_idle_timeout && rx_params->max_idle_timeout) - qc->max_idle_timeout = - QUIC_MIN(tx_params->max_idle_timeout, rx_params->max_idle_timeout); - else - qc->max_idle_timeout = - QUIC_MAX(tx_params->max_idle_timeout, rx_params->max_idle_timeout); - - TRACE_PROTO("\nTX(remote) transp. params.", QUIC_EV_TRANSP_PARAMS, qc, tx_params); - return 1; } diff --git a/src/ssl_sock.c b/src/ssl_sock.c index db255fa9e1..01c5bc910f 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -2621,7 +2621,8 @@ int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg) } if (!quic_transport_params_store(qc, 0, extension_data, - extension_data + extension_len)) + extension_data + extension_len) || + !qc_conn_finalize(qc, 0)) goto abort; } #endif /* USE_QUIC */ diff --git a/src/xprt_quic.c b/src/xprt_quic.c index cf88d48774..57f9f53c9c 100644 --- a/src/xprt_quic.c +++ b/src/xprt_quic.c @@ -57,7 +57,7 @@ #include /* list of supported QUIC versions by this implementation */ -static const struct quic_version quic_versions[] = { +const struct quic_version quic_versions[] = { { .num = QUIC_PROTOCOL_VERSION_DRAFT_29, .initial_salt = initial_salt_draft_29, @@ -106,7 +106,9 @@ static const struct quic_version quic_versions[] = { }; /* The total number of supported versions */ -static size_t quic_versions_nb = sizeof quic_versions / sizeof *quic_versions; +const size_t quic_versions_nb = sizeof quic_versions / sizeof *quic_versions; +/* Listener only preferred version */ +const struct quic_version *preferred_version; /* trace source and events */ static void quic_trace(enum trace_level level, uint64_t mask, \ @@ -206,8 +208,9 @@ DECLARE_POOL(pool_head_quic_frame, "quic_frame_pool", sizeof(struct quic_frame)) DECLARE_STATIC_POOL(pool_head_quic_arng, "quic_arng_pool", sizeof(struct quic_arng_node)); static struct quic_tx_packet *qc_build_pkt(unsigned char **pos, const unsigned char *buf_end, - struct quic_enc_level *qel, struct list *frms, - struct quic_conn *qc, size_t dglen, int pkt_type, + struct quic_enc_level *qel, struct quic_tls_ctx *ctx, + struct list *frms, struct quic_conn *qc, + const struct quic_version *ver, size_t dglen, int pkt_type, int padding, int probe, int cc, int *err); static struct task *quic_conn_app_io_cb(struct task *t, void *context, unsigned int state); static void qc_idle_timer_do_rearm(struct quic_conn *qc); @@ -642,6 +645,7 @@ static void quic_trace(enum trace_level level, uint64_t mask, const struct trace if (mask & QUIC_EV_CONN_LPKT) { const struct quic_rx_packet *pkt = a2; const uint64_t *len = a3; + const struct quic_version *ver = a4; if (pkt) { chunk_appendf(&trace_buf, " pkt@%p type=0x%02x %s", @@ -652,6 +656,9 @@ static void quic_trace(enum trace_level level, uint64_t mask, const struct trace if (len) chunk_appendf(&trace_buf, " len=%llu", (ull)*len); + + if (ver) + chunk_appendf(&trace_buf, " ver=0x%08x", ver->num); } if (mask & QUIC_EV_STATELESS_RST) { @@ -744,6 +751,8 @@ static int quic_tls_key_update(struct quic_conn *qc) struct quic_tls_secrets *rx, *tx; struct quic_tls_kp *nxt_rx = &qc->ku.nxt_rx; struct quic_tls_kp *nxt_tx = &qc->ku.nxt_tx; + const struct quic_version *ver = + qc->negotiated_version ? qc->negotiated_version : qc->original_version; tls_ctx = &qc->els[QUIC_TLS_ENC_LEVEL_APP].tls_ctx; rx = &tls_ctx->rx; @@ -752,13 +761,13 @@ static int quic_tls_key_update(struct quic_conn *qc) nxt_tx = &qc->ku.nxt_tx; /* Prepare new RX secrets */ - if (!quic_tls_sec_update(rx->md, qc->version, nxt_rx->secret, nxt_rx->secretlen, + if (!quic_tls_sec_update(rx->md, ver, nxt_rx->secret, nxt_rx->secretlen, rx->secret, rx->secretlen)) { TRACE_DEVEL("New RX secret update failed", QUIC_EV_CONN_RWSEC, qc); return 0; } - if (!quic_tls_derive_keys(rx->aead, NULL, rx->md, qc->version, + if (!quic_tls_derive_keys(rx->aead, NULL, rx->md, ver, nxt_rx->key, nxt_rx->keylen, nxt_rx->iv, nxt_rx->ivlen, NULL, 0, nxt_rx->secret, nxt_rx->secretlen)) { @@ -767,13 +776,13 @@ static int quic_tls_key_update(struct quic_conn *qc) } /* Prepare new TX secrets */ - if (!quic_tls_sec_update(tx->md, qc->version, nxt_tx->secret, nxt_tx->secretlen, + if (!quic_tls_sec_update(tx->md, ver, nxt_tx->secret, nxt_tx->secretlen, tx->secret, tx->secretlen)) { TRACE_DEVEL("New TX secret update failed", QUIC_EV_CONN_RWSEC, qc); return 0; } - if (!quic_tls_derive_keys(tx->aead, NULL, tx->md, qc->version, + if (!quic_tls_derive_keys(tx->aead, NULL, tx->md, ver, nxt_tx->key, nxt_tx->keylen, nxt_tx->iv, nxt_tx->ivlen, NULL, 0, nxt_tx->secret, nxt_tx->secretlen)) { @@ -862,6 +871,8 @@ int ha_quic_set_encryption_secrets(SSL *ssl, enum ssl_encryption_level_t level, struct quic_tls_ctx *tls_ctx = &qc->els[ssl_to_quic_enc_level(level)].tls_ctx; const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl); struct quic_tls_secrets *rx, *tx; + const struct quic_version *ver = + qc->negotiated_version ? qc->negotiated_version : qc->original_version; TRACE_ENTER(QUIC_EV_CONN_RWSEC, qc); BUG_ON(secret_len > QUIC_TLS_SECRET_LEN); @@ -882,7 +893,7 @@ int ha_quic_set_encryption_secrets(SSL *ssl, enum ssl_encryption_level_t level, rx->md = tx->md = tls_md(cipher); rx->hp = tx->hp = tls_hp(cipher); - if (!quic_tls_derive_keys(rx->aead, rx->hp, rx->md, qc->version, rx->key, rx->keylen, + if (!quic_tls_derive_keys(rx->aead, rx->hp, rx->md, ver, rx->key, rx->keylen, rx->iv, rx->ivlen, rx->hp_key, sizeof rx->hp_key, read_secret, secret_len)) { TRACE_DEVEL("RX key derivation failed", QUIC_EV_CONN_RWSEC, qc); @@ -904,7 +915,7 @@ int ha_quic_set_encryption_secrets(SSL *ssl, enum ssl_encryption_level_t level, if (!write_secret) goto out; - if (!quic_tls_derive_keys(tx->aead, tx->hp, tx->md, qc->version, tx->key, tx->keylen, + if (!quic_tls_derive_keys(tx->aead, tx->hp, tx->md, ver, tx->key, tx->keylen, tx->iv, tx->ivlen, tx->hp_key, sizeof tx->hp_key, write_secret, secret_len)) { TRACE_DEVEL("TX key derivation failed", QUIC_EV_CONN_RWSEC, qc); @@ -2851,7 +2862,7 @@ static int qc_prep_app_pkts(struct quic_conn *qc, struct qring *qr, end = pos + qc->path->mtu; } - pkt = qc_build_pkt(&pos, end, qel, frms, qc, 0, 0, + pkt = qc_build_pkt(&pos, end, qel, &qel->tls_ctx, frms, qc, NULL, 0, 0, QUIC_PACKET_TYPE_SHORT, probe, cc, &err); switch (err) { case -2: @@ -2939,6 +2950,8 @@ static int qc_prep_pkts(struct quic_conn *qc, struct qring *qr, while (end_buf - pos >= (int)qc->path->mtu + dg_headlen || prv_pkt) { int err, probe, cc; enum quic_pkt_type pkt_type; + struct quic_tls_ctx *tls_ctx; + const struct quic_version *ver; TRACE_POINT(QUIC_EV_CONN_PHPKTS, qc, qel); probe = 0; @@ -2974,8 +2987,20 @@ static int qc_prep_pkts(struct quic_conn *qc, struct qring *qr, } } - cur_pkt = qc_build_pkt(&pos, end, qel, frms, - qc, dglen, padding, pkt_type, probe, cc, &err); + if (qc->negotiated_version) { + ver = qc->negotiated_version; + if (qel == &qc->els[QUIC_TLS_ENC_LEVEL_INITIAL]) + tls_ctx = &qc->negotiated_ictx; + else + tls_ctx = &qel->tls_ctx; + } + else { + ver = qc->original_version; + tls_ctx = &qel->tls_ctx; + } + + cur_pkt = qc_build_pkt(&pos, end, qel, tls_ctx, frms, + qc, ver, dglen, padding, pkt_type, probe, cc, &err); switch (err) { case -2: goto err; @@ -4173,6 +4198,7 @@ static void quic_conn_release(struct quic_conn *qc) quic_tls_ctx_secs_free(&qc->els[i].tls_ctx); quic_conn_enc_level_uninit(&qc->els[i]); } + quic_tls_ctx_secs_free(&qc->negotiated_ictx); app_tls_ctx = &qc->els[QUIC_TLS_ENC_LEVEL_APP].tls_ctx; pool_free(pool_head_quic_tls_secret, app_tls_ctx->rx.secret); @@ -4401,8 +4427,8 @@ static struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4, qc->els[i].pktns = &qc->pktns[quic_tls_pktns(i)]; } - qc->version = qv; - qc->tps_tls_ext = (qc->version->num & 0xff000000) == 0xff000000 ? + qc->original_version = qv; + qc->tps_tls_ext = (qc->original_version->num & 0xff000000) == 0xff000000 ? TLS_EXTENSION_QUIC_TRANSPORT_PARAMETERS_DRAFT: TLS_EXTENSION_QUIC_TRANSPORT_PARAMETERS; /* TX part. */ @@ -4444,19 +4470,13 @@ static struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4, odcid->data, odcid->len, token)) goto err; - qc->enc_params_len = - quic_transport_params_encode(qc->enc_params, - qc->enc_params + sizeof qc->enc_params, - &qc->rx.params, 1); - if (!qc->enc_params_len) - goto err; - if (qc_conn_alloc_ssl_ctx(qc) || !quic_conn_init_timer(qc) || !quic_conn_init_idle_timer_task(qc)) goto err; - if (!qc_new_isecs(qc, dcid->data, dcid->len, 1)) + if (!qc_new_isecs(qc, &qc->els[QUIC_TLS_ENC_LEVEL_INITIAL].tls_ctx, + qc->original_version, dcid->data, dcid->len, 1)) goto err; TRACE_LEAVE(QUIC_EV_CONN_INIT, qc); @@ -4963,8 +4983,8 @@ static int quic_generate_retry_token(unsigned char *buf, size_t len, * of client source connection ID. * Return 1 if succeeded, 0 if not. */ -static int quic_retry_token_check(unsigned char *token, size_t tokenlen, - const uint32_t version, +static int quic_retry_token_check(const unsigned char *token, size_t tokenlen, + const struct quic_version *qv, struct quic_cid *odcid, const struct quic_cid *dcid, struct quic_conn *qc, @@ -4974,7 +4994,7 @@ static int quic_retry_token_check(unsigned char *token, size_t tokenlen, unsigned char aad[sizeof(uint32_t) + sizeof(in_port_t) + sizeof(struct in6_addr) + QUIC_HAP_CID_LEN]; size_t aadlen; - unsigned char *salt; + const unsigned char *salt; unsigned char key[QUIC_TLS_KEY_LEN]; unsigned char iv[QUIC_TLS_IV_LEN]; const unsigned char *sec = (const unsigned char *)global.cluster_secret; @@ -4985,7 +5005,7 @@ static int quic_retry_token_check(unsigned char *token, size_t tokenlen, if (sizeof buf < tokenlen) return 0; - aadlen = quic_generate_retry_token_aad(aad, version, dcid, addr); + aadlen = quic_generate_retry_token_aad(aad, qv->num, dcid, addr); salt = token + tokenlen - QUIC_RETRY_TOKEN_SALTLEN; if (!quic_tls_derive_retry_token_secret(EVP_sha256(), key, sizeof key, iv, sizeof iv, salt, QUIC_RETRY_TOKEN_SALTLEN, sec, seclen)) { @@ -5154,8 +5174,7 @@ static int qc_ssl_sess_init(struct quic_conn *qc, SSL_CTX *ssl_ctx, SSL **ssl, } if (!SSL_set_quic_method(*ssl, &ha_quic_method) || - !SSL_set_ex_data(*ssl, ssl_qc_app_data_index, qc) || - !SSL_set_quic_transport_params(*ssl, qc->enc_params, qc->enc_params_len)) { + !SSL_set_ex_data(*ssl, ssl_qc_app_data_index, qc)) { SSL_free(*ssl); *ssl = NULL; if (!retry--) @@ -5172,6 +5191,61 @@ static int qc_ssl_sess_init(struct quic_conn *qc, SSL_CTX *ssl_ctx, SSL **ssl, return -1; } +/* Finalize QUIC connection: + * - initialize the Initial QUIC TLS context for negotiated version, + * - derive the secrets for this context, + * - encode the transport parameters to be sent, + * - set them into the TLS stack, + * - initialize ->max_ack_delay and max_idle_timeout, + * + * MUST be called after having received the remote transport parameters. + * Return 1 if succeeded, 0 if not. + */ +int qc_conn_finalize(struct quic_conn *qc, int server) +{ + struct quic_transport_params *tx_tp = &qc->tx.params; + struct quic_transport_params *rx_tp = &qc->rx.params; + const struct quic_version *ver; + + if (tx_tp->version_information.negotiated_version && + tx_tp->version_information.negotiated_version != qc->original_version) { + qc->negotiated_version = + qc->tx.params.version_information.negotiated_version; + if (!qc_new_isecs(qc, &qc->negotiated_ictx, qc->negotiated_version, + qc->odcid.data, qc->odcid.len, !server)) + return 0; + + ver = qc->negotiated_version; + } + else { + ver = qc->original_version; + } + + qc->enc_params_len = + quic_transport_params_encode(qc->enc_params, + qc->enc_params + sizeof qc->enc_params, + &qc->rx.params, ver, 1); + if (!qc->enc_params_len) + return 0; + + if (!SSL_set_quic_transport_params(qc->xprt_ctx->ssl, qc->enc_params, qc->enc_params_len)) + return 0; + + if (tx_tp->max_ack_delay) + qc->max_ack_delay = tx_tp->max_ack_delay; + + if (tx_tp->max_idle_timeout && rx_tp->max_idle_timeout) + qc->max_idle_timeout = + QUIC_MIN(tx_tp->max_idle_timeout, rx_tp->max_idle_timeout); + else + qc->max_idle_timeout = + QUIC_MAX(tx_tp->max_idle_timeout, rx_tp->max_idle_timeout); + + TRACE_PROTO("\nTX(remote) transp. params.", QUIC_EV_TRANSP_PARAMS, qc, tx_tp); + + return 1; +} + /* Allocate the ssl_sock_ctx from connection . This creates the tasklet * used to process received packets. The allocated context is stored in * . @@ -5269,6 +5343,7 @@ static void qc_lstnr_pkt_rcv(unsigned char *buf, const unsigned char *end, size_t b_cspace; struct quic_enc_level *qel; uint32_t version; + const struct quic_version *qv = NULL; beg = buf; qc = NULL; @@ -5312,7 +5387,7 @@ static void qc_lstnr_pkt_rcv(unsigned char *buf, const unsigned char *end, if (long_header) { uint64_t len; struct quic_cid odcid; - const struct quic_version *qv; + int check_token = 0; if (!quic_packet_read_long_header(&buf, end, pkt)) { TRACE_PROTO("Packet dropped", QUIC_EV_CONN_LPKT); @@ -5350,11 +5425,11 @@ static void qc_lstnr_pkt_rcv(unsigned char *buf, const unsigned char *end, if (!qv) { /* unsupported version, send Negotiation packet */ if (send_version_negotiation(l->rx.fd, &dgram->saddr, pkt)) { - TRACE_PROTO("Error on Version Negotiation sending", QUIC_EV_CONN_LPKT); + TRACE_PROTO("VN packet not sent", QUIC_EV_CONN_LPKT); goto err; } - TRACE_PROTO("Unsupported QUIC version, send Version Negotiation packet", QUIC_EV_CONN_LPKT); + TRACE_PROTO("VN packet sent", QUIC_EV_CONN_LPKT); goto err; } @@ -5366,7 +5441,8 @@ static void qc_lstnr_pkt_rcv(unsigned char *buf, const unsigned char *end, if (!quic_dec_int(&token_len, (const unsigned char **)&buf, end) || end - buf < token_len) { - TRACE_PROTO("Packet dropped", QUIC_EV_CONN_LPKT); + TRACE_PROTO("Packet dropped", + QUIC_EV_CONN_LPKT, NULL, NULL, NULL, qv); goto drop; } @@ -5376,9 +5452,11 @@ static void qc_lstnr_pkt_rcv(unsigned char *buf, const unsigned char *end, if (global.cluster_secret) { if (!token_len) { if (l->bind_conf->options & BC_O_QUIC_FORCE_RETRY) { - TRACE_PROTO("Initial without token, sending retry", QUIC_EV_CONN_LPKT); + TRACE_PROTO("Initial without token, sending retry", + QUIC_EV_CONN_LPKT, NULL, NULL, NULL, qv); if (send_retry(l->rx.fd, &dgram->saddr, pkt, qv)) { - TRACE_PROTO("Error during Retry generation", QUIC_EV_CONN_LPKT); + TRACE_PROTO("Error during Retry generation", + QUIC_EV_CONN_LPKT, NULL, NULL, NULL, qv); goto err; } @@ -5387,24 +5465,7 @@ static void qc_lstnr_pkt_rcv(unsigned char *buf, const unsigned char *end, } } else { - if (*buf == QUIC_TOKEN_FMT_RETRY) { - if (!quic_retry_token_check(buf, token_len, version, &odcid, - &pkt->scid, qc, &dgram->saddr)) { - HA_ATOMIC_INC(&prx_counters->retry_error); - TRACE_PROTO("Wrong retry token", QUIC_EV_CONN_LPKT); - /* TODO: RFC 9000 8.1.2 A server SHOULD immediately close the connection - * with an INVALID_TOKEN error. - */ - goto drop; - } - - HA_ATOMIC_INC(&prx_counters->retry_validated); - } - else { - /* TODO: New token check */ - TRACE_PROTO("Packet dropped", QUIC_EV_CONN_LPKT); - goto drop; - } + check_token = 1; } } @@ -5414,14 +5475,16 @@ static void qc_lstnr_pkt_rcv(unsigned char *buf, const unsigned char *end, } else if (pkt->type != QUIC_PACKET_TYPE_0RTT) { if (pkt->dcid.len != QUIC_HAP_CID_LEN) { - TRACE_PROTO("Packet dropped", QUIC_EV_CONN_LPKT); + TRACE_PROTO("Packet dropped", + QUIC_EV_CONN_LPKT, NULL, NULL, NULL, qv); goto drop; } } if (!quic_dec_int(&len, (const unsigned char **)&buf, end) || end - buf < len) { - TRACE_PROTO("Packet dropped", QUIC_EV_CONN_LPKT); + TRACE_PROTO("Packet dropped", + QUIC_EV_CONN_LPKT, NULL, NULL, NULL, qv); goto drop; } @@ -5431,20 +5494,44 @@ static void qc_lstnr_pkt_rcv(unsigned char *buf, const unsigned char *end, goto drop_no_conn; qc = retrieve_qc_conn_from_cid(pkt, l, &dgram->saddr); + if (check_token && pkt->token) { + if (*pkt->token == QUIC_TOKEN_FMT_RETRY) { + const struct quic_version *ver = qc ? qc->original_version : qv; + if (!quic_retry_token_check(pkt->token, pkt->token_len, ver, &odcid, + &pkt->scid, qc, &dgram->saddr)) { + HA_ATOMIC_INC(&prx_counters->retry_error); + TRACE_PROTO("Wrong retry token", + QUIC_EV_CONN_LPKT, qc, NULL, NULL, qv); + /* TODO: RFC 9000 8.1.2 A server SHOULD immediately close the connection + * with an INVALID_TOKEN error. + */ + goto drop; + } + + HA_ATOMIC_INC(&prx_counters->retry_validated); + } + else { + /* TODO: New token check */ + TRACE_PROTO("Packet dropped", QUIC_EV_CONN_LPKT, qc, NULL, NULL, qv); + goto drop; + } + } if (!qc) { int ipv4; struct ebmb_node *n = NULL; if (pkt->type != QUIC_PACKET_TYPE_INITIAL) { - TRACE_PROTO("Non Initial packet", QUIC_EV_CONN_LPKT); + TRACE_PROTO("Non Initial packet", QUIC_EV_CONN_LPKT, NULL, NULL, NULL, qv); goto drop; } if (global.cluster_secret && !pkt->token_len && !(l->bind_conf->options & BC_O_QUIC_FORCE_RETRY) && HA_ATOMIC_LOAD(&prx_counters->half_open_conn) >= global.tune.quic_retry_threshold) { - TRACE_PROTO("Initial without token, sending retry", QUIC_EV_CONN_LPKT); + TRACE_PROTO("Initial without token, sending retry", + QUIC_EV_CONN_LPKT, NULL, NULL, NULL, qv); if (send_retry(l->rx.fd, &dgram->saddr, pkt, qv)) { - TRACE_PROTO("Error during Retry generation", QUIC_EV_CONN_LPKT); + TRACE_PROTO("Error during Retry generation", + QUIC_EV_CONN_LPKT, NULL, NULL, NULL, qv); goto err; } @@ -5459,7 +5546,8 @@ static void qc_lstnr_pkt_rcv(unsigned char *buf, const unsigned char *end, * value. This Destination Connection ID MUST be at least 8 bytes in length. */ if (pkt->dcid.len < QUIC_ODCID_MINLEN) { - TRACE_PROTO("dropped packet", QUIC_EV_CONN_LPKT); + TRACE_PROTO("dropped packet", + QUIC_EV_CONN_LPKT, NULL, NULL, NULL, qv); goto drop; } @@ -5543,7 +5631,8 @@ static void qc_lstnr_pkt_rcv(unsigned char *buf, const unsigned char *end, } /* Skip the entire datagram */ pkt->len = end - beg; - TRACE_PROTO("Closing state connection", QUIC_EV_CONN_LPKT, pkt->qc); + TRACE_PROTO("Closing state connection", + QUIC_EV_CONN_LPKT, pkt->qc, NULL, NULL, qv); goto drop; } @@ -5556,7 +5645,7 @@ static void qc_lstnr_pkt_rcv(unsigned char *buf, const unsigned char *end, if (first_pkt && !quic_peer_validated_addr(qc) && qc->flags & QUIC_FL_CONN_ANTI_AMPLIFICATION_REACHED) { TRACE_PROTO("PTO timer must be armed after anti-amplication was reached", - QUIC_EV_CONN_LPKT, qc); + QUIC_EV_CONN_LPKT, qc, NULL, NULL, qv); /* Reset the anti-amplification bit. It will be set again * when sending the next packet if reached again. */ @@ -5568,7 +5657,8 @@ static void qc_lstnr_pkt_rcv(unsigned char *buf, const unsigned char *end, dgram->qc = qc; if (qc->err_code) { - TRACE_PROTO("Connection error", QUIC_EV_CONN_LPKT, qc); + TRACE_PROTO("Connection error", + QUIC_EV_CONN_LPKT, qc, NULL, NULL, qv); goto out; } @@ -5578,7 +5668,8 @@ static void qc_lstnr_pkt_rcv(unsigned char *buf, const unsigned char *end, if (b_cspace < pkt->len) { /* Do not consume buf if space not at the end. */ if (b_tail(&qc->rx.buf) + b_cspace < b_wrap(&qc->rx.buf)) { - TRACE_PROTO("Packet dropped", QUIC_EV_CONN_LPKT, qc); + TRACE_PROTO("Packet dropped", + QUIC_EV_CONN_LPKT, qc, NULL, NULL, qv); goto err; } @@ -5589,18 +5680,19 @@ static void qc_lstnr_pkt_rcv(unsigned char *buf, const unsigned char *end, } b_add(&qc->rx.buf, b_cspace); if (b_contig_space(&qc->rx.buf) < pkt->len) { - TRACE_PROTO("Too big packet", QUIC_EV_CONN_LPKT, qc, pkt, &pkt->len); + TRACE_PROTO("Too big packet", + QUIC_EV_CONN_LPKT, qc, pkt, &pkt->len, qv); qc_list_all_rx_pkts(qc); goto drop; } } if (!qc_try_rm_hp(qc, pkt, payload, beg, end, &qel)) { - TRACE_PROTO("Packet dropped", QUIC_EV_CONN_LPKT, qc); + TRACE_PROTO("Packet dropped", QUIC_EV_CONN_LPKT, qc, NULL, NULL, qv); goto drop; } - TRACE_PROTO("New packet", QUIC_EV_CONN_LPKT, qc, pkt); + TRACE_PROTO("New packet", QUIC_EV_CONN_LPKT, qc, pkt, NULL, qv); if (pkt->aad_len) qc_pkt_insert(pkt, qel); out: @@ -5617,7 +5709,7 @@ static void qc_lstnr_pkt_rcv(unsigned char *buf, const unsigned char *end, drop_no_conn: if (drop_no_conn) HA_ATOMIC_INC(&prx_counters->dropped_pkt); - TRACE_LEAVE(QUIC_EV_CONN_LPKT, qc ? qc : NULL, pkt); + TRACE_LEAVE(QUIC_EV_CONN_LPKT, qc ? qc : NULL, pkt, NULL, qv); return; @@ -5636,24 +5728,25 @@ static void qc_lstnr_pkt_rcv(unsigned char *buf, const unsigned char *end, if (!pkt->len) pkt->len = end - beg; TRACE_DEVEL("Leaving in error", QUIC_EV_CONN_LPKT, - qc ? qc : NULL, pkt); + qc ? qc : NULL, pkt, NULL, qv); } /* This function builds into buffer a QUIC long packet header. * Return 1 if enough room to build this header, 0 if not. */ static int quic_build_packet_long_header(unsigned char **buf, const unsigned char *end, - int type, size_t pn_len, struct quic_conn *conn) + int type, size_t pn_len, + struct quic_conn *conn, const struct quic_version *ver) { - if (end - *buf < sizeof conn->version + conn->dcid.len + conn->scid.len + 3) + if (end - *buf < sizeof ver->num + conn->dcid.len + conn->scid.len + 3) return 0; - type = quic_pkt_type(type, conn->version->num); + type = quic_pkt_type(type, ver->num); /* #0 byte flags */ *(*buf)++ = QUIC_PACKET_FIXED_BIT | QUIC_PACKET_LONG_HEADER_BIT | (type << QUIC_PACKET_TYPE_SHIFT) | (pn_len - 1); /* Version */ - quic_write_uint32(buf, end, conn->version->num); + quic_write_uint32(buf, end, ver->num); *(*buf)++ = conn->dcid.len; /* Destination connection ID */ if (conn->dcid.len) { @@ -6021,7 +6114,7 @@ static int qc_do_build_pkt(unsigned char *pos, const unsigned char *end, int64_t pn, size_t *pn_len, unsigned char **buf_pn, int padding, int cc, int probe, struct quic_enc_level *qel, struct quic_conn *qc, - struct list *frms) + const struct quic_version *ver, struct list *frms) { unsigned char *beg; size_t len, len_sz, len_frms, padding_len; @@ -6064,7 +6157,7 @@ static int qc_do_build_pkt(unsigned char *pos, const unsigned char *end, if ((pkt->type == QUIC_PACKET_TYPE_SHORT && !quic_build_packet_short_header(&pos, end, *pn_len, qc, qel->tls_ctx.flags)) || (pkt->type != QUIC_PACKET_TYPE_SHORT && - !quic_build_packet_long_header(&pos, end, pkt->type, *pn_len, qc))) + !quic_build_packet_long_header(&pos, end, pkt->type, *pn_len, qc, ver))) goto no_room; /* XXX FIXME XXX Encode the token length (0) for an Initial packet. */ @@ -6273,8 +6366,10 @@ static inline void quic_tx_packet_init(struct quic_tx_packet *pkt, int type) */ static struct quic_tx_packet *qc_build_pkt(unsigned char **pos, const unsigned char *buf_end, - struct quic_enc_level *qel, struct list *frms, - struct quic_conn *qc, size_t dglen, int padding, + struct quic_enc_level *qel, + struct quic_tls_ctx *tls_ctx, struct list *frms, + struct quic_conn *qc, const struct quic_version *ver, + size_t dglen, int padding, int pkt_type, int probe, int cc, int *err) { /* The pointer to the packet number field. */ @@ -6282,7 +6377,6 @@ static struct quic_tx_packet *qc_build_pkt(unsigned char **pos, unsigned char *beg, *end, *payload; int64_t pn; size_t pn_len, payload_len, aad_len; - struct quic_tls_ctx *tls_ctx; struct quic_tx_packet *pkt; TRACE_ENTER(QUIC_EV_CONN_HPKT, qc, NULL, qel); @@ -6301,7 +6395,7 @@ static struct quic_tx_packet *qc_build_pkt(unsigned char **pos, pn = qel->pktns->tx.next_pn + 1; if (!qc_do_build_pkt(*pos, buf_end, dglen, pkt, pn, &pn_len, &buf_pn, - padding, cc, probe, qel, qc, frms)) { + padding, cc, probe, qel, qc, ver, frms)) { *err = -1; goto err; } @@ -6311,7 +6405,6 @@ static struct quic_tx_packet *qc_build_pkt(unsigned char **pos, payload_len = end - payload; aad_len = payload - beg; - tls_ctx = &qel->tls_ctx; if (!quic_packet_encrypt(payload, payload_len, beg, aad_len, pn, tls_ctx, qc)) { *err = -2; goto err;