mirror of
https://github.com/nginx/nginx.git
synced 2025-02-25 18:55:26 -06:00
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.
This commit is contained in:
parent
d35eebede2
commit
ad2289e70e
@ -123,6 +123,7 @@ struct ngx_quic_connection_s {
|
|||||||
unsigned closing:1;
|
unsigned closing:1;
|
||||||
unsigned draining:1;
|
unsigned draining:1;
|
||||||
unsigned key_phase: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_quic_tp_t *tp, ngx_quic_header_t *pkt,
|
||||||
ngx_connection_handler_pt handler);
|
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_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 ngx_int_t ngx_quic_init_connection(ngx_connection_t *c);
|
||||||
static void ngx_quic_input_handler(ngx_event_t *rev);
|
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_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_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,
|
static ngx_int_t ngx_quic_initial_input(ngx_connection_t *c,
|
||||||
ngx_quic_header_t *pkt);
|
ngx_quic_header_t *pkt);
|
||||||
static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c,
|
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;
|
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;
|
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);
|
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];
|
keys = &c->quic->keys[ssl_encryption_initial];
|
||||||
|
|
||||||
if (ngx_quic_set_initial_secret(c->pool, &keys->client, &keys->server,
|
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;
|
return NGX_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tp->retry) {
|
||||||
|
return ngx_quic_retry(c);
|
||||||
|
}
|
||||||
|
|
||||||
pkt->secret = &keys->client;
|
pkt->secret = &keys->client;
|
||||||
pkt->level = ssl_encryption_initial;
|
pkt->level = ssl_encryption_initial;
|
||||||
pkt->plaintext = buf;
|
pkt->plaintext = buf;
|
||||||
@ -706,6 +711,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
|
static ngx_int_t
|
||||||
ngx_quic_init_connection(ngx_connection_t *c)
|
ngx_quic_init_connection(ngx_connection_t *c)
|
||||||
{
|
{
|
||||||
@ -776,6 +1045,7 @@ ngx_quic_input_handler(ngx_event_t *rev)
|
|||||||
b.start = buf;
|
b.start = buf;
|
||||||
b.end = buf + sizeof(buf);
|
b.end = buf + sizeof(buf);
|
||||||
b.pos = b.last = b.start;
|
b.pos = b.last = b.start;
|
||||||
|
b.memory = 1;
|
||||||
|
|
||||||
c = rev->data;
|
c = rev->data;
|
||||||
qc = c->quic;
|
qc = c->quic;
|
||||||
@ -1047,6 +1317,10 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b)
|
|||||||
pkt.log = c->log;
|
pkt.log = c->log;
|
||||||
pkt.flags = p[0];
|
pkt.flags = p[0];
|
||||||
|
|
||||||
|
if (c->quic->in_retry) {
|
||||||
|
return ngx_quic_retry_input(c, &pkt);
|
||||||
|
}
|
||||||
|
|
||||||
/* TODO: check current state */
|
/* TODO: check current state */
|
||||||
if (ngx_quic_long_pkt(pkt.flags)) {
|
if (ngx_quic_long_pkt(pkt.flags)) {
|
||||||
|
|
||||||
@ -1110,6 +1384,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
|
static ngx_int_t
|
||||||
ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt)
|
ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt)
|
||||||
{
|
{
|
||||||
|
@ -23,6 +23,13 @@
|
|||||||
#define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3
|
#define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3
|
||||||
#define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25
|
#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_HARDCODED_PTO 1000 /* 1s, TODO: collect */
|
||||||
#define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */
|
#define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */
|
||||||
|
|
||||||
@ -49,9 +56,12 @@ typedef struct {
|
|||||||
ngx_uint_t ack_delay_exponent;
|
ngx_uint_t ack_delay_exponent;
|
||||||
ngx_uint_t disable_active_migration;
|
ngx_uint_t disable_active_migration;
|
||||||
ngx_uint_t active_connection_id_limit;
|
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 */
|
/* TODO */
|
||||||
ngx_uint_t original_connection_id;
|
|
||||||
u_char stateless_reset_token[16];
|
u_char stateless_reset_token[16];
|
||||||
void *preferred_address;
|
void *preferred_address;
|
||||||
} ngx_quic_tp_t;
|
} ngx_quic_tp_t;
|
||||||
|
@ -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);
|
ngx_ssl_conn_t *ssl_conn, ngx_str_t *res);
|
||||||
static ngx_int_t ngx_quic_create_short_packet(ngx_quic_header_t *pkt,
|
static ngx_int_t ngx_quic_create_short_packet(ngx_quic_header_t *pkt,
|
||||||
ngx_ssl_conn_t *ssl_conn, ngx_str_t *res);
|
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
|
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
|
static uint64_t
|
||||||
ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask,
|
ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask,
|
||||||
uint64_t *largest_pn)
|
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);
|
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);
|
return ngx_quic_create_long_packet(pkt, ssl_conn, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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_int_t
|
||||||
ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid)
|
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,
|
len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_IDLE_TIMEOUT,
|
||||||
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) {
|
if (pos == NULL) {
|
||||||
return len;
|
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,
|
ngx_quic_tp_vint(NGX_QUIC_TP_MAX_IDLE_TIMEOUT,
|
||||||
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;
|
return p - pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,6 +280,7 @@ typedef struct {
|
|||||||
size_t len;
|
size_t len;
|
||||||
|
|
||||||
/* cleartext fields */
|
/* cleartext fields */
|
||||||
|
ngx_str_t odcid; /* retry packet tag */
|
||||||
ngx_str_t dcid;
|
ngx_str_t dcid;
|
||||||
ngx_str_t scid;
|
ngx_str_t scid;
|
||||||
uint64_t pn;
|
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 ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out,
|
||||||
size_t pkt_len, u_char **pnp);
|
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_initial_header(ngx_quic_header_t *pkt);
|
||||||
ngx_int_t ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt);
|
ngx_int_t ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt);
|
||||||
|
|
||||||
|
@ -111,6 +111,13 @@ static ngx_command_t ngx_http_v3_commands[] = {
|
|||||||
offsetof(ngx_http_v3_srv_conf_t, quic.active_connection_id_limit),
|
offsetof(ngx_http_v3_srv_conf_t, quic.active_connection_id_limit),
|
||||||
&ngx_http_v3_active_connection_id_limit_bounds },
|
&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
|
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.disable_active_migration = NGX_CONF_UNSET_UINT;
|
||||||
v3cf->quic.active_connection_id_limit = NGX_CONF_UNSET_UINT;
|
v3cf->quic.active_connection_id_limit = NGX_CONF_UNSET_UINT;
|
||||||
|
|
||||||
|
v3cf->quic.retry = NGX_CONF_UNSET;
|
||||||
|
|
||||||
return v3cf;
|
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,
|
ngx_conf_merge_uint_value(conf->quic.active_connection_id_limit,
|
||||||
prev->quic.active_connection_id_limit, 2);
|
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;
|
return NGX_CONF_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user