*/
uint64_t hash64;
+ /* QUIC client only retry token received from servers RETRY packet */
+ unsigned char *retry_token;
+ size_t retry_token_len;
+
/* Initial encryption level */
struct quic_enc_level *iel;
/* 0-RTT encryption level */
#include <haproxy/quic_rx-t.h>
#include <haproxy/quic_sock-t.h>
+extern struct pool_head *pool_head_quic_retry_token;
+
struct listener;
int quic_generate_retry_token(unsigned char *token, size_t len,
struct listener *l,
struct quic_conn *qc,
struct quic_cid *odcid);
+int quic_retry_packet_check(struct quic_conn *qc, struct quic_rx_packet *pkt,
+ const unsigned char *beg, const unsigned char *end,
+ const unsigned char *pos, size_t *retry_token_len);
#endif /* USE_QUIC */
#endif /* _HAPROXY_QUIC_RETRY_H */
const unsigned char *key, const unsigned char *iv);
int quic_tls_generate_retry_integrity_tag(unsigned char *odcid, unsigned char odcid_len,
- unsigned char *buf, size_t len,
+ const unsigned char *buf, size_t len,
+ unsigned char *tag,
const struct quic_version *qv);
int quic_tls_derive_keys(const QUIC_AEAD *aead, const EVP_CIPHER *hp,
return 1;
}
-static inline void quic_pktns_tx_pkts_release(struct quic_pktns *pktns, struct quic_conn *qc)
+static inline void quic_pktns_tx_pkts_release(struct quic_pktns *pktns,
+ struct quic_conn *qc, int resend)
{
struct eb64_node *node;
qc_frm_unref(frm, qc);
LIST_DEL_INIT(&frm->list);
quic_tx_packet_refdec(frm->pkt);
- qc_frm_free(qc, &frm);
+ if (!resend)
+ qc_frm_free(qc, &frm);
+ else
+ LIST_APPEND(&pktns->tx.frms, &frm->list);
+
}
eb64_delete(&pkt->pn_node);
quic_tx_packet_refdec(pkt);
* connection.
* Note that all the non acknowledged TX packets and their frames are freed.
* Always succeeds.
+ * <resend> boolean must be 1 to resend the frames which are in flight.
+ * This is only used to resend the Initial packet frames upon a RETRY
+ * packet receipt (backend only option).
*/
static inline void quic_pktns_discard(struct quic_pktns *pktns,
- struct quic_conn *qc)
+ struct quic_conn *qc, int resend)
{
TRACE_ENTER(QUIC_EV_CONN_PHPKTS, qc);
pktns->tx.loss_time = TICK_ETERNITY;
pktns->tx.pto_probe = 0;
pktns->tx.in_flight = 0;
- quic_pktns_tx_pkts_release(pktns, qc);
+ quic_pktns_tx_pkts_release(pktns, qc, resend);
TRACE_LEAVE(QUIC_EV_CONN_PHPKTS, qc);
}
#include <haproxy/quic_enc.h>
#include <haproxy/quic_loss.h>
#include <haproxy/quic_rx.h>
+#include <haproxy/quic_retry.h>
#include <haproxy/quic_ssl.h>
#include <haproxy/quic_sock.h>
#include <haproxy/quic_stats.h>
if (discard_hpktns) {
/* Discard the Handshake packet number space. */
TRACE_PROTO("discarding Handshake pktns", QUIC_EV_CONN_PHPKTS, qc);
- quic_pktns_discard(qc->hel->pktns, qc);
+ quic_pktns_discard(qc->hel->pktns, qc, 0);
qc_set_timer(qc);
qc_el_rx_pkts_del(qc->hel);
qc_release_pktns_frms(qc, qc->hel->pktns);
qc->hpktns && qc->hpktns->tx.in_flight > 0) {
/* Discard the Initial packet number space. */
TRACE_PROTO("discarding Initial pktns", QUIC_EV_CONN_PRSHPKT, qc);
- quic_pktns_discard(qc->ipktns, qc);
+ quic_pktns_discard(qc->ipktns, qc, 0);
qc_set_timer(qc);
qc_el_rx_pkts_del(qc->iel);
qc_release_pktns_frms(qc, qc->ipktns);
#ifdef HAVE_OPENSSL_QUIC
qc->prot_level = OSSL_RECORD_PROTECTION_LEVEL_NONE;
#endif
+ qc->retry_token = NULL;
+ qc->retry_token_len = 0;
/* Encryption levels */
qc->iel = qc->eel = qc->hel = qc->ael = NULL;
LIST_INIT(&qc->qel_list);
qc->flags = QUIC_FL_CONN_PEER_VALIDATED_ADDR;
qc->state = QUIC_HS_ST_CLIENT_INITIAL;
- memset(&qc->odcid, 0, sizeof qc->odcid);
- qc->odcid.len = 0;
/* This is the original connection ID from the peer server
* point of view.
*/
qc->dcid.len = sizeof(qc->dcid.data);
+ memcpy(&qc->odcid, qc->dcid.data, sizeof(qc->dcid.data));
+ qc->odcid.len = qc->dcid.len;
+
conn_cid = new_quic_cid(qc->cids, qc, NULL, NULL);
if (!conn_cid)
goto err;
pool_free(pool_head_quic_tls_secret, actx->tx.secret);
}
+ pool_free(pool_head_quic_retry_token, qc->retry_token);
+ qc->retry_token = NULL;
+ qc->retry_token_len = 0;
qc_enc_level_free(qc, &qc->iel);
qc_enc_level_free(qc, &qc->eel);
qc_enc_level_free(qc, &qc->hel);
#include <string.h>
#include <haproxy/clock.h>
+#include <haproxy/errors.h>
#include <haproxy/global.h>
#include <haproxy/quic_retry.h>
#include <haproxy/quic_tls.h>
/* Salt length used to derive retry token secret */
#define QUIC_RETRY_TOKEN_SALTLEN 16 /* bytes */
+#define QUIC_RETRY_TOKEN_MAXLEN 256 /* bytes */
+
+struct pool_head *pool_head_quic_retry_token;
/* Copy <saddr> socket address data into <buf> buffer.
* This is the responsibility of the caller to check the output buffer is big
goto leave;
}
+/* QUIC client only function.
+ * Check the integrity tag of <pkt> retry packet received on <qc> connection
+ * with <beg> and <end> as packet payload delimiters. <pos> is the retry token position.
+ * Return 1 if succeeded, 0 if not.
+ */
+int quic_retry_packet_check(struct quic_conn *qc, struct quic_rx_packet *pkt,
+ const unsigned char *beg, const unsigned char *end,
+ const unsigned char *pos, size_t *retry_token_len)
+{
+ int ret = 0;
+ unsigned char tag[QUIC_TLS_TAG_LEN];
+ ssize_t toklen;
+
+ TRACE_ENTER(QUIC_EV_CONN_SPKT, qc);
+
+ if (!pkt->version) {
+ TRACE_PROTO("retry packet without version", QUIC_EV_CONN_SPKT);
+ goto err;
+ }
+
+ if (end - beg <= QUIC_LONG_PACKET_MINLEN +
+ pkt->scid.len + pkt->dcid.len + QUIC_TLS_TAG_LEN ||
+ end - pos <= QUIC_TLS_TAG_LEN) {
+ TRACE_PROTO("Too short retry packet", QUIC_EV_CONN_SPKT);
+ goto err;
+ }
+
+ if (!quic_tls_generate_retry_integrity_tag(qc->odcid.data, qc->odcid.len,
+ beg, end - beg - QUIC_TLS_TAG_LEN,
+ tag, pkt->version)) {
+ TRACE_PROTO("retry integrity tag faild", QUIC_EV_CONN_SPKT, qc);
+ goto err;
+ }
+
+ if (memcmp(tag, end - QUIC_TLS_TAG_LEN, QUIC_TLS_TAG_LEN) != 0) {
+ TRACE_PROTO("retry integrity tag mismatch", QUIC_EV_CONN_SPKT, qc);
+ goto err;
+ }
+
+ toklen = end - pos - QUIC_TLS_TAG_LEN;
+ if (toklen <= 0 || toklen > QUIC_RETRY_TOKEN_MAXLEN) {
+ TRACE_PROTO("wrong retry token size", QUIC_EV_CONN_SPKT, qc);
+ goto err;
+ }
+
+ *retry_token_len = (size_t)toklen;
+ ret = 1;
+ leave:
+ TRACE_LEAVE(QUIC_EV_CONN_SPKT, qc);
+ return ret;
+ err:
+ TRACE_DEVEL("leaving on error", QUIC_EV_CONN_SPKT, qc);
+ goto leave;
+}
+
+static int create_quic_retry_token_pool(void)
+{
+ pool_head_quic_retry_token =
+ create_pool("quic_retry_token", QUIC_RETRY_TOKEN_MAXLEN, MEM_F_SHARED|MEM_F_EXACT);
+ if (!pool_head_quic_retry_token) {
+ ha_warning("error on QUIC retry token buffer pool allocation.\n");
+ return ERR_FATAL|ERR_ABORT;
+ }
+
+ return ERR_NONE;
+}
+REGISTER_POST_CHECK(create_quic_retry_token_pool);
if (qc->ipktns && !quic_tls_pktns_is_dcd(qc, qc->ipktns)) {
/* Discard the handshake packet number space. */
TRACE_PROTO("discarding Initial pktns", QUIC_EV_CONN_PRSHPKT, qc);
- quic_pktns_discard(qc->ipktns, qc);
+ quic_pktns_discard(qc->ipktns, qc, 0);
qc_set_timer(qc);
qc_el_rx_pkts_del(qc->iel);
qc_release_pktns_frms(qc, qc->ipktns);
}
/* Retry of Version Negotiation packets are only sent by servers */
- if (pkt->type == QUIC_PACKET_TYPE_RETRY ||
- (pkt->version && !pkt->version->num)) {
+ if (l && (pkt->type == QUIC_PACKET_TYPE_RETRY || (pkt->version && !pkt->version->num))) {
TRACE_PROTO("Packet dropped", QUIC_EV_CONN_LPKT);
goto drop;
}
goto drop_silent;
}
- /* For Initial packets, and for servers (QUIC clients connections),
- * there is no Initial connection IDs storage.
- */
if (pkt->type == QUIC_PACKET_TYPE_INITIAL) {
+ /* For Initial packets, and for servers (QUIC clients connections),
+ * there is no Initial connection IDs storage.
+ */
uint64_t token_len;
if (!quic_dec_int(&token_len, (const unsigned char **)&pos, end) ||
pkt->token_len = token_len;
pos += pkt->token_len;
}
+ else if (pkt->type == QUIC_PACKET_TYPE_RETRY) {
+ if (!quic_retry_packet_check(qc, pkt, beg, end, pos, &qc->retry_token_len))
+ /* TODO: should close the connection? */
+ goto drop;
+
+ qc->retry_token = pool_alloc(pool_head_quic_retry_token);
+ if (!qc->retry_token) {
+ TRACE_ERROR("retry token allocation failed", QUIC_EV_CONN_LPKT);
+ }
+ else {
+ memcpy(qc->retry_token, pos, qc->retry_token_len);
+ /* Save the peer Retry source connection ID into the connection ODCID.
+ * This is also this connection DCID (or even the first ODCID value).
+ * It can be erased because used only to check the retry integrity
+ * tag. Then, it will be matched against the retry_source_connection_id
+ * transport parameter which will be sent by the server.
+ */
+ memcpy(qc->odcid.data, pkt->scid.data, pkt->scid.len);
+ qc->odcid.len = pkt->scid.len;
+ /* Copy the peer scid to be the destination of the next Initial packet */
+ memcpy(qc->dcid.data, pkt->scid.data, pkt->scid.len);
+ qc->dcid.len = pkt->scid.len;
+ /* Initial packet number space discarding without releasing
+ * the existing frames (not already sent).
+ */
+ quic_pktns_discard(qc->ipktns, qc, 1);
+ qc_set_timer(qc);
+ qc_el_rx_pkts_del(qc->iel);
+ /* Reset the DISCARDED flag for Initial packet number space */
+ qc->flags &= ~QUIC_FL_CONN_IPKTNS_DCD;
+ /* Change the Initial TLS cryptographic context */
+ quic_tls_ctx_secs_free(&qc->iel->tls_ctx);
+ if (!qc_new_isecs(qc, &qc->iel->tls_ctx, qc->original_version,
+ qc->dcid.data, qc->dcid.len, !!l))
+ goto drop_silent;
+
+ tasklet_wakeup(qc->wait_event.tasklet);
+ }
+
+ goto drop_silent;
+ }
else if (pkt->type != QUIC_PACKET_TYPE_0RTT) {
if (pkt->dcid.len != QUIC_HAP_CID_LEN) {
TRACE_PROTO("Packet dropped",
if (!*pktns)
return;
- quic_pktns_tx_pkts_release(*pktns, qc);
+ quic_pktns_tx_pkts_release(*pktns, qc, 0);
qc_release_pktns_frms(qc, *pktns);
quic_free_arngs(qc, &(*pktns)->rx.arngs);
LIST_DEL_INIT(&(*pktns)->list);
}
/* Generate the AEAD tag for the Retry packet <pkt> of <pkt_len> bytes and
- * write it to <tag>. The tag is written just after the <pkt> area. It should
+ * write it to <tag>. The tag is written at <tag> address. It should
* be at least 16 bytes longs. <odcid> is the CID of the Initial packet
* received which triggers the Retry.
*
* Returns non-zero on success else zero.
*/
int quic_tls_generate_retry_integrity_tag(unsigned char *odcid, unsigned char odcid_len,
- unsigned char *pkt, size_t pkt_len,
+ const unsigned char *pkt, size_t pkt_len,
+ unsigned char *tag,
const struct quic_version *qv)
{
const EVP_CIPHER *evp = EVP_aes_128_gcm();
/* encryption buffer - not used as only AEAD tag generation is proceed */
unsigned char *out = NULL;
- /* address to store the AEAD tag */
- unsigned char *tag = pkt + pkt_len;
int outlen, ret = 0;
ctx = EVP_CIPHER_CTX_new();
if (!server)
return QUIC_TP_DEC_ERR_INVAL;
- /* TODO implement parsing for client side */
+ if (len > sizeof p->retry_source_connection_id.data)
+ return QUIC_TP_DEC_ERR_TRUNC;
+
+ if (len)
+ memcpy(p->retry_source_connection_id.data, *buf, len);
+ p->retry_source_connection_id.len = len;
+ *buf += len;
break;
default:
*buf += len;
return 0;
}
+ if (server && (qc->odcid.len != tx_params->retry_source_connection_id.len ||
+ memcmp(qc->odcid.data, tx_params->retry_source_connection_id.data, qc->odcid.len) != 0)) {
+ TRACE_ERROR("retry_source_connection_id mismatch", QUIC_EV_TRANSP_PARAMS, qc);
+ return 0;
+ }
+
/* Update the connection from transport parameters received */
if (tx_params->version_information.negotiated_version &&
tx_params->version_information.negotiated_version != qc->original_version)
/* token integrity tag */
if ((sizeof(buf) - i < QUIC_TLS_TAG_LEN) ||
!quic_tls_generate_retry_integrity_tag(pkt->dcid.data,
- pkt->dcid.len, buf, i, qv)) {
+ pkt->dcid.len, buf, i, buf + i, qv)) {
TRACE_ERROR("quic_tls_generate_retry_integrity_tag() failed", QUIC_EV_CONN_TXPKT);
goto out;
}
/* Encode the token length (0) for an Initial packet. */
if (pkt->type == QUIC_PACKET_TYPE_INITIAL) {
- if (end <= pos)
+ if (!quic_enc_int(&pos, end, qc->retry_token_len) ||
+ end - pos <= qc->retry_token_len)
goto no_room;
- *pos++ = 0;
+ if (qc->retry_token_len) {
+ memcpy(pos, qc->retry_token, qc->retry_token_len);
+ pos += qc->retry_token_len;
+ }
}
head_len = pos - beg;