]> git.kaiwu.me - nginx.git/commitdiff
Added generation of CC frames with error on connection termination.
authorVladimir Homutov <vl@nginx.com>
Thu, 14 May 2020 12:54:45 +0000 (15:54 +0300)
committerVladimir Homutov <vl@nginx.com>
Thu, 14 May 2020 12:54:45 +0000 (15:54 +0300)
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.

src/event/ngx_event_quic.c
src/event/ngx_event_quic_transport.c
src/event/ngx_event_quic_transport.h

index 6bb34889206178dad51a9a7afb6cafc9487fe0b6..bbe1e79c32b2de6707549d0e0f40938357e9aa66 100644 (file)
@@ -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_t *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_t *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 *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp,
         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 *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp,
     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 *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp,
         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 *c, ngx_quic_header_t *pkt)
 
     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 *c, ngx_quic_header_t *pkt)
     /* 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 *c, ngx_quic_header_t *pkt)
 
     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 *c, ngx_quic_header_t *pkt)
     }
 
     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_t *c, ngx_int_t rc)
     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_t *c, ngx_int_t rc)
 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, ngx_int_t rc)
 
     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_int_t rc)
              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, ngx_int_t rc)
                            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_t *c, ngx_quic_header_t *pkt)
 
         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_t *c, ngx_quic_header_t *pkt)
     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_t *c, ngx_quic_header_t *pkt,
                    "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_t *c, ngx_quic_header_t *pkt,
         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_t *c, ngx_quic_header_t *pkt,
         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_connection_t *c, ngx_quic_send_ctx_t *ctx,
 
         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_queue_t *frames)
     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_queue_t *frames)
     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, ngx_queue_t *frames)
                    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;
     }
 
index ddf99a3bc94b77e1ac49dba6fa6469bf4fb9dbb2..4dcb78abd47c30e302645476a4c58f88a34407c6 100644 (file)
@@ -568,6 +568,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end,
 
     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 *pkt, u_char *start, u_char *end,
     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 *pkt, u_char *start, u_char *end,
 
 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);
 
index 1ff70f97b275acdb7b18813992bcd4c6a2d56b92..364f827ac9f06b2641d87ea897488a6e022acd48 100644 (file)
@@ -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 */