aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSergey Kandaurov <pluknet@nginx.com>2020-05-14 15:47:18 +0300
committerSergey Kandaurov <pluknet@nginx.com>2020-05-14 15:47:18 +0300
commitad2289e70ed3b3c0d0fb75b554f493a60db99307 (patch)
treef38fea4e2e80b5faf9403349f3a2e4840345d459 /src
parentd35eebede2dc224cff5773badb6d75ad05c9bd65 (diff)
downloadnginx-ad2289e70ed3b3c0d0fb75b554f493a60db99307.tar.gz
nginx-ad2289e70ed3b3c0d0fb75b554f493a60db99307.zip
Address validation using Retry packets.
The behaviour is toggled with the new directive "quic_retry on|off". QUIC token construction is made suitable for issuing with NEW_TOKEN.
Diffstat (limited to 'src')
-rw-r--r--src/event/ngx_event_quic.c377
-rw-r--r--src/event/ngx_event_quic.h12
-rw-r--r--src/event/ngx_event_quic_protection.c53
-rw-r--r--src/event/ngx_event_quic_transport.c42
-rw-r--r--src/event/ngx_event_quic_transport.h4
-rw-r--r--src/http/v3/ngx_http_v3_module.c18
6 files changed, 497 insertions, 9 deletions
diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c
index d13274159..2fd629c3b 100644
--- a/src/event/ngx_event_quic.c
+++ b/src/event/ngx_event_quic.c
@@ -123,6 +123,7 @@ struct ngx_quic_connection_s {
unsigned closing:1;
unsigned draining:1;
unsigned key_phase:1;
+ unsigned in_retry:1;
};
@@ -154,6 +155,10 @@ static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl,
ngx_quic_tp_t *tp, ngx_quic_header_t *pkt,
ngx_connection_handler_pt handler);
static ngx_int_t ngx_quic_new_dcid(ngx_connection_t *c, ngx_str_t *odcid);
+static ngx_int_t ngx_quic_retry(ngx_connection_t *c);
+static ngx_int_t ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token);
+static ngx_int_t ngx_quic_validate_token(ngx_connection_t *c,
+ ngx_quic_header_t *pkt);
static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c);
static void ngx_quic_input_handler(ngx_event_t *rev);
@@ -165,6 +170,8 @@ static ngx_int_t ngx_quic_close_streams(ngx_connection_t *c,
static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b);
static ngx_inline u_char *ngx_quic_skip_zero_padding(ngx_buf_t *b);
+static ngx_int_t ngx_quic_retry_input(ngx_connection_t *c,
+ ngx_quic_header_t *pkt);
static ngx_int_t ngx_quic_initial_input(ngx_connection_t *c,
ngx_quic_header_t *pkt);
static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c,
@@ -524,7 +531,8 @@ ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp,
return;
}
- ngx_add_timer(c->read, c->quic->tp.max_idle_timeout);
+ ngx_add_timer(c->read, c->quic->in_retry ? NGX_QUIC_RETRY_TIMEOUT
+ : c->quic->tp.max_idle_timeout);
c->read->handler = ngx_quic_input_handler;
@@ -625,13 +633,6 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp,
}
ngx_memcpy(qc->scid.data, pkt->scid.data, qc->scid.len);
- qc->token.len = pkt->token.len;
- qc->token.data = ngx_pnalloc(c->pool, qc->token.len);
- if (qc->token.data == NULL) {
- return NGX_ERROR;
- }
- ngx_memcpy(qc->token.data, pkt->token.data, qc->token.len);
-
keys = &c->quic->keys[ssl_encryption_initial];
if (ngx_quic_set_initial_secret(c->pool, &keys->client, &keys->server,
@@ -641,6 +642,10 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp,
return NGX_ERROR;
}
+ if (tp->retry) {
+ return ngx_quic_retry(c);
+ }
+
pkt->secret = &keys->client;
pkt->level = ssl_encryption_initial;
pkt->plaintext = buf;
@@ -707,6 +712,270 @@ ngx_quic_new_dcid(ngx_connection_t *c, ngx_str_t *odcid)
static ngx_int_t
+ngx_quic_retry(ngx_connection_t *c)
+{
+ ssize_t len;
+ ngx_str_t res, token;
+ ngx_quic_header_t pkt;
+ u_char buf[NGX_QUIC_RETRY_BUFFER_SIZE];
+
+ if (ngx_quic_new_token(c, &token) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ngx_memzero(&pkt, sizeof(ngx_quic_header_t));
+ pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_RETRY;
+ pkt.log = c->log;
+ pkt.odcid = c->quic->odcid;
+ pkt.dcid = c->quic->scid;
+ pkt.scid = c->quic->dcid;
+ pkt.token = token;
+
+ res.data = buf;
+
+ if (ngx_quic_encrypt(&pkt, NULL, &res) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+#ifdef NGX_QUIC_DEBUG_PACKETS
+ ngx_quic_hexdump(c->log, "quic packet to send", res.data, res.len);
+#endif
+
+ len = c->send(c, res.data, res.len);
+ if (len == NGX_ERROR || (size_t) len != res.len) {
+ return NGX_ERROR;
+ }
+
+ c->quic->token = token;
+ c->quic->tp.original_connection_id = c->quic->odcid;
+ c->quic->in_retry = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token)
+{
+ int len, iv_len;
+ u_char *data, *p, *key, *iv;
+ ngx_msec_t now;
+ EVP_CIPHER_CTX *ctx;
+ const EVP_CIPHER *cipher;
+ struct sockaddr_in *sin;
+#if (NGX_HAVE_INET6)
+ struct sockaddr_in6 *sin6;
+#endif
+ u_char in[NGX_QUIC_MAX_TOKEN_SIZE];
+
+ switch (c->sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *) c->sockaddr;
+
+ len = sizeof(struct in6_addr);
+ data = sin6->sin6_addr.s6_addr;
+
+ break;
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+ case AF_UNIX:
+
+ len = ngx_min(c->addr_text.len, NGX_QUIC_MAX_TOKEN_SIZE - sizeof(now));
+ data = c->addr_text.data;
+
+ break;
+#endif
+
+ default: /* AF_INET */
+ sin = (struct sockaddr_in *) c->sockaddr;
+
+ len = sizeof(in_addr_t);
+ data = (u_char *) &sin->sin_addr;
+
+ break;
+ }
+
+ p = ngx_cpymem(in, data, len);
+
+ now = ngx_current_msec;
+ len += sizeof(now);
+ ngx_memcpy(p, &now, sizeof(now));
+
+ cipher = EVP_aes_256_cbc();
+ iv_len = EVP_CIPHER_iv_length(cipher);
+
+ token->len = iv_len + len + EVP_CIPHER_block_size(cipher);
+ token->data = ngx_pnalloc(c->pool, token->len);
+ if (token->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx = EVP_CIPHER_CTX_new();
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ key = c->quic->tp.token_key;
+ iv = token->data;
+
+ if (RAND_bytes(iv, iv_len) <= 0
+ || !EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv))
+ {
+ EVP_CIPHER_CTX_free(ctx);
+ return NGX_ERROR;
+ }
+
+ token->len = iv_len;
+
+ if (EVP_EncryptUpdate(ctx, token->data + token->len, &len, in, len) != 1) {
+ EVP_CIPHER_CTX_free(ctx);
+ return NGX_ERROR;
+ }
+
+ token->len += len;
+
+ if (EVP_EncryptFinal_ex(ctx, token->data + token->len, &len) <= 0) {
+ EVP_CIPHER_CTX_free(ctx);
+ return NGX_ERROR;
+ }
+
+ token->len += len;
+
+ EVP_CIPHER_CTX_free(ctx);
+
+#ifdef NGX_QUIC_DEBUG_PACKETS
+ ngx_quic_hexdump(c->log, "quic new token", token->data, token->len);
+#endif
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt)
+{
+ int len, tlen, iv_len;
+ u_char *key, *iv, *p, *data;
+ ngx_msec_t msec;
+ EVP_CIPHER_CTX *ctx;
+ const EVP_CIPHER *cipher;
+ struct sockaddr_in *sin;
+#if (NGX_HAVE_INET6)
+ struct sockaddr_in6 *sin6;
+#endif
+ ngx_quic_connection_t *qc;
+ u_char tdec[NGX_QUIC_MAX_TOKEN_SIZE];
+
+ if (pkt->token.len == 0) {
+ return NGX_ERROR;
+ }
+
+ qc = c->quic;
+
+ /* Retry token */
+
+ if (qc->token.len) {
+ if (pkt->token.len != qc->token.len) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_memcmp(pkt->token.data, qc->token.data, pkt->token.len) != 0) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+ }
+
+ /* NEW_TOKEN in a previous connection */
+
+ cipher = EVP_aes_256_cbc();
+ key = c->quic->tp.token_key;
+ iv = pkt->token.data;
+ iv_len = EVP_CIPHER_iv_length(cipher);
+
+ /* sanity checks */
+
+ if (pkt->token.len < (size_t) iv_len + EVP_CIPHER_block_size(cipher)) {
+ return NGX_ERROR;
+ }
+
+ if (pkt->token.len > (size_t) iv_len + NGX_QUIC_MAX_TOKEN_SIZE) {
+ return NGX_ERROR;
+ }
+
+ ctx = EVP_CIPHER_CTX_new();
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (!EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv)) {
+ EVP_CIPHER_CTX_free(ctx);
+ return NGX_ERROR;
+ }
+
+ p = pkt->token.data + iv_len;
+ len = pkt->token.len - iv_len;
+
+ if (EVP_DecryptUpdate(ctx, tdec, &len, p, len) != 1) {
+ EVP_CIPHER_CTX_free(ctx);
+ return NGX_ERROR;
+ }
+
+ if (EVP_DecryptFinal_ex(ctx, tdec + len, &tlen) <= 0) {
+ EVP_CIPHER_CTX_free(ctx);
+ return NGX_ERROR;
+ }
+
+ EVP_CIPHER_CTX_free(ctx);
+
+ switch (c->sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *) c->sockaddr;
+
+ len = sizeof(struct in6_addr);
+ data = sin6->sin6_addr.s6_addr;
+
+ break;
+#endif
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+ case AF_UNIX:
+
+ len = ngx_min(c->addr_text.len, NGX_QUIC_MAX_TOKEN_SIZE - sizeof(msec));
+ data = c->addr_text.data;
+
+ break;
+#endif
+
+ default: /* AF_INET */
+ sin = (struct sockaddr_in *) c->sockaddr;
+
+ len = sizeof(in_addr_t);
+ data = (u_char *) &sin->sin_addr;
+
+ break;
+ }
+
+ if (ngx_memcmp(tdec, data, len) != 0) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(&msec, tdec + len, sizeof(msec));
+
+ if (ngx_current_msec - msec > NGX_QUIC_RETRY_LIFETIME) {
+ return NGX_DECLINED;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
ngx_quic_init_connection(ngx_connection_t *c)
{
u_char *p;
@@ -776,6 +1045,7 @@ ngx_quic_input_handler(ngx_event_t *rev)
b.start = buf;
b.end = buf + sizeof(buf);
b.pos = b.last = b.start;
+ b.memory = 1;
c = rev->data;
qc = c->quic;
@@ -1047,6 +1317,10 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b)
pkt.log = c->log;
pkt.flags = p[0];
+ if (c->quic->in_retry) {
+ return ngx_quic_retry_input(c, &pkt);
+ }
+
/* TODO: check current state */
if (ngx_quic_long_pkt(pkt.flags)) {
@@ -1111,6 +1385,93 @@ ngx_quic_skip_zero_padding(ngx_buf_t *b)
static ngx_int_t
+ngx_quic_retry_input(ngx_connection_t *c, ngx_quic_header_t *pkt)
+{
+ ngx_quic_secrets_t *keys;
+ ngx_quic_send_ctx_t *ctx;
+ ngx_quic_connection_t *qc;
+ static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE];
+
+ c->log->action = "retrying quic connection";
+
+ qc = c->quic;
+
+ if (ngx_buf_size(pkt->raw) < NGX_QUIC_MIN_INITIAL_SIZE) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic UDP datagram is too small for initial packet");
+ return NGX_OK;
+ }
+
+ if (ngx_quic_parse_long_header(pkt) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_quic_pkt_zrtt(pkt->flags)) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic discard inflight 0-RTT packet");
+ return NGX_OK;
+ }
+
+ if (!ngx_quic_pkt_in(pkt->flags)) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic invalid initial packet: 0x%xi", pkt->flags);
+ return NGX_ERROR;
+ }
+
+ if (ngx_quic_parse_initial_header(pkt) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_quic_new_dcid(c, &pkt->dcid) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ qc = c->quic;
+
+ keys = &c->quic->keys[ssl_encryption_initial];
+
+ if (ngx_quic_set_initial_secret(c->pool, &keys->client, &keys->server,
+ &qc->odcid)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ c->quic->in_retry = 0;
+
+ if (ngx_quic_validate_token(c, pkt) != NGX_OK) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid token");
+ return NGX_ERROR;
+ }
+
+ pkt->secret = &keys->client;
+ pkt->level = ssl_encryption_initial;
+ pkt->plaintext = buf;
+
+ ctx = ngx_quic_get_send_ctx(qc, pkt->level);
+
+ if (ngx_quic_decrypt(pkt, NULL, &ctx->largest_pn) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_quic_init_connection(c) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_quic_payload_handler(c, pkt) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ /* pos is at header end, adjust by actual packet length */
+ pkt->raw->pos += pkt->len;
+
+ (void) ngx_quic_skip_zero_padding(pkt->raw);
+
+ return ngx_quic_input(c, pkt->raw);
+}
+
+
+static ngx_int_t
ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt)
{
ngx_ssl_conn_t *ssl_conn;
diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h
index eb184bd7d..862b339eb 100644
--- a/src/event/ngx_event_quic.h
+++ b/src/event/ngx_event_quic.h
@@ -23,6 +23,13 @@
#define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3
#define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25
+#define NGX_QUIC_RETRY_TIMEOUT 3000
+#define NGX_QUIC_RETRY_LIFETIME 30000
+#define NGX_QUIC_RETRY_BUFFER_SIZE 128
+ /* 1 flags + 4 version + 3 x (1 + 20) s/o/dcid + itag + token(44) */
+#define NGX_QUIC_MAX_TOKEN_SIZE 32
+ /* sizeof(struct in6_addr) + sizeof(ngx_msec_t) up to AES-256 block size */
+
#define NGX_QUIC_HARDCODED_PTO 1000 /* 1s, TODO: collect */
#define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */
@@ -49,9 +56,12 @@ typedef struct {
ngx_uint_t ack_delay_exponent;
ngx_uint_t disable_active_migration;
ngx_uint_t active_connection_id_limit;
+ ngx_str_t original_connection_id;
+
+ ngx_flag_t retry;
+ u_char token_key[32]; /* AES 256 */
/* TODO */
- ngx_uint_t original_connection_id;
u_char stateless_reset_token[16];
void *preferred_address;
} ngx_quic_tp_t;
diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c
index 6af047d40..97fc58ecc 100644
--- a/src/event/ngx_event_quic_protection.c
+++ b/src/event/ngx_event_quic_protection.c
@@ -57,6 +57,8 @@ static ngx_int_t ngx_quic_create_long_packet(ngx_quic_header_t *pkt,
ngx_ssl_conn_t *ssl_conn, ngx_str_t *res);
static ngx_int_t ngx_quic_create_short_packet(ngx_quic_header_t *pkt,
ngx_ssl_conn_t *ssl_conn, ngx_str_t *res);
+static ngx_int_t ngx_quic_create_retry_packet(ngx_quic_header_t *pkt,
+ ngx_str_t *res);
static ngx_int_t
@@ -891,6 +893,53 @@ ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn,
}
+static ngx_int_t
+ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res)
+{
+ u_char *start;
+ ngx_str_t ad, itag;
+ ngx_quic_secret_t secret;
+ ngx_quic_ciphers_t ciphers;
+
+ /* 5.8. Retry Packet Integrity */
+ static u_char key[16] =
+ "\x4d\x32\xec\xdb\x2a\x21\x33\xc8"
+ "\x41\xe4\x04\x3d\xf2\x7d\x44\x30";
+ static u_char nonce[12] =
+ "\x4d\x16\x11\xd0\x55\x13"
+ "\xa5\x52\xc5\x87\xd5\x75";
+ static ngx_str_t in = ngx_string("");
+
+ ad.data = res->data;
+ ad.len = ngx_quic_create_retry_itag(pkt, ad.data, &start);
+
+ itag.data = ad.data + ad.len;
+
+#ifdef NGX_QUIC_DEBUG_CRYPTO
+ ngx_quic_hexdump(pkt->log, "quic retry itag", ad.data, ad.len);
+#endif
+
+ if (ngx_quic_ciphers(NULL, &ciphers, pkt->level) == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ secret.key.len = sizeof(key);
+ secret.key.data = key;
+ secret.iv.len = sizeof(nonce);
+
+ if (ngx_quic_tls_seal(ciphers.c, &secret, &itag, nonce, &in, &ad, pkt->log)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ res->len = itag.data + itag.len - start;
+ res->data = start;
+
+ return NGX_OK;
+}
+
+
static uint64_t
ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask,
uint64_t *largest_pn)
@@ -952,6 +1001,10 @@ ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn,
return ngx_quic_create_short_packet(pkt, ssl_conn, res);
}
+ if (ngx_quic_pkt_retry(pkt->flags)) {
+ return ngx_quic_create_retry_packet(pkt, res);
+ }
+
return ngx_quic_create_long_packet(pkt, ssl_conn, res);
}
diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c
index 5098b505e..7f064eb54 100644
--- a/src/event/ngx_event_quic_transport.c
+++ b/src/event/ngx_event_quic_transport.c
@@ -385,6 +385,35 @@ ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out,
}
+size_t
+ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out,
+ u_char **start)
+{
+ u_char *p;
+
+ p = out;
+
+ *p++ = pkt->odcid.len;
+ p = ngx_cpymem(p, pkt->odcid.data, pkt->odcid.len);
+
+ *start = p;
+
+ *p++ = 0xff;
+
+ p = ngx_quic_write_uint32(p, NGX_QUIC_VERSION);
+
+ *p++ = pkt->dcid.len;
+ p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len);
+
+ *p++ = pkt->scid.len;
+ p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len);
+
+ p = ngx_cpymem(p, pkt->token.data, pkt->token.len);
+
+ return p - out;
+}
+
+
ngx_int_t
ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid)
{
@@ -1553,6 +1582,12 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp)
len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_IDLE_TIMEOUT,
tp->max_idle_timeout);
+ if (tp->retry) {
+ len += ngx_quic_varint_len(NGX_QUIC_TP_ORIGINAL_CONNECTION_ID);
+ len += ngx_quic_varint_len(tp->original_connection_id.len);
+ len += tp->original_connection_id.len;
+ }
+
if (pos == NULL) {
return len;
}
@@ -1581,6 +1616,13 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp)
ngx_quic_tp_vint(NGX_QUIC_TP_MAX_IDLE_TIMEOUT,
tp->max_idle_timeout);
+ if (tp->retry) {
+ ngx_quic_build_int(&p, NGX_QUIC_TP_ORIGINAL_CONNECTION_ID);
+ ngx_quic_build_int(&p, tp->original_connection_id.len);
+ p = ngx_cpymem(p, tp->original_connection_id.data,
+ tp->original_connection_id.len);
+ }
+
return p - pos;
}
diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h
index e2b383e5e..62ec842d6 100644
--- a/src/event/ngx_event_quic_transport.h
+++ b/src/event/ngx_event_quic_transport.h
@@ -280,6 +280,7 @@ typedef struct {
size_t len;
/* cleartext fields */
+ ngx_str_t odcid; /* retry packet tag */
ngx_str_t dcid;
ngx_str_t scid;
uint64_t pn;
@@ -303,6 +304,9 @@ ngx_int_t ngx_quic_parse_short_header(ngx_quic_header_t *pkt,
size_t ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out,
size_t pkt_len, u_char **pnp);
+size_t ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out,
+ u_char **start);
+
ngx_int_t ngx_quic_parse_initial_header(ngx_quic_header_t *pkt);
ngx_int_t ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt);
diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c
index 9daaedb3e..efad51c71 100644
--- a/src/http/v3/ngx_http_v3_module.c
+++ b/src/http/v3/ngx_http_v3_module.c
@@ -111,6 +111,13 @@ static ngx_command_t ngx_http_v3_commands[] = {
offsetof(ngx_http_v3_srv_conf_t, quic.active_connection_id_limit),
&ngx_http_v3_active_connection_id_limit_bounds },
+ { ngx_string("quic_retry"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_v3_srv_conf_t, quic.retry),
+ NULL },
+
ngx_null_command
};
@@ -257,6 +264,8 @@ ngx_http_v3_create_srv_conf(ngx_conf_t *cf)
v3cf->quic.disable_active_migration = NGX_CONF_UNSET_UINT;
v3cf->quic.active_connection_id_limit = NGX_CONF_UNSET_UINT;
+ v3cf->quic.retry = NGX_CONF_UNSET;
+
return v3cf;
}
@@ -310,6 +319,15 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
ngx_conf_merge_uint_value(conf->quic.active_connection_id_limit,
prev->quic.active_connection_id_limit, 2);
+ ngx_conf_merge_value(conf->quic.retry, prev->quic.retry, 0);
+
+ if (conf->quic.retry) {
+ if (RAND_bytes(conf->quic.token_key, sizeof(conf->quic.token_key)) <= 0) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+
return NGX_CONF_OK;
}