mirror of
https://github.com/nginx/nginx.git
synced 2025-02-25 18:55:26 -06:00
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:
parent
cd62534300
commit
89dda20510
@ -9,6 +9,7 @@
|
||||
#include <ngx_event.h>
|
||||
#include <ngx_event_quic_transport.h>
|
||||
#include <ngx_event_quic_protection.h>
|
||||
#include <ngx_sha1.h>
|
||||
|
||||
|
||||
/* 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 dcid; /* server (our own) ID */
|
||||
ngx_str_t odcid; /* original server ID */
|
||||
ngx_str_t token;
|
||||
|
||||
struct sockaddr *sockaddr;
|
||||
socklen_t socklen;
|
||||
@ -175,8 +175,6 @@ typedef struct {
|
||||
unsigned closing:1;
|
||||
unsigned draining:1;
|
||||
unsigned key_phase:1;
|
||||
unsigned in_retry:1;
|
||||
unsigned initialized:1;
|
||||
unsigned validated:1;
|
||||
} 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)
|
||||
static ngx_int_t ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id);
|
||||
#endif
|
||||
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);
|
||||
static ngx_int_t ngx_quic_send_retry(ngx_connection_t *c,
|
||||
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,
|
||||
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_inline size_t ngx_quic_max_udp_payload(ngx_connection_t *c);
|
||||
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);
|
||||
static ngx_int_t ngx_quic_process_packet(ngx_connection_t *c,
|
||||
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,
|
||||
enum ssl_encryption_level_t level);
|
||||
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->draining ? " draining" : "");
|
||||
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" : "");
|
||||
|
||||
} else {
|
||||
@ -1014,12 +1016,16 @@ ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf)
|
||||
|
||||
qc = ngx_quic_get_connection(c);
|
||||
|
||||
ngx_add_timer(c->read, qc->in_retry ? NGX_QUIC_RETRY_TIMEOUT
|
||||
: qc->tp.max_idle_timeout);
|
||||
if (qc == NULL) {
|
||||
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;
|
||||
|
||||
ngx_quic_connstate_dbg(c);
|
||||
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.recovery_start = ngx_current_msec;
|
||||
|
||||
qc->odcid.len = pkt->dcid.len;
|
||||
qc->odcid.data = ngx_pstrdup(c->pool, &pkt->dcid);
|
||||
qc->odcid.len = pkt->odcid.len;
|
||||
qc->odcid.data = ngx_pstrdup(c->pool, &pkt->odcid);
|
||||
if (qc->odcid.data == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
@ -1144,12 +1150,19 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf,
|
||||
#endif
|
||||
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.data = ngx_pnalloc(c->pool, qc->scid.len);
|
||||
qc->scid.data = ngx_pstrdup(c->pool, &pkt->scid);
|
||||
if (qc->scid.data == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
ngx_memcpy(qc->scid.data, pkt->scid.data, qc->scid.len);
|
||||
|
||||
cid = ngx_quic_alloc_client_id(c, qc);
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -1344,27 +1377,41 @@ ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id)
|
||||
|
||||
|
||||
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;
|
||||
ngx_str_t res, token;
|
||||
ngx_quic_header_t pkt;
|
||||
ngx_quic_connection_t *qc;
|
||||
u_char buf[NGX_QUIC_RETRY_BUFFER_SIZE];
|
||||
time_t expires;
|
||||
ssize_t len;
|
||||
ngx_str_t res, token;
|
||||
ngx_quic_header_t pkt;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ngx_memzero(&pkt, sizeof(ngx_quic_header_t));
|
||||
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.odcid = qc->odcid;
|
||||
pkt.dcid = qc->scid;
|
||||
pkt.scid = qc->dcid;
|
||||
|
||||
pkt.odcid = inpkt->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;
|
||||
|
||||
res.data = buf;
|
||||
@ -1383,71 +1430,46 @@ ngx_quic_send_retry(ngx_connection_t *c)
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
qc->token = token;
|
||||
#if (NGX_QUIC_DRAFT_VERSION < 28)
|
||||
qc->tp.original_dcid = qc->odcid;
|
||||
#endif
|
||||
qc->tp.retry_scid = qc->dcid;
|
||||
qc->in_retry = 1;
|
||||
ngx_log_debug(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"quic retry packet sent to %xV", &pkt.dcid);
|
||||
|
||||
if (ngx_quic_insert_server_id(c, &qc->dcid) == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
/*
|
||||
* 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_DONE;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
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
|
||||
ngx_quic_connection_t *qc;
|
||||
u_char in[NGX_QUIC_MAX_TOKEN_SIZE];
|
||||
int len, iv_len;
|
||||
u_char *p, *iv;
|
||||
EVP_CIPHER_CTX *ctx;
|
||||
const EVP_CIPHER *cipher;
|
||||
|
||||
switch (c->sockaddr->sa_family) {
|
||||
u_char in[NGX_QUIC_MAX_TOKEN_SIZE];
|
||||
|
||||
#if (NGX_HAVE_INET6)
|
||||
case AF_INET6:
|
||||
sin6 = (struct sockaddr_in6 *) c->sockaddr;
|
||||
ngx_quic_address_hash(c, !is_retry, in);
|
||||
|
||||
len = sizeof(struct in6_addr);
|
||||
data = sin6->sin6_addr.s6_addr;
|
||||
p = in + 20;
|
||||
|
||||
break;
|
||||
#endif
|
||||
p = ngx_cpymem(p, &exp, sizeof(time_t));
|
||||
|
||||
#if (NGX_HAVE_UNIX_DOMAIN)
|
||||
case AF_UNIX:
|
||||
*p++ = is_retry ? 1 : 0;
|
||||
|
||||
len = ngx_min(c->addr_text.len, NGX_QUIC_MAX_TOKEN_SIZE - sizeof(now));
|
||||
data = c->addr_text.data;
|
||||
if (odcid) {
|
||||
*p++ = odcid->len;
|
||||
p = ngx_cpymem(p, odcid->data, odcid->len);
|
||||
|
||||
break;
|
||||
#endif
|
||||
|
||||
default: /* AF_INET */
|
||||
sin = (struct sockaddr_in *) c->sockaddr;
|
||||
|
||||
len = sizeof(in_addr_t);
|
||||
data = (u_char *) &sin->sin_addr;
|
||||
|
||||
break;
|
||||
} else {
|
||||
*p++ = 0;
|
||||
}
|
||||
|
||||
p = ngx_cpymem(in, data, len);
|
||||
|
||||
now = ngx_current_msec;
|
||||
len += sizeof(now);
|
||||
ngx_memcpy(p, &now, sizeof(now));
|
||||
len = p - in;
|
||||
|
||||
cipher = EVP_aes_256_cbc();
|
||||
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;
|
||||
}
|
||||
|
||||
qc = ngx_quic_get_connection(c);
|
||||
key = qc->conf->token_key;
|
||||
iv = token->data;
|
||||
|
||||
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
|
||||
ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt)
|
||||
static void
|
||||
ngx_quic_address_hash(ngx_connection_t *c, ngx_uint_t no_port, u_char buf[20])
|
||||
{
|
||||
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;
|
||||
size_t len;
|
||||
u_char *data;
|
||||
ngx_sha1_t sha1;
|
||||
struct sockaddr_in *sin;
|
||||
#if (NGX_HAVE_INET6)
|
||||
struct sockaddr_in6 *sin6;
|
||||
struct sockaddr_in6 *sin6;
|
||||
#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 (pkt->token.len != qc->token.len) {
|
||||
goto bad_token;
|
||||
#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
|
||||
|
||||
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();
|
||||
key = qc->conf->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)) {
|
||||
goto bad_token;
|
||||
goto garbage;
|
||||
}
|
||||
|
||||
if (pkt->token.len > (size_t) iv_len + NGX_QUIC_MAX_TOKEN_SIZE) {
|
||||
goto bad_token;
|
||||
goto garbage;
|
||||
}
|
||||
|
||||
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) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
goto bad_token;
|
||||
goto garbage;
|
||||
}
|
||||
total = len;
|
||||
|
||||
if (EVP_DecryptFinal_ex(ctx, tdec + len, &tlen) <= 0) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
goto bad_token;
|
||||
goto garbage;
|
||||
}
|
||||
total += tlen;
|
||||
|
||||
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 (total < (20 + sizeof(time_t) + 2)) {
|
||||
goto garbage;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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");
|
||||
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;
|
||||
|
||||
garbage:
|
||||
|
||||
ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic garbage token");
|
||||
|
||||
return NGX_ABORT;
|
||||
|
||||
bad_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;
|
||||
}
|
||||
|
||||
@ -1817,8 +1878,10 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc)
|
||||
qc = ngx_quic_get_connection(c);
|
||||
|
||||
if (qc == NULL) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
||||
"quic close connection early error");
|
||||
if (rc == NGX_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) {
|
||||
return;
|
||||
@ -2101,6 +2164,11 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf)
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (rc == NGX_DONE) {
|
||||
/* stop further processing */
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
if (rc == NGX_OK) {
|
||||
good = 1;
|
||||
}
|
||||
@ -2216,60 +2284,6 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf,
|
||||
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 {
|
||||
|
||||
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) {
|
||||
|
||||
c->log->action = "creating quic connection";
|
||||
c->log->action = "processing initial packet";
|
||||
|
||||
if (pkt->dcid.len < NGX_QUIC_CID_LEN_MIN) {
|
||||
/* 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;
|
||||
}
|
||||
|
||||
qc = ngx_quic_new_connection(c, conf, pkt);
|
||||
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;
|
||||
}
|
||||
/* process retry and initialize connection IDs */
|
||||
|
||||
if (pkt->token.len) {
|
||||
rc = ngx_quic_validate_token(c, pkt);
|
||||
|
||||
if (rc == NGX_OK) {
|
||||
qc->validated = 1;
|
||||
rc = ngx_quic_validate_token(c, conf->token_key, pkt);
|
||||
|
||||
} else if (rc == NGX_ERROR) {
|
||||
if (rc == NGX_ERROR) {
|
||||
/* internal error */
|
||||
return NGX_ERROR;
|
||||
|
||||
} else {
|
||||
/* NGX_DECLINED */
|
||||
if (conf->retry) {
|
||||
return ngx_quic_send_retry(c);
|
||||
} else if (rc == NGX_ABORT) {
|
||||
/* token cannot be decrypted */
|
||||
return ngx_quic_send_early_cc(c, pkt,
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
if (ngx_quic_insert_server_id(c, &qc->odcid) == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
c->log->action = "creating quic connection";
|
||||
|
||||
qc->server_seqnum = 0;
|
||||
|
||||
if (ngx_quic_insert_server_id(c, &qc->dcid) == NULL) {
|
||||
qc = ngx_quic_new_connection(c, conf, pkt);
|
||||
if (qc == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
@ -2411,19 +2431,76 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf,
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
@ -3156,13 +3233,6 @@ ngx_quic_send_cc(ngx_connection_t *c)
|
||||
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
|
||||
&& 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
|
||||
ngx_quic_send_new_token(ngx_connection_t *c)
|
||||
{
|
||||
time_t expires;
|
||||
ngx_str_t token;
|
||||
ngx_quic_frame_t *frame;
|
||||
ngx_quic_connection_t *qc;
|
||||
@ -3207,7 +3278,11 @@ ngx_quic_send_new_token(ngx_connection_t *c)
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -29,12 +29,12 @@
|
||||
#define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25
|
||||
#define NGX_QUIC_DEFAULT_SRT_KEY_LEN 32
|
||||
|
||||
#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_RETRY_LIFETIME 3 /* seconds */
|
||||
#define NGX_QUIC_NEW_TOKEN_LIFETIME 600 /* seconds */
|
||||
#define NGX_QUIC_RETRY_BUFFER_SIZE 256
|
||||
/* 1 flags + 4 version + 3 x (1 + 20) s/o/dcid + itag + token(64) */
|
||||
#define NGX_QUIC_MAX_TOKEN_SIZE 64
|
||||
/* SHA-1(addr)=20 + sizeof(time_t) + retry(1) + odcid.len(1) + odcid */
|
||||
|
||||
/* quic-recovery, section 6.2.2, kInitialRtt */
|
||||
#define NGX_QUIC_INITIAL_RTT 333 /* ms */
|
||||
|
@ -325,6 +325,8 @@ typedef struct {
|
||||
unsigned key_update:1;
|
||||
unsigned parsed:1;
|
||||
unsigned decrypted:1;
|
||||
unsigned validated:1;
|
||||
unsigned retried:1;
|
||||
} ngx_quic_header_t;
|
||||
|
||||
|
||||
|
@ -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);
|
||||
|
||||
if (conf->retry) {
|
||||
if (RAND_bytes(conf->token_key, sizeof(conf->token_key)) <= 0) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
if (RAND_bytes(conf->token_key, sizeof(conf->token_key)) <= 0) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
ngx_conf_merge_str_value(conf->sr_token_key, prev->sr_token_key, "");
|
||||
|
@ -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);
|
||||
|
||||
if (conf->retry) {
|
||||
if (RAND_bytes(conf->token_key, sizeof(conf->token_key)) <= 0) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
if (RAND_bytes(conf->token_key, sizeof(conf->token_key)) <= 0) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
ngx_conf_merge_str_value(conf->sr_token_key, prev->sr_token_key, "");
|
||||
|
Loading…
Reference in New Issue
Block a user