]> git.kaiwu.me - nginx.git/commitdiff
QUIC: reject streams which we could not create.
authorRoman Arutyunyan <arut@nginx.com>
Thu, 11 Nov 2021 16:07:00 +0000 (19:07 +0300)
committerRoman Arutyunyan <arut@nginx.com>
Thu, 11 Nov 2021 16:07:00 +0000 (19:07 +0300)
The reasons why a stream may not be created by server currently include hitting
worker_connections limit and memory allocation error.  Previously in these
cases the entire QUIC connection was closed and all its streams were shut down.
Now the new stream is rejected and existing streams continue working.

To reject an HTTP/3 request stream, RESET_STREAM and STOP_SENDING with
H3_REQUEST_REJECTED error code are sent to client.  HTTP/3 uni streams and
Stream streams are not rejected.

src/event/quic/ngx_event_quic.h
src/event/quic/ngx_event_quic_streams.c
src/http/modules/ngx_http_quic_module.c
src/stream/ngx_stream_quic_module.c

index 839570af0b88619381743a79a2de6d083629bf63..1007c491de751669dc1a07a98baf61d7243f15e1 100644 (file)
@@ -61,6 +61,9 @@ typedef struct {
     ngx_flag_t                 retry;
     ngx_flag_t                 gso_enabled;
     ngx_str_t                  host_key;
+    ngx_int_t                  stream_close_code;
+    ngx_int_t                  stream_reject_code_uni;
+    ngx_int_t                  stream_reject_code_bidi;
     u_char                     av_token_key[NGX_QUIC_AV_KEY_LEN];
     u_char                     sr_token_key[NGX_QUIC_SR_KEY_LEN];
 } ngx_quic_conf_t;
index 68f6b867c1fe629b0a2ffd8fded5ec8944d494fc..bcb10d74ddfa734183d091da6fa5fa49c69ea5e2 100644 (file)
@@ -15,6 +15,7 @@
 
 static ngx_quic_stream_t *ngx_quic_create_client_stream(ngx_connection_t *c,
     uint64_t id);
+static ngx_int_t ngx_quic_reject_stream(ngx_connection_t *c, uint64_t id);
 static ngx_int_t ngx_quic_init_stream(ngx_quic_stream_t *qs);
 static void ngx_quic_init_streams_handler(ngx_connection_t *c);
 static ngx_quic_stream_t *ngx_quic_create_stream(ngx_connection_t *c,
@@ -377,8 +378,13 @@ ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id)
     for ( /* void */ ; min_id < id; min_id += 0x04) {
 
         qs = ngx_quic_create_stream(c, min_id);
+
         if (qs == NULL) {
-            return NULL;
+            if (ngx_quic_reject_stream(c, min_id) != NGX_OK) {
+                return NULL;
+            }
+
+            continue;
         }
 
         if (ngx_quic_init_stream(qs) != NGX_OK) {
@@ -390,7 +396,66 @@ ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id)
         }
     }
 
-    return ngx_quic_create_stream(c, id);
+    qs = ngx_quic_create_stream(c, id);
+
+    if (qs == NULL) {
+        if (ngx_quic_reject_stream(c, id) != NGX_OK) {
+            return NULL;
+        }
+
+        return NGX_QUIC_STREAM_GONE;
+    }
+
+    return qs;
+}
+
+
+static ngx_int_t
+ngx_quic_reject_stream(ngx_connection_t *c, uint64_t id)
+{
+    uint64_t                code;
+    ngx_quic_frame_t       *frame;
+    ngx_quic_connection_t  *qc;
+
+    qc = ngx_quic_get_connection(c);
+
+    code = (id & NGX_QUIC_STREAM_UNIDIRECTIONAL)
+           ? qc->conf->stream_reject_code_uni
+           : qc->conf->stream_reject_code_bidi;
+
+    if (code == 0) {
+        return NGX_DECLINED;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "quic stream id:0x%xL reject err:0x%xL", id, code);
+
+    frame = ngx_quic_alloc_frame(c);
+    if (frame == NULL) {
+        return NGX_ERROR;
+    }
+
+    frame->level = ssl_encryption_application;
+    frame->type = NGX_QUIC_FT_RESET_STREAM;
+    frame->u.reset_stream.id = id;
+    frame->u.reset_stream.error_code = code;
+    frame->u.reset_stream.final_size = 0;
+
+    ngx_quic_queue_frame(qc, frame);
+
+    frame = ngx_quic_alloc_frame(c);
+    if (frame == NULL) {
+        return NGX_ERROR;
+    }
+
+    frame->level = ssl_encryption_application;
+    frame->type = NGX_QUIC_FT_STOP_SENDING;
+    frame->u.stop_sending.id = id;
+    frame->u.stop_sending.error_code = code;
+
+    ngx_quic_queue_frame(qc, frame);
+
+    return NGX_OK;
 }
 
 
@@ -866,7 +931,9 @@ ngx_quic_stream_cleanup_handler(void *data)
     if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0
         || (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0)
     {
-        if (!c->read->pending_eof && !c->read->error) {
+        if (!c->read->pending_eof && !c->read->error
+            && qc->conf->stream_close_code)
+        {
             frame = ngx_quic_alloc_frame(pc);
             if (frame == NULL) {
                 goto done;
@@ -875,7 +942,7 @@ ngx_quic_stream_cleanup_handler(void *data)
             frame->level = ssl_encryption_application;
             frame->type = NGX_QUIC_FT_STOP_SENDING;
             frame->u.stop_sending.id = qs->id;
-            frame->u.stop_sending.error_code = 0x100; /* HTTP/3 no error */
+            frame->u.stop_sending.error_code = qc->conf->stream_close_code;
 
             ngx_quic_queue_frame(qc, frame);
         }
index 9e6d17ead87d01b1d47dc1781857c095c194889e..323ee2eadab1342636245cc85249cad7edd3e6a2 100644 (file)
@@ -314,6 +314,7 @@ ngx_http_quic_create_srv_conf(ngx_conf_t *cf)
      *     conf->tp.sr_enabled = 0
      *     conf->tp.preferred_address = NULL
      *     conf->host_key = { 0, NULL }
+     *     cong->stream_reject_code_uni = 0;
      */
 
     conf->tp.max_idle_timeout = NGX_CONF_UNSET_MSEC;
@@ -331,6 +332,8 @@ ngx_http_quic_create_srv_conf(ngx_conf_t *cf)
 
     conf->retry = NGX_CONF_UNSET;
     conf->gso_enabled = NGX_CONF_UNSET;
+    conf->stream_close_code = NGX_HTTP_V3_ERR_NO_ERROR;
+    conf->stream_reject_code_bidi = NGX_HTTP_V3_ERR_REQUEST_REJECTED;
 
     return conf;
 }
index b40b17c93690313ffd7347f33b6365582df1241d..7ad96a11c0e3001c60234c6fa6cd05e581bb2539 100644 (file)
@@ -241,6 +241,9 @@ ngx_stream_quic_create_srv_conf(ngx_conf_t *cf)
      *     conf->tp.retry_scid = { 0, NULL };
      *     conf->tp.preferred_address = NULL
      *     conf->host_key = { 0, NULL }
+     *     conf->stream_close_code = 0;
+     *     conf->stream_reject_code_uni = 0;
+     *     conf->stream_reject_code_bidi= 0;
      */
 
     conf->tp.max_idle_timeout = NGX_CONF_UNSET_MSEC;