# HG changeset patch # User Vladimir Homutov # Date 1589460885 -10800 # Node ID fb7422074258c84f4acad392638dcc2399d1d8fb # Parent 52d0c4832570283d1a5007fd564871e8d063e2be Added generation of CC frames with error on connection termination. When an error occurs, then c->quic->error field may be populated with an appropriate error code, and the CONNECTION CLOSE frame will be sent to the peer before the connection is closed. Otherwise, the error treated as internal and INTERNAL_ERROR code is sent. The pkt->error field is populated by functions processing packets to indicate an error when it does not fit into pass/fail return status. diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -34,6 +34,7 @@ typedef enum { + NGX_QUIC_ST_UNAVAIL, /* connection not ready */ NGX_QUIC_ST_INITIAL, /* connection just created */ NGX_QUIC_ST_HANDSHAKE, /* handshake started */ NGX_QUIC_ST_EARLY_DATA, /* handshake in progress */ @@ -119,6 +120,8 @@ struct ngx_quic_connection_s { uint64_t cur_streams; uint64_t max_streams; + ngx_uint_t error; + unsigned send_timer_set:1; unsigned closing:1; unsigned draining:1; @@ -405,6 +408,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn if (ngx_quic_parse_transport_params(p, end, &qc->ctp, c->log) != NGX_OK) { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; return NGX_ERROR; } @@ -417,6 +421,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn if (qc->ctp.max_packet_size < NGX_QUIC_MIN_INITIAL_SIZE || qc->ctp.max_packet_size > NGX_QUIC_DEFAULT_MAX_PACKET_SIZE) { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic maximum packet size is invalid"); return NGX_ERROR; @@ -580,8 +585,6 @@ ngx_quic_new_connection(ngx_connection_t return NGX_ERROR; } - qc->state = NGX_QUIC_ST_INITIAL; - ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel, ngx_quic_rbtree_insert_stream); @@ -608,6 +611,7 @@ ngx_quic_new_connection(ngx_connection_t qc->push.cancelable = 1; c->quic = qc; + qc->state = NGX_QUIC_ST_UNAVAIL; qc->ssl = ssl; qc->tp = *tp; qc->streams.handler = handler; @@ -644,6 +648,8 @@ ngx_quic_new_connection(ngx_connection_t return NGX_ERROR; } + qc->state = NGX_QUIC_ST_INITIAL; + if (pkt->token.len) { rc = ngx_quic_validate_token(c, pkt); @@ -896,10 +902,12 @@ ngx_quic_validate_token(ngx_connection_t if (qc->token.len) { if (pkt->token.len != qc->token.len) { + qc->error = NGX_QUIC_ERR_INVALID_TOKEN; return NGX_ERROR; } if (ngx_memcmp(pkt->token.data, qc->token.data, pkt->token.len) != 0) { + qc->error = NGX_QUIC_ERR_INVALID_TOKEN; return NGX_ERROR; } @@ -916,6 +924,7 @@ ngx_quic_validate_token(ngx_connection_t /* sanity checks */ if (pkt->token.len < (size_t) iv_len + EVP_CIPHER_block_size(cipher)) { + qc->error = NGX_QUIC_ERR_INVALID_TOKEN; return NGX_ERROR; } @@ -938,11 +947,13 @@ ngx_quic_validate_token(ngx_connection_t if (EVP_DecryptUpdate(ctx, tdec, &len, p, len) != 1) { EVP_CIPHER_CTX_free(ctx); + qc->error = NGX_QUIC_ERR_INVALID_TOKEN; return NGX_ERROR; } if (EVP_DecryptFinal_ex(ctx, tdec + len, &tlen) <= 0) { EVP_CIPHER_CTX_free(ctx); + qc->error = NGX_QUIC_ERR_INVALID_TOKEN; return NGX_ERROR; } @@ -979,6 +990,7 @@ ngx_quic_validate_token(ngx_connection_t } if (ngx_memcmp(tdec, data, len) != 0) { + qc->error = NGX_QUIC_ERR_INVALID_TOKEN; return NGX_ERROR; } @@ -1116,7 +1128,7 @@ ngx_quic_close_connection(ngx_connection ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_close_connection, rc: %i", rc); - if (!c->quic) { + if (!c->quic || c->quic->state == NGX_QUIC_ST_UNAVAIL) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic close connection early error"); @@ -1149,7 +1161,7 @@ ngx_quic_close_connection(ngx_connection static ngx_int_t ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) { - ngx_uint_t i; + ngx_uint_t i, err; ngx_quic_connection_t *qc; enum ssl_encryption_level_t level; @@ -1157,6 +1169,20 @@ ngx_quic_close_quic(ngx_connection_t *c, if (!qc->closing) { + switch (qc->state) { + case NGX_QUIC_ST_INITIAL: + level = ssl_encryption_initial; + break; + + case NGX_QUIC_ST_HANDSHAKE: + level = ssl_encryption_handshake; + break; + + default: /* NGX_QUIC_ST_APPLICATION/EARLY_DATA */ + level = ssl_encryption_application; + break; + } + if (rc == NGX_OK) { /* @@ -1168,20 +1194,6 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic immediate close, drain = %d", qc->draining); - switch (qc->state) { - case NGX_QUIC_ST_INITIAL: - level = ssl_encryption_initial; - break; - - case NGX_QUIC_ST_HANDSHAKE: - level = ssl_encryption_handshake; - break; - - default: /* NGX_QUIC_ST_APPLICATION/EARLY_DATA */ - level = ssl_encryption_application; - break; - } - if (ngx_quic_send_cc(c, level, NGX_QUIC_ERR_NO_ERROR) == NGX_OK) { qc->close.log = c->log; @@ -1206,8 +1218,12 @@ ngx_quic_close_quic(ngx_connection_t *c, qc->draining ? "drained" : "idle"); } else { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic immediate close due to fatal error"); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic immediate close due to fatal error: %ui", + qc->error); + + err = qc->error ? qc->error : NGX_QUIC_ERR_INTERNAL_ERROR; + (void) ngx_quic_send_cc(c, level, err); } qc->closing = 1; @@ -1749,14 +1765,8 @@ ngx_quic_payload_handler(ngx_connection_ len = ngx_quic_parse_frame(pkt, p, end, &frame); - if (len == NGX_DECLINED) { - /* TODO: handle protocol violation: - * such frame not allowed in this packet - */ - return NGX_ERROR; - } - if (len < 0) { + qc->error = pkt->error; return NGX_ERROR; } @@ -1887,6 +1897,8 @@ ngx_quic_payload_handler(ngx_connection_ if (p != end) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic trailing garbage in payload: %ui bytes", end - p); + + qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; return NGX_ERROR; } @@ -2012,12 +2024,13 @@ ngx_quic_handle_ack_frame(ngx_connection "quic ngx_quic_handle_ack_frame level %d", pkt->level); /* - * TODO: If any computed packet number is negative, an endpoint MUST - * generate a connection error of type FRAME_ENCODING_ERROR. - * (19.3.1) + * If any computed packet number is negative, an endpoint MUST + * generate a connection error of type FRAME_ENCODING_ERROR. + * (19.3.1) */ if (ack->first_range > ack->largest) { + c->quic->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid first range in ack frame"); return NGX_ERROR; @@ -2049,6 +2062,7 @@ ngx_quic_handle_ack_frame(ngx_connection pos += n; if (gap >= min) { + c->quic->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid range %ui in ack frame", i); return NGX_ERROR; @@ -2057,6 +2071,7 @@ ngx_quic_handle_ack_frame(ngx_connection max = min - 1 - gap; if (range > max + 1) { + c->quic->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid range %ui in ack frame", i); return NGX_ERROR; @@ -2116,7 +2131,9 @@ ngx_quic_handle_ack_frame_range(ngx_conn ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic ACK for the packet not in sent queue "); - /* TODO: handle error properly: PROTOCOL VIOLATION */ + + qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + return NGX_ERROR; } @@ -2978,6 +2995,7 @@ ngx_quic_send_frames(ngx_connection_t *c ngx_msec_t now; ngx_str_t out, res; ngx_queue_t *q; + ngx_ssl_conn_t *ssl_conn; ngx_quic_frame_t *f, *start; ngx_quic_header_t pkt; ngx_quic_secrets_t *keys; @@ -2990,6 +3008,8 @@ ngx_quic_send_frames(ngx_connection_t *c ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_send_frames"); + ssl_conn = c->ssl ? c->ssl->connection : NULL; + q = ngx_queue_head(frames); start = ngx_queue_data(q, ngx_quic_frame_t, queue); @@ -3077,7 +3097,7 @@ ngx_quic_send_frames(ngx_connection_t *c out.len, start->level, pkt.need_ack, pkt.number, pkt.num_len, pkt.trunc); - if (ngx_quic_encrypt(&pkt, c->ssl->connection, &res) != NGX_OK) { + if (ngx_quic_encrypt(&pkt, ssl_conn, &res) != NGX_OK) { return NGX_ERROR; } diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -568,6 +568,7 @@ ngx_quic_parse_frame(ngx_quic_header_t * p = ngx_quic_parse_int(p, end, &varint); if (p == NULL) { + pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic failed to obtain quic frame type"); return NGX_ERROR; @@ -576,7 +577,8 @@ ngx_quic_parse_frame(ngx_quic_header_t * f->type = varint; if (ngx_quic_frame_allowed(pkt, f->type) != NGX_OK) { - return NGX_DECLINED; + pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + return NGX_ERROR; } switch (f->type) { @@ -1003,6 +1005,8 @@ ngx_quic_parse_frame(ngx_quic_header_t * error: + pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic failed to parse frame type 0x%xi", f->type); diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -278,6 +278,7 @@ typedef struct { uint32_t version; ngx_str_t token; enum ssl_encryption_level_t level; + ngx_uint_t error; /* filled in by parser */ ngx_buf_t *raw; /* udp datagram */