Added functions to decrypt long packets.

This commit is contained in:
Vladimir Homutov 2020-03-05 17:18:33 +03:00
parent 12fc8a8bac
commit 32b2728ebb

View File

@ -185,9 +185,22 @@ typedef struct {
ngx_uint_t type; ngx_uint_t type;
ngx_uint_t *number; ngx_uint_t *number;
ngx_uint_t flags; ngx_uint_t flags;
ngx_uint_t version; uint32_t version;
ngx_str_t *token; ngx_str_t token;
ngx_quic_level_t level; ngx_quic_level_t level;
/* filled in by parser */
ngx_str_t buf; /* quic packet from wire */
u_char *pos; /* current parser position */
/* cleartext fields */
ngx_str_t dcid;
ngx_str_t scid;
uint64_t pn;
ngx_str_t payload; /* decrypted payload */
} ngx_quic_header_t; } ngx_quic_header_t;
@ -210,6 +223,15 @@ static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn);
static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn,
enum ssl_encryption_level_t level, uint8_t alert); enum ssl_encryption_level_t level, uint8_t alert);
static ngx_int_t ngx_quic_process_long_header(ngx_connection_t *c,
ngx_quic_header_t *pkt);
static ngx_int_t ngx_quic_process_initial_header(ngx_connection_t *c,
ngx_quic_header_t *pkt);
static ngx_int_t ngx_quic_process_handshake_header(ngx_connection_t *c,
ngx_quic_header_t *pkt);
static ngx_int_t ngx_quic_initial_secret(ngx_connection_t *c);
static ngx_int_t ngx_quic_decrypt(ngx_connection_t *c, ngx_quic_header_t *pkt);
static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask); static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask);
static uint64_t ngx_quic_parse_int(u_char **pos); static uint64_t ngx_quic_parse_int(u_char **pos);
static void ngx_quic_build_int(u_char **pos, uint64_t value); static void ngx_quic_build_int(u_char **pos, uint64_t value);
@ -287,7 +309,7 @@ ngx_quic_send_packet(ngx_connection_t *c, ngx_quic_connection_t *qc,
pkt.number = &qc->initial_pn; pkt.number = &qc->initial_pn;
pkt.flags = NGX_QUIC_PKT_INITIAL; pkt.flags = NGX_QUIC_PKT_INITIAL;
pkt.secret = &qc->server_in; pkt.secret = &qc->server_in;
pkt.token = &initial_token; pkt.token = initial_token;
if (ngx_quic_create_long_packet(c, c->ssl->connection, if (ngx_quic_create_long_packet(c, c->ssl->connection,
&pkt, payload, &res) &pkt, payload, &res)
@ -599,8 +621,8 @@ ngx_quic_create_long_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn,
*p++ = qc->dcid.len; *p++ = qc->dcid.len;
p = ngx_cpymem(p, qc->dcid.data, qc->dcid.len); p = ngx_cpymem(p, qc->dcid.data, qc->dcid.len);
if (pkt->token) { // if pkt->flags & initial ? if (pkt->level == ssl_encryption_initial) {
ngx_quic_build_int(&p, pkt->token->len); ngx_quic_build_int(&p, pkt->token.len);
} }
ngx_quic_build_int(&p, out.len + 1); // length (inc. pnl) ngx_quic_build_int(&p, out.len + 1); // length (inc. pnl)
@ -865,90 +887,114 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level,
static ngx_int_t static ngx_int_t
ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) ngx_quic_process_long_header(ngx_connection_t *c, ngx_quic_header_t *pkt)
{ {
int n, sslerr; u_char *p;
ngx_quic_connection_t *qc;
if ((b->pos[0] & 0xf0) != NGX_QUIC_PKT_INITIAL) { p = pkt->buf.data;
ngx_log_error(NGX_LOG_INFO, c->log, 0, "invalid initial packet");
ngx_quic_hexdump0(c->log, "input", pkt->buf.data, pkt->buf.len);
if (!(p[0] & NGX_QUIC_PKT_LONG)) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "not a long packet");
return NGX_ERROR; return NGX_ERROR;
} }
if (ngx_buf_size(b) < 1200) { pkt->flags = *p++;
ngx_log_error(NGX_LOG_INFO, c->log, 0, "too small UDP datagram");
return NGX_ERROR;
}
ngx_int_t flags = *b->pos++; pkt->version = ngx_quic_parse_uint32(p);
uint32_t version = ngx_quic_parse_uint32(b->pos); p += sizeof(uint32_t);
b->pos += sizeof(uint32_t);
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic flags:%xi version:%xD", flags, version); "quic flags:%xi version:%xD", pkt->flags, pkt->version);
if (version != quic_version) { if (pkt->version != quic_version) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "unsupported quic version"); ngx_log_error(NGX_LOG_INFO, c->log, 0, "unsupported quic version");
return NGX_ERROR; return NGX_ERROR;
} }
qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); pkt->dcid.len = *p++;
if (qc == NULL) { pkt->dcid.data = p;
return NGX_ERROR; p += pkt->dcid.len;
}
c->quic = qc; pkt->scid.len = *p++;
pkt->scid.data = p;
p += pkt->scid.len;
qc->dcid.len = *b->pos++; pkt->pos = p;
qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len);
if (qc->dcid.data == NULL) {
return NGX_ERROR;
}
ngx_memcpy(qc->dcid.data, b->pos, qc->dcid.len); return NGX_OK;
b->pos += qc->dcid.len; }
qc->scid.len = *b->pos++;
qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len);
if (qc->scid.data == NULL) {
return NGX_ERROR;
}
ngx_memcpy(qc->scid.data, b->pos, qc->scid.len); static ngx_int_t
b->pos += qc->scid.len; ngx_quic_process_initial_header(ngx_connection_t *c, ngx_quic_header_t *pkt)
{
u_char *p;
ngx_int_t plen;
qc->token.len = ngx_quic_parse_int(&b->pos); p = pkt->pos;
qc->token.data = ngx_pnalloc(c->pool, qc->token.len);
if (qc->token.data == NULL) {
return NGX_ERROR;
}
ngx_memcpy(qc->token.data, b->pos, qc->token.len); pkt->token.len = ngx_quic_parse_int(&p);
b->pos += qc->token.len; pkt->token.data = p;
ngx_int_t plen = ngx_quic_parse_int(&b->pos); p += pkt->token.len;
if (plen > b->last - b->pos) { plen = ngx_quic_parse_int(&p);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic packet length: %d", plen);
if (plen > pkt->buf.data + pkt->buf.len - p) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated initial packet"); ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated initial packet");
return NGX_ERROR; return NGX_ERROR;
} }
/* draft-ietf-quic-tls-23#section-5.4.2: pkt->pos = p;
* the Packet Number field is assumed to be 4 bytes long pkt->buf.len = plen;
* draft-ietf-quic-tls-23#section-5.4.[34]:
* AES-Based and ChaCha20-Based header protections sample 16 bytes ngx_quic_hexdump0(c->log, "DCID", pkt->dcid.data, pkt->dcid.len);
*/ ngx_quic_hexdump0(c->log, "SCID", pkt->scid.data, pkt->scid.len);
u_char *sample = b->pos + 4; ngx_quic_hexdump0(c->log, "token", pkt->token.data, pkt->token.len);
ngx_quic_hexdump0(c->log, "DCID", qc->dcid.data, qc->dcid.len);
ngx_quic_hexdump0(c->log, "SCID", qc->scid.data, qc->scid.len);
ngx_quic_hexdump0(c->log, "token", qc->token.data, qc->token.len);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic packet length: %d", plen); "quic packet length: %d", plen);
ngx_quic_hexdump0(c->log, "sample", sample, 16);
return NGX_OK;
}
static ngx_int_t
ngx_quic_process_handshake_header(ngx_connection_t *c, ngx_quic_header_t *pkt)
{
u_char *p;
ngx_int_t plen;
p = pkt->pos;
plen = ngx_quic_parse_int(&p);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic packet length: %d", plen);
if (plen > pkt->buf.data + pkt->buf.len - p) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated handshake packet");
return NGX_ERROR;
}
pkt->pos = p;
pkt->buf.len = plen;
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic packet length: %d", plen);
return NGX_OK;
}
// initial secret static ngx_int_t
ngx_quic_initial_secret(ngx_connection_t *c)
{
ngx_quic_connection_t *qc = c->quic;
size_t is_len; size_t is_len;
uint8_t is[SHA256_DIGEST_LENGTH]; uint8_t is[SHA256_DIGEST_LENGTH];
@ -1047,57 +1093,176 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b)
} }
} }
// header protection return NGX_OK;
}
uint8_t mask[16];
if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), &qc->client_in, mask, sample) static ngx_int_t
ngx_quic_decrypt(ngx_connection_t *c, ngx_quic_header_t *pkt)
{
u_char clearflags, *p, *sample;
uint8_t *nonce;
uint64_t pn;
ngx_int_t pnl, rc;
ngx_str_t in, ad;
const EVP_CIPHER *cipher;
uint8_t mask[16];
p = pkt->pos;
/* draft-ietf-quic-tls-23#section-5.4.2:
* the Packet Number field is assumed to be 4 bytes long
* draft-ietf-quic-tls-23#section-5.4.[34]:
* AES-Based and ChaCha20-Based header protections sample 16 bytes
*/
sample = p + 4;
ngx_quic_hexdump0(c->log, "sample", sample, 16);
/* header protection */
if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), pkt->secret, mask, sample)
!= NGX_OK) != NGX_OK)
{ {
return NGX_ERROR; return NGX_ERROR;
} }
u_char clearflags = flags ^ (mask[0] & 0x0f); clearflags = pkt->flags ^ (mask[0] & 0x0f);
ngx_int_t pnl = (clearflags & 0x03) + 1; pnl = (clearflags & 0x03) + 1;
uint64_t pn = ngx_quic_parse_pn(&b->pos, pnl, &mask[1]); pn = ngx_quic_parse_pn(&p, pnl, &mask[1]);
pkt->pn = pn;
ngx_quic_hexdump0(c->log, "sample", sample, 16);
ngx_quic_hexdump0(c->log, "mask", mask, 5); ngx_quic_hexdump0(c->log, "mask", mask, 5);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic clear flags: %xi", clearflags);
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic packet number: %uL, len: %xi", pn, pnl); "quic packet number: %uL, len: %xi", pn, pnl);
// packet protection /* packet protection */
ngx_str_t in; in.data = p;
in.data = b->pos; in.len = pkt->buf.len - pnl;
in.len = plen - pnl;
ngx_str_t ad; ad.len = p - pkt->buf.data;;
ad.len = b->pos - b->start;
ad.data = ngx_pnalloc(c->pool, ad.len); ad.data = ngx_pnalloc(c->pool, ad.len);
if (ad.data == NULL) { if (ad.data == NULL) {
return NGX_ERROR; return NGX_ERROR;
} }
ngx_memcpy(ad.data, b->start, ad.len); ngx_memcpy(ad.data, pkt->buf.data, ad.len);
ad.data[0] = clearflags; ad.data[0] = clearflags;
ad.data[ad.len - pnl] = (u_char)pn; ad.data[ad.len - pnl] = (u_char) pn;
uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_in.iv); nonce = ngx_pstrdup(c->pool, &pkt->secret->iv);
nonce[11] ^= pn; nonce[11] ^= pn;
ngx_quic_hexdump0(c->log, "nonce", nonce, 12); ngx_quic_hexdump0(c->log, "nonce", nonce, 12);
ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len); ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len);
ngx_str_t out; if (c->ssl) {
switch (SSL_CIPHER_get_id(SSL_get_current_cipher(c->ssl->connection)) & 0xffff) {
if (ngx_quic_tls_open(c, EVP_aes_128_gcm(), &qc->client_in, &out, nonce, case NGX_AES_128_GCM_SHA256:
&in, &ad) cipher = EVP_aes_128_gcm();
!= NGX_OK) break;
{ case NGX_AES_256_GCM_SHA384:
cipher = EVP_aes_256_gcm();
break;
default:
ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher");
return NGX_ERROR;
}
} else {
/* initial packets */
cipher = EVP_aes_128_gcm();
}
rc = ngx_quic_tls_open(c, cipher, pkt->secret, &pkt->payload,
nonce, &in, &ad);
ngx_quic_hexdump0(c->log, "packet payload",
pkt->payload.data, pkt->payload.len);
return rc;
}
static ngx_int_t
ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b)
{
int n, sslerr;
ngx_str_t out;
ngx_quic_connection_t *qc;
ngx_quic_header_t pkt = { 0 };
pkt.buf.data = b->start;
pkt.buf.len = b->last - b->pos;
if (ngx_buf_size(b) < 1200) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "too small UDP datagram");
return NGX_ERROR; return NGX_ERROR;
} }
ngx_quic_hexdump0(c->log, "packet payload", out.data, out.len); if (ngx_quic_process_long_header(c, &pkt) != NGX_OK) {
return NGX_ERROR;
}
if ((pkt.flags & 0xf0) != NGX_QUIC_PKT_INITIAL) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"invalid initial packet: 0x%x", pkt.flags);
return NGX_ERROR;
}
if (ngx_quic_process_initial_header(c, &pkt) != NGX_OK) {
return NGX_ERROR;
}
qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t));
if (qc == NULL) {
return NGX_ERROR;
}
c->quic = qc;
qc->dcid.len = pkt.dcid.len;
qc->dcid.data = ngx_pnalloc(c->pool, pkt.dcid.len);
if (qc->dcid.data == NULL) {
return NGX_ERROR;
}
ngx_memcpy(qc->dcid.data, pkt.dcid.data, qc->dcid.len);
qc->scid.len = pkt.scid.len;
qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len);
if (qc->scid.data == NULL) {
return NGX_ERROR;
}
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);
if (ngx_quic_initial_secret(c) != NGX_OK) {
return NGX_ERROR;
}
pkt.secret = &qc->client_in;
if (ngx_quic_decrypt(c, &pkt) != NGX_OK) {
return NGX_ERROR;
}
out = pkt.payload;
if (out.data[0] != 0x06) { if (out.data[0] != 0x06) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, ngx_log_error(NGX_LOG_INFO, c->log, 0,
@ -1183,144 +1348,64 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b)
static ngx_int_t static ngx_int_t
ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb) ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *b)
{ {
int sslerr; int sslerr;
u_char *p, *b;
ssize_t n; ssize_t n;
ngx_str_t out; ngx_str_t out;
ngx_ssl_conn_t *ssl_conn; ngx_ssl_conn_t *ssl_conn;
const EVP_CIPHER *cipher;
ngx_quic_connection_t *qc; ngx_quic_connection_t *qc;
ngx_quic_header_t pkt = { 0 };
qc = c->quic; qc = c->quic;
ssl_conn = c->ssl->connection; ssl_conn = c->ssl->connection;
n = bb->last - bb->pos; pkt.buf.data = b->start;
p = bb->pos; pkt.buf.len = b->last - b->pos;
b = bb->start;
ngx_quic_hexdump0(c->log, "input", p, n); /* extract cleartext data into pkt */
if (ngx_quic_process_long_header(c, &pkt) != NGX_OK) {
if ((p[0] & 0xf0) != NGX_QUIC_PKT_HANDSHAKE) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "invalid packet type");
return NGX_ERROR; return NGX_ERROR;
} }
ngx_int_t flags = *p++; if (pkt.dcid.len != qc->dcid.len) {
uint32_t version = ngx_quic_parse_uint32(p);
p += sizeof(uint32_t);
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic flags:%xi version:%xD", flags, version);
if (version != quic_version) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "unsupported quic version");
return NGX_ERROR;
}
if (*p++ != qc->dcid.len) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcidl"); ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcidl");
return NGX_ERROR; return NGX_ERROR;
} }
if (ngx_memcmp(p, qc->dcid.data, qc->dcid.len) != 0) { if (ngx_memcmp(pkt.dcid.data, qc->dcid.data, qc->dcid.len) != 0) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcid"); ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcid");
return NGX_ERROR; return NGX_ERROR;
} }
p += qc->dcid.len; if (pkt.scid.len != qc->scid.len) {
if (*p++ != qc->scid.len) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scidl"); ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scidl");
return NGX_ERROR; return NGX_ERROR;
} }
if (ngx_memcmp(p, qc->scid.data, qc->scid.len) != 0) { if (ngx_memcmp(pkt.scid.data, qc->scid.data, qc->scid.len) != 0) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scid"); ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scid");
return NGX_ERROR; return NGX_ERROR;
} }
p += qc->scid.len; if ((pkt.flags & 0xf0) != NGX_QUIC_PKT_HANDSHAKE) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
ngx_int_t plen = ngx_quic_parse_int(&p); "invalid packet type: 0x%x", pkt.flags);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic packet length: %d", plen);
if (plen > b + n - p) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated handshake packet");
return NGX_ERROR; return NGX_ERROR;
} }
u_char *sample = p + 4; if (ngx_quic_process_handshake_header(c, &pkt) != NGX_OK) {
ngx_quic_hexdump0(c->log, "sample", sample, 16);
// header protection
uint8_t mask[16];
if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), &qc->client_hs, mask, sample)
!= NGX_OK)
{
return NGX_ERROR; return NGX_ERROR;
} }
u_char clearflags = flags ^ (mask[0] & 0x0f); pkt.secret = &qc->client_hs;
ngx_int_t pnl = (clearflags & 0x03) + 1;
uint64_t pn = ngx_quic_parse_pn(&p, pnl, &mask[1]);
ngx_quic_hexdump0(c->log, "mask", mask, 5); if (ngx_quic_decrypt(c, &pkt) != NGX_OK) {
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic clear flags: %xi", clearflags);
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic packet number: %uL, len: %xi", pn, pnl);
// packet protection
ngx_str_t in;
in.data = p;
in.len = plen - pnl;
ngx_str_t ad;
ad.len = p - b;
ad.data = ngx_pnalloc(c->pool, ad.len);
if (ad.data == NULL) {
return NGX_ERROR; return NGX_ERROR;
} }
ngx_memcpy(ad.data, b, ad.len); out = pkt.payload;
ad.data[0] = clearflags;
ad.data[ad.len - pnl] = (u_char)pn;
uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_hs.iv);
nonce[11] ^= pn;
ngx_quic_hexdump0(c->log, "nonce", nonce, 12);
ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len);
switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl_conn)) & 0xffff) {
case NGX_AES_128_GCM_SHA256:
cipher = EVP_aes_128_gcm();
break;
case NGX_AES_256_GCM_SHA384:
cipher = EVP_aes_256_gcm();
break;
default:
ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher");
return NGX_ERROR;
}
if (ngx_quic_tls_open(c, cipher, &qc->client_hs, &out, nonce, &in, &ad)
!= NGX_OK)
{
return NGX_ERROR;
}
ngx_quic_hexdump0(c->log, "packet payload", out.data, out.len);
if (out.data[0] != 0x06) { if (out.data[0] != 0x06) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, ngx_log_error(NGX_LOG_INFO, c->log, 0,
@ -1388,9 +1473,9 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb)
frame->level = ssl_encryption_handshake; frame->level = ssl_encryption_handshake;
frame->type = NGX_QUIC_FT_ACK; frame->type = NGX_QUIC_FT_ACK;
frame->u.ack.pn = pn; frame->u.ack.pn = pkt.pn;
ngx_sprintf(frame->info, "ACK for PN=%d at handshake level, in respond to client finished", pn); ngx_sprintf(frame->info, "ACK for PN=%d at handshake level, in respond to client finished", pkt.pn);
ngx_quic_queue_frame(qc, frame); ngx_quic_queue_frame(qc, frame);
if (ngx_quic_output(c) != NGX_OK) { if (ngx_quic_output(c) != NGX_OK) {