QUIC: stateless retry.

Previously, quic connection object was created when Retry packet was sent.
This is neither necessary nor convenient, and contradicts the idea of retry:
protecting from bad clients and saving server resources.

Now, the connection is not created, token is verified cryptographically
instead of holding it in connection.
This commit is contained in:
Vladimir Homutov 2021-01-29 15:53:47 +03:00
parent cd62534300
commit 89dda20510
5 changed files with 326 additions and 253 deletions

View File

@ -9,6 +9,7 @@
#include <ngx_event.h> #include <ngx_event.h>
#include <ngx_event_quic_transport.h> #include <ngx_event_quic_transport.h>
#include <ngx_event_quic_protection.h> #include <ngx_event_quic_protection.h>
#include <ngx_sha1.h>
/* 0-RTT and 1-RTT data exist in the same packet number space, /* 0-RTT and 1-RTT data exist in the same packet number space,
@ -113,7 +114,6 @@ typedef struct {
ngx_str_t scid; /* initial client ID */ ngx_str_t scid; /* initial client ID */
ngx_str_t dcid; /* server (our own) ID */ ngx_str_t dcid; /* server (our own) ID */
ngx_str_t odcid; /* original server ID */ ngx_str_t odcid; /* original server ID */
ngx_str_t token;
struct sockaddr *sockaddr; struct sockaddr *sockaddr;
socklen_t socklen; socklen_t socklen;
@ -175,8 +175,6 @@ typedef struct {
unsigned closing:1; unsigned closing:1;
unsigned draining:1; unsigned draining:1;
unsigned key_phase:1; unsigned key_phase:1;
unsigned in_retry:1;
unsigned initialized:1;
unsigned validated:1; unsigned validated:1;
} ngx_quic_connection_t; } ngx_quic_connection_t;
@ -235,10 +233,14 @@ static ngx_int_t ngx_quic_create_server_id(ngx_connection_t *c, u_char *id);
#if (NGX_QUIC_BPF) #if (NGX_QUIC_BPF)
static ngx_int_t ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id); static ngx_int_t ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id);
#endif #endif
static ngx_int_t ngx_quic_send_retry(ngx_connection_t *c); static ngx_int_t ngx_quic_send_retry(ngx_connection_t *c,
static ngx_int_t ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token); ngx_quic_conf_t *conf, ngx_quic_header_t *pkt);
static ngx_int_t ngx_quic_new_token(ngx_connection_t *c, u_char *key,
ngx_str_t *token, ngx_str_t *odcid, time_t expires, ngx_uint_t is_retry);
static void ngx_quic_address_hash(ngx_connection_t *c, ngx_uint_t no_port,
u_char buf[20]);
static ngx_int_t ngx_quic_validate_token(ngx_connection_t *c, static ngx_int_t ngx_quic_validate_token(ngx_connection_t *c,
ngx_quic_header_t *pkt); u_char *key, ngx_quic_header_t *pkt);
static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c); static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c);
static ngx_inline size_t ngx_quic_max_udp_payload(ngx_connection_t *c); static ngx_inline size_t ngx_quic_max_udp_payload(ngx_connection_t *c);
static void ngx_quic_input_handler(ngx_event_t *rev); static void ngx_quic_input_handler(ngx_event_t *rev);
@ -253,7 +255,8 @@ static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b,
ngx_quic_conf_t *conf); ngx_quic_conf_t *conf);
static ngx_int_t ngx_quic_process_packet(ngx_connection_t *c, static ngx_int_t ngx_quic_process_packet(ngx_connection_t *c,
ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); ngx_quic_conf_t *conf, ngx_quic_header_t *pkt);
static ngx_int_t ngx_quic_init_secrets(ngx_connection_t *c); static ngx_int_t ngx_quic_send_early_cc(ngx_connection_t *c,
ngx_quic_header_t *inpkt, ngx_uint_t err, const char *reason);
static void ngx_quic_discard_ctx(ngx_connection_t *c, static void ngx_quic_discard_ctx(ngx_connection_t *c,
enum ssl_encryption_level_t level); enum ssl_encryption_level_t level);
static ngx_int_t ngx_quic_check_peer(ngx_quic_connection_t *qc, static ngx_int_t ngx_quic_check_peer(ngx_quic_connection_t *qc,
@ -673,7 +676,6 @@ ngx_quic_connstate_dbg(ngx_connection_t *c)
p = ngx_slprintf(p, last, "%s", qc->closing ? " closing" : ""); p = ngx_slprintf(p, last, "%s", qc->closing ? " closing" : "");
p = ngx_slprintf(p, last, "%s", qc->draining ? " draining" : ""); p = ngx_slprintf(p, last, "%s", qc->draining ? " draining" : "");
p = ngx_slprintf(p, last, "%s", qc->key_phase ? " kp" : ""); p = ngx_slprintf(p, last, "%s", qc->key_phase ? " kp" : "");
p = ngx_slprintf(p, last, "%s", qc->in_retry ? " retry" : "");
p = ngx_slprintf(p, last, "%s", qc->validated? " valid" : ""); p = ngx_slprintf(p, last, "%s", qc->validated? " valid" : "");
} else { } else {
@ -1014,12 +1016,16 @@ ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf)
qc = ngx_quic_get_connection(c); qc = ngx_quic_get_connection(c);
ngx_add_timer(c->read, qc->in_retry ? NGX_QUIC_RETRY_TIMEOUT if (qc == NULL) {
: qc->tp.max_idle_timeout); ngx_quic_close_connection(c, NGX_DONE);
return;
}
ngx_add_timer(c->read, qc->tp.max_idle_timeout);
ngx_quic_connstate_dbg(c);
c->read->handler = ngx_quic_input_handler; c->read->handler = ngx_quic_input_handler;
ngx_quic_connstate_dbg(c);
return; return;
} }
@ -1123,8 +1129,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf,
qc->congestion.ssthresh = (size_t) -1; qc->congestion.ssthresh = (size_t) -1;
qc->congestion.recovery_start = ngx_current_msec; qc->congestion.recovery_start = ngx_current_msec;
qc->odcid.len = pkt->dcid.len; qc->odcid.len = pkt->odcid.len;
qc->odcid.data = ngx_pstrdup(c->pool, &pkt->dcid); qc->odcid.data = ngx_pstrdup(c->pool, &pkt->odcid);
if (qc->odcid.data == NULL) { if (qc->odcid.data == NULL) {
return NULL; return NULL;
} }
@ -1144,12 +1150,19 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf,
#endif #endif
qc->tp.initial_scid = qc->dcid; qc->tp.initial_scid = qc->dcid;
if (pkt->validated && pkt->retried) {
qc->tp.retry_scid.len = pkt->dcid.len;
qc->tp.retry_scid.data = ngx_pstrdup(c->pool, &pkt->dcid);
if (qc->tp.retry_scid.data == NULL) {
return NULL;
}
}
qc->scid.len = pkt->scid.len; qc->scid.len = pkt->scid.len;
qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len); qc->scid.data = ngx_pstrdup(c->pool, &pkt->scid);
if (qc->scid.data == NULL) { if (qc->scid.data == NULL) {
return NULL; return NULL;
} }
ngx_memcpy(qc->scid.data, pkt->scid.data, qc->scid.len);
cid = ngx_quic_alloc_client_id(c, qc); cid = ngx_quic_alloc_client_id(c, qc);
if (cid == NULL) { if (cid == NULL) {
@ -1166,6 +1179,26 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf,
qc->server_seqnum = NGX_QUIC_UNSET_PN; qc->server_seqnum = NGX_QUIC_UNSET_PN;
if (ngx_quic_keys_set_initial_secret(c->pool, qc->keys, &pkt->dcid)
!= NGX_OK)
{
return NULL;
}
c->udp = &qc->udp;
if (ngx_quic_insert_server_id(c, &qc->odcid) == NULL) {
return NULL;
}
qc->server_seqnum = 0;
if (ngx_quic_insert_server_id(c, &qc->dcid) == NULL) {
return NULL;
}
qc->validated = pkt->validated;
return qc; return qc;
} }
@ -1344,27 +1377,41 @@ ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id)
static ngx_int_t static ngx_int_t
ngx_quic_send_retry(ngx_connection_t *c) ngx_quic_send_retry(ngx_connection_t *c, ngx_quic_conf_t *conf,
ngx_quic_header_t *inpkt)
{ {
ssize_t len; time_t expires;
ngx_str_t res, token; ssize_t len;
ngx_quic_header_t pkt; ngx_str_t res, token;
ngx_quic_connection_t *qc; ngx_quic_header_t pkt;
u_char buf[NGX_QUIC_RETRY_BUFFER_SIZE];
qc = ngx_quic_get_connection(c); u_char buf[NGX_QUIC_RETRY_BUFFER_SIZE];
u_char dcid[NGX_QUIC_SERVER_CID_LEN];
if (ngx_quic_new_token(c, &token) != NGX_OK) { expires = ngx_time() + NGX_QUIC_RETRY_LIFETIME;
if (ngx_quic_new_token(c, conf->token_key, &token, &inpkt->dcid, expires, 1)
!= NGX_OK)
{
return NGX_ERROR; return NGX_ERROR;
} }
ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); ngx_memzero(&pkt, sizeof(ngx_quic_header_t));
pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_RETRY; pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_RETRY;
pkt.version = qc->version; pkt.version = inpkt->version;
pkt.log = c->log; pkt.log = c->log;
pkt.odcid = qc->odcid;
pkt.dcid = qc->scid; pkt.odcid = inpkt->dcid;
pkt.scid = qc->dcid; pkt.dcid = inpkt->scid;
/* TODO: generate routable dcid */
if (RAND_bytes(dcid, NGX_QUIC_SERVER_CID_LEN) != 1) {
return NGX_ERROR;
}
pkt.scid.len = NGX_QUIC_SERVER_CID_LEN;
pkt.scid.data = dcid;
pkt.token = token; pkt.token = token;
res.data = buf; res.data = buf;
@ -1383,71 +1430,46 @@ ngx_quic_send_retry(ngx_connection_t *c)
return NGX_ERROR; return NGX_ERROR;
} }
qc->token = token; ngx_log_debug(NGX_LOG_DEBUG_EVENT, c->log, 0,
#if (NGX_QUIC_DRAFT_VERSION < 28) "quic retry packet sent to %xV", &pkt.dcid);
qc->tp.original_dcid = qc->odcid;
#endif
qc->tp.retry_scid = qc->dcid;
qc->in_retry = 1;
if (ngx_quic_insert_server_id(c, &qc->dcid) == NULL) { /*
return NGX_ERROR; * quic-transport 17.2.5.1: A server MUST NOT send more than one Retry
} * packet in response to a single UDP datagram.
* NGX_DONE will stop quic_input() from processing further
return NGX_OK; */
return NGX_DONE;
} }
static ngx_int_t static ngx_int_t
ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token) ngx_quic_new_token(ngx_connection_t *c, u_char *key, ngx_str_t *token,
ngx_str_t *odcid, time_t exp, ngx_uint_t is_retry)
{ {
int len, iv_len; int len, iv_len;
u_char *data, *p, *key, *iv; u_char *p, *iv;
ngx_msec_t now; EVP_CIPHER_CTX *ctx;
EVP_CIPHER_CTX *ctx; const EVP_CIPHER *cipher;
const EVP_CIPHER *cipher;
struct sockaddr_in *sin;
#if (NGX_HAVE_INET6)
struct sockaddr_in6 *sin6;
#endif
ngx_quic_connection_t *qc;
u_char in[NGX_QUIC_MAX_TOKEN_SIZE];
switch (c->sockaddr->sa_family) { u_char in[NGX_QUIC_MAX_TOKEN_SIZE];
#if (NGX_HAVE_INET6) ngx_quic_address_hash(c, !is_retry, in);
case AF_INET6:
sin6 = (struct sockaddr_in6 *) c->sockaddr;
len = sizeof(struct in6_addr); p = in + 20;
data = sin6->sin6_addr.s6_addr;
break; p = ngx_cpymem(p, &exp, sizeof(time_t));
#endif
#if (NGX_HAVE_UNIX_DOMAIN) *p++ = is_retry ? 1 : 0;
case AF_UNIX:
len = ngx_min(c->addr_text.len, NGX_QUIC_MAX_TOKEN_SIZE - sizeof(now)); if (odcid) {
data = c->addr_text.data; *p++ = odcid->len;
p = ngx_cpymem(p, odcid->data, odcid->len);
break; } else {
#endif *p++ = 0;
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); len = p - in;
now = ngx_current_msec;
len += sizeof(now);
ngx_memcpy(p, &now, sizeof(now));
cipher = EVP_aes_256_cbc(); cipher = EVP_aes_256_cbc();
iv_len = EVP_CIPHER_iv_length(cipher); iv_len = EVP_CIPHER_iv_length(cipher);
@ -1463,8 +1485,6 @@ ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token)
return NGX_ERROR; return NGX_ERROR;
} }
qc = ngx_quic_get_connection(c);
key = qc->conf->token_key;
iv = token->data; iv = token->data;
if (RAND_bytes(iv, iv_len) <= 0 if (RAND_bytes(iv, iv_len) <= 0
@ -1501,52 +1521,78 @@ ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token)
} }
static ngx_int_t static void
ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_quic_address_hash(ngx_connection_t *c, ngx_uint_t no_port, u_char buf[20])
{ {
int len, tlen, iv_len; size_t len;
u_char *key, *iv, *p, *data; u_char *data;
ngx_msec_t msec; ngx_sha1_t sha1;
EVP_CIPHER_CTX *ctx; struct sockaddr_in *sin;
const EVP_CIPHER *cipher;
struct sockaddr_in *sin;
#if (NGX_HAVE_INET6) #if (NGX_HAVE_INET6)
struct sockaddr_in6 *sin6; struct sockaddr_in6 *sin6;
#endif #endif
ngx_quic_connection_t *qc;
u_char tdec[NGX_QUIC_MAX_TOKEN_SIZE];
qc = ngx_quic_get_connection(c); len = (size_t) c->socklen;
data = (u_char *) c->sockaddr;
/* Retry token */ if (no_port) {
switch (c->sockaddr->sa_family) {
if (qc->token.len) { #if (NGX_HAVE_INET6)
if (pkt->token.len != qc->token.len) { case AF_INET6:
goto bad_token; sin6 = (struct sockaddr_in6 *) c->sockaddr;
len = sizeof(struct in6_addr);
data = sin6->sin6_addr.s6_addr;
break;
#endif
case AF_INET:
sin = (struct sockaddr_in *) c->sockaddr;
len = sizeof(in_addr_t);
data = (u_char *) &sin->sin_addr;
break;
} }
if (ngx_memcmp(pkt->token.data, qc->token.data, pkt->token.len) != 0) {
goto bad_token;
}
return NGX_OK;
} }
/* NEW_TOKEN in a previous connection */ ngx_sha1_init(&sha1);
ngx_sha1_update(&sha1, data, len);
ngx_sha1_final(buf, &sha1);
}
static ngx_int_t
ngx_quic_validate_token(ngx_connection_t *c, u_char *key,
ngx_quic_header_t *pkt)
{
int len, tlen, iv_len;
u_char *iv, *p;
time_t now, exp;
size_t total;
ngx_str_t odcid;
EVP_CIPHER_CTX *ctx;
const EVP_CIPHER *cipher;
u_char addr_hash[20];
u_char tdec[NGX_QUIC_MAX_TOKEN_SIZE];
/* Retry token or NEW_TOKEN in a previous connection */
cipher = EVP_aes_256_cbc(); cipher = EVP_aes_256_cbc();
key = qc->conf->token_key;
iv = pkt->token.data; iv = pkt->token.data;
iv_len = EVP_CIPHER_iv_length(cipher); iv_len = EVP_CIPHER_iv_length(cipher);
/* sanity checks */ /* sanity checks */
if (pkt->token.len < (size_t) iv_len + EVP_CIPHER_block_size(cipher)) { if (pkt->token.len < (size_t) iv_len + EVP_CIPHER_block_size(cipher)) {
goto bad_token; goto garbage;
} }
if (pkt->token.len > (size_t) iv_len + NGX_QUIC_MAX_TOKEN_SIZE) { if (pkt->token.len > (size_t) iv_len + NGX_QUIC_MAX_TOKEN_SIZE) {
goto bad_token; goto garbage;
} }
ctx = EVP_CIPHER_CTX_new(); ctx = EVP_CIPHER_CTX_new();
@ -1564,66 +1610,81 @@ ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt)
if (EVP_DecryptUpdate(ctx, tdec, &len, p, len) != 1) { if (EVP_DecryptUpdate(ctx, tdec, &len, p, len) != 1) {
EVP_CIPHER_CTX_free(ctx); EVP_CIPHER_CTX_free(ctx);
goto bad_token; goto garbage;
} }
total = len;
if (EVP_DecryptFinal_ex(ctx, tdec + len, &tlen) <= 0) { if (EVP_DecryptFinal_ex(ctx, tdec + len, &tlen) <= 0) {
EVP_CIPHER_CTX_free(ctx); EVP_CIPHER_CTX_free(ctx);
goto bad_token; goto garbage;
} }
total += tlen;
EVP_CIPHER_CTX_free(ctx); EVP_CIPHER_CTX_free(ctx);
switch (c->sockaddr->sa_family) { if (total < (20 + sizeof(time_t) + 2)) {
goto garbage;
#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) { p = tdec + 20;
ngx_memcpy(&exp, p, sizeof(time_t));
p += sizeof(time_t);
pkt->retried = (*p++ == 1);
ngx_quic_address_hash(c, !pkt->retried, addr_hash);
if (ngx_memcmp(tdec, addr_hash, 20) != 0) {
goto bad_token; goto bad_token;
} }
ngx_memcpy(&msec, tdec + len, sizeof(msec)); odcid.len = *p++;
if (odcid.len) {
if (odcid.len > NGX_QUIC_MAX_CID_LEN) {
goto bad_token;
}
if (ngx_current_msec - msec > NGX_QUIC_RETRY_LIFETIME) { if ((size_t)(tdec + total - p) < odcid.len) {
goto bad_token;
}
odcid.data = p;
p += odcid.len;
}
now = ngx_time();
if (now > exp) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic expired token"); ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic expired token");
return NGX_DECLINED; return NGX_DECLINED;
} }
if (odcid.len) {
pkt->odcid.len = odcid.len;
pkt->odcid.data = ngx_pstrdup(c->pool, &odcid);
if (pkt->odcid.data == NULL) {
return NGX_ERROR;
}
} else {
pkt->odcid = pkt->dcid;
}
pkt->validated = 1;
return NGX_OK; return NGX_OK;
garbage:
ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic garbage token");
return NGX_ABORT;
bad_token: bad_token:
ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid token"); ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid token");
qc->error = NGX_QUIC_ERR_INVALID_TOKEN;
qc->error_reason = "invalid_token";
return NGX_DECLINED; return NGX_DECLINED;
} }
@ -1817,8 +1878,10 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc)
qc = ngx_quic_get_connection(c); qc = ngx_quic_get_connection(c);
if (qc == NULL) { if (qc == NULL) {
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, if (rc == NGX_ERROR) {
"quic close connection early error"); ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic close connection early error");
}
} else if (ngx_quic_close_quic(c, rc) == NGX_AGAIN) { } else if (ngx_quic_close_quic(c, rc) == NGX_AGAIN) {
return; return;
@ -2101,6 +2164,11 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf)
return NGX_ERROR; return NGX_ERROR;
} }
if (rc == NGX_DONE) {
/* stop further processing */
return NGX_DECLINED;
}
if (rc == NGX_OK) { if (rc == NGX_OK) {
good = 1; good = 1;
} }
@ -2216,60 +2284,6 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf,
return NGX_DECLINED; return NGX_DECLINED;
} }
if (qc->in_retry) {
c->log->action = "retrying quic connection";
if (pkt->level != ssl_encryption_initial) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic discard late retry packet");
return NGX_DECLINED;
}
if (!pkt->token.len) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic discard retry packet without token");
return NGX_DECLINED;
}
qc->odcid.len = pkt->dcid.len;
qc->odcid.data = ngx_pstrdup(c->pool, &pkt->dcid);
if (qc->odcid.data == NULL) {
return NGX_ERROR;
}
ngx_quic_clear_temp_server_ids(c);
qc->dcid.len = NGX_QUIC_SERVER_CID_LEN;
qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len);
if (qc->dcid.data == NULL) {
return NGX_ERROR;
}
if (ngx_quic_create_server_id(c, qc->dcid.data) != NGX_OK) {
return NGX_ERROR;
}
qc->server_seqnum = 0;
if (ngx_quic_insert_server_id(c, &qc->dcid) == NULL) {
return NGX_ERROR;
}
qc->tp.initial_scid = qc->dcid;
qc->in_retry = 0;
if (ngx_quic_init_secrets(c) != NGX_OK) {
return NGX_ERROR;
}
if (ngx_quic_validate_token(c, pkt) != NGX_OK) {
return NGX_ERROR;
}
qc->validated = 1;
}
} else { } else {
if (rc == NGX_ABORT) { if (rc == NGX_ABORT) {
@ -2277,8 +2291,7 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf,
} }
if (pkt->level == ssl_encryption_initial) { if (pkt->level == ssl_encryption_initial) {
c->log->action = "processing initial packet";
c->log->action = "creating quic connection";
if (pkt->dcid.len < NGX_QUIC_CID_LEN_MIN) { if (pkt->dcid.len < NGX_QUIC_CID_LEN_MIN) {
/* 7.2. Negotiating Connection IDs */ /* 7.2. Negotiating Connection IDs */
@ -2288,49 +2301,56 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf,
return NGX_ERROR; return NGX_ERROR;
} }
qc = ngx_quic_new_connection(c, conf, pkt); /* process retry and initialize connection IDs */
if (qc == NULL) {
return NGX_ERROR;
}
c->udp = &qc->udp;
if (ngx_terminate || ngx_exiting) {
qc->error = NGX_QUIC_ERR_CONNECTION_REFUSED;
return NGX_ERROR;
}
if (pkt->token.len) { if (pkt->token.len) {
rc = ngx_quic_validate_token(c, pkt);
if (rc == NGX_OK) { rc = ngx_quic_validate_token(c, conf->token_key, pkt);
qc->validated = 1;
} else if (rc == NGX_ERROR) { if (rc == NGX_ERROR) {
/* internal error */
return NGX_ERROR; return NGX_ERROR;
} else { } else if (rc == NGX_ABORT) {
/* NGX_DECLINED */ /* token cannot be decrypted */
if (conf->retry) { return ngx_quic_send_early_cc(c, pkt,
return ngx_quic_send_retry(c); NGX_QUIC_ERR_INVALID_TOKEN,
"cannot decrypt token");
} else if (rc == NGX_DECLINED) {
/* token is invalid */
if (pkt->retried) {
/* invalid Retry token */
return ngx_quic_send_early_cc(c, pkt,
NGX_QUIC_ERR_INVALID_TOKEN,
"invalid token");
} else if (conf->retry) {
/* invalid NEW_TOKEN */
return ngx_quic_send_retry(c, conf, pkt);
} }
} }
/* NGX_OK */
} else if (conf->retry) { } else if (conf->retry) {
return ngx_quic_send_retry(c); return ngx_quic_send_retry(c, conf, pkt);
} else {
pkt->odcid = pkt->dcid;
} }
if (ngx_quic_init_secrets(c) != NGX_OK) { if (ngx_terminate || ngx_exiting) {
if (conf->retry) {
return ngx_quic_send_retry(c, conf, pkt);
}
return NGX_ERROR; return NGX_ERROR;
} }
if (ngx_quic_insert_server_id(c, &qc->odcid) == NULL) { c->log->action = "creating quic connection";
return NGX_ERROR;
}
qc->server_seqnum = 0; qc = ngx_quic_new_connection(c, conf, pkt);
if (qc == NULL) {
if (ngx_quic_insert_server_id(c, &qc->dcid) == NULL) {
return NGX_ERROR; return NGX_ERROR;
} }
@ -2411,19 +2431,76 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf,
static ngx_int_t static ngx_int_t
ngx_quic_init_secrets(ngx_connection_t *c) ngx_quic_send_early_cc(ngx_connection_t *c, ngx_quic_header_t *inpkt,
ngx_uint_t err, const char *reason)
{ {
ngx_quic_connection_t *qc; ssize_t len;
ngx_str_t res;
ngx_quic_frame_t frame;
ngx_quic_header_t pkt;
qc = ngx_quic_get_connection(c); static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
if (ngx_quic_keys_set_initial_secret(c->pool, qc->keys, &qc->odcid) ngx_memzero(&frame, sizeof(ngx_quic_frame_t));
ngx_memzero(&pkt, sizeof(ngx_quic_header_t));
frame.level = inpkt->level;
frame.type = NGX_QUIC_FT_CONNECTION_CLOSE;
frame.u.close.error_code = err;
frame.u.close.reason.data = (u_char *) reason;
frame.u.close.reason.len = ngx_strlen(reason);
len = ngx_quic_create_frame(NULL, &frame);
if (len > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) {
return NGX_ERROR;
}
ngx_quic_log_frame(c->log, &frame, 1);
len = ngx_quic_create_frame(src, &frame);
if (len == -1) {
return NGX_ERROR;
}
pkt.keys = ngx_quic_keys_new(c->pool);
if (pkt.keys == NULL) {
return NGX_ERROR;
}
if (ngx_quic_keys_set_initial_secret(c->pool, pkt.keys, &inpkt->dcid)
!= NGX_OK) != NGX_OK)
{ {
return NGX_ERROR; return NGX_ERROR;
} }
qc->initialized = 1; pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG
| NGX_QUIC_PKT_INITIAL;
pkt.num_len = 1;
/*
* pkt.num = 0;
* pkt.trunc = 0;
*/
pkt.version = inpkt->version;
pkt.log = c->log;
pkt.level = inpkt->level;
pkt.dcid = inpkt->scid;
pkt.scid = inpkt->dcid;
pkt.payload.data = src;
pkt.payload.len = len;
res.data = dst;
if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) {
return NGX_ERROR;
}
if (ngx_quic_send(c, res.data, res.len) == NGX_ERROR) {
return NGX_ERROR;
}
return NGX_OK; return NGX_OK;
} }
@ -3156,13 +3233,6 @@ ngx_quic_send_cc(ngx_connection_t *c)
return NGX_OK; return NGX_OK;
} }
if (!qc->initialized) {
/* try to initialize secrets to send an early error */
if (ngx_quic_init_secrets(c) != NGX_OK) {
return NGX_OK;
}
}
if (qc->closing if (qc->closing
&& ngx_current_msec - qc->last_cc < NGX_QUIC_CC_MIN_INTERVAL) && ngx_current_msec - qc->last_cc < NGX_QUIC_CC_MIN_INTERVAL)
{ {
@ -3197,6 +3267,7 @@ ngx_quic_send_cc(ngx_connection_t *c)
static ngx_int_t static ngx_int_t
ngx_quic_send_new_token(ngx_connection_t *c) ngx_quic_send_new_token(ngx_connection_t *c)
{ {
time_t expires;
ngx_str_t token; ngx_str_t token;
ngx_quic_frame_t *frame; ngx_quic_frame_t *frame;
ngx_quic_connection_t *qc; ngx_quic_connection_t *qc;
@ -3207,7 +3278,11 @@ ngx_quic_send_new_token(ngx_connection_t *c)
return NGX_OK; return NGX_OK;
} }
if (ngx_quic_new_token(c, &token) != NGX_OK) { expires = ngx_time() + NGX_QUIC_NEW_TOKEN_LIFETIME;
if (ngx_quic_new_token(c, qc->conf->token_key, &token, NULL, expires, 0)
!= NGX_OK)
{
return NGX_ERROR; return NGX_ERROR;
} }

View File

@ -29,12 +29,12 @@
#define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25 #define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25
#define NGX_QUIC_DEFAULT_SRT_KEY_LEN 32 #define NGX_QUIC_DEFAULT_SRT_KEY_LEN 32
#define NGX_QUIC_RETRY_TIMEOUT 3000 #define NGX_QUIC_RETRY_LIFETIME 3 /* seconds */
#define NGX_QUIC_RETRY_LIFETIME 30000 #define NGX_QUIC_NEW_TOKEN_LIFETIME 600 /* seconds */
#define NGX_QUIC_RETRY_BUFFER_SIZE 128 #define NGX_QUIC_RETRY_BUFFER_SIZE 256
/* 1 flags + 4 version + 3 x (1 + 20) s/o/dcid + itag + token(44) */ /* 1 flags + 4 version + 3 x (1 + 20) s/o/dcid + itag + token(64) */
#define NGX_QUIC_MAX_TOKEN_SIZE 32 #define NGX_QUIC_MAX_TOKEN_SIZE 64
/* sizeof(struct in6_addr) + sizeof(ngx_msec_t) up to AES-256 block size */ /* SHA-1(addr)=20 + sizeof(time_t) + retry(1) + odcid.len(1) + odcid */
/* quic-recovery, section 6.2.2, kInitialRtt */ /* quic-recovery, section 6.2.2, kInitialRtt */
#define NGX_QUIC_INITIAL_RTT 333 /* ms */ #define NGX_QUIC_INITIAL_RTT 333 /* ms */

View File

@ -325,6 +325,8 @@ typedef struct {
unsigned key_update:1; unsigned key_update:1;
unsigned parsed:1; unsigned parsed:1;
unsigned decrypted:1; unsigned decrypted:1;
unsigned validated:1;
unsigned retried:1;
} ngx_quic_header_t; } ngx_quic_header_t;

View File

@ -346,10 +346,8 @@ ngx_http_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
ngx_conf_merge_value(conf->retry, prev->retry, 0); ngx_conf_merge_value(conf->retry, prev->retry, 0);
if (conf->retry) { if (RAND_bytes(conf->token_key, sizeof(conf->token_key)) <= 0) {
if (RAND_bytes(conf->token_key, sizeof(conf->token_key)) <= 0) { return NGX_CONF_ERROR;
return NGX_CONF_ERROR;
}
} }
ngx_conf_merge_str_value(conf->sr_token_key, prev->sr_token_key, ""); ngx_conf_merge_str_value(conf->sr_token_key, prev->sr_token_key, "");

View File

@ -305,10 +305,8 @@ ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
ngx_conf_merge_value(conf->retry, prev->retry, 0); ngx_conf_merge_value(conf->retry, prev->retry, 0);
if (conf->retry) { if (RAND_bytes(conf->token_key, sizeof(conf->token_key)) <= 0) {
if (RAND_bytes(conf->token_key, sizeof(conf->token_key)) <= 0) { return NGX_CONF_ERROR;
return NGX_CONF_ERROR;
}
} }
ngx_conf_merge_str_value(conf->sr_token_key, prev->sr_token_key, ""); ngx_conf_merge_str_value(conf->sr_token_key, prev->sr_token_key, "");