[PATCH 2 of 3] HTTP/3: allowed QUIC stream connection reuse
Vladimir Homutov
vl at nginx.com
Tue Nov 16 09:18:47 UTC 2021
On Mon, Nov 15, 2021 at 03:33:25PM +0300, Roman Arutyunyan wrote:
> # HG changeset patch
> # User Roman Arutyunyan <arut at nginx.com>
> # Date 1636646820 -10800
> # Thu Nov 11 19:07:00 2021 +0300
> # Branch quic
> # Node ID 801103b7645d93d0d06f63019e54d9e76f1baa6c
> # Parent d2c193aa84800da00314f1af72ae722d964445a4
> QUIC: reject streams which we could not create.
>
> 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.
>
> diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h
> --- a/src/event/quic/ngx_event_quic.h
> +++ b/src/event/quic/ngx_event_quic.h
> @@ -61,6 +61,9 @@ typedef struct {
> ngx_flag_t retry;
> ngx_flag_t gso_enabled;
> ngx_str_t host_key;
> + ngx_int_t close_stream_code;
> + ngx_int_t reject_uni_stream_code;
> + ngx_int_t reject_bidi_stream_code;
i would prefer stream_close_code and stream_reject_code_uni|bidi,
a bit similar to transport parameter naming like
'initial_max_stream_data_bidi_local', YMMV
> u_char av_token_key[NGX_QUIC_AV_KEY_LEN];
> u_char sr_token_key[NGX_QUIC_SR_KEY_LEN];
> } ngx_quic_conf_t;
> diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c
> --- a/src/event/quic/ngx_event_quic_streams.c
> +++ b/src/event/quic/ngx_event_quic_streams.c
> @@ -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_connec
> 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_connec
> }
> }
>
> - 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->reject_uni_stream_code
> + : qc->conf->reject_bidi_stream_code;
> +
> + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
> + "quic stream id:0x%xL reject err:0x%xL", id, code);
Here we may decline stream rejection, but have already logged it.
I suggest putting debug below 'code == 0' test.
> +
> + if (code == 0) {
> + return NGX_DECLINED;
> + }
> +
> + 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 *da
> 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->close_stream_code)
> + {
> frame = ngx_quic_alloc_frame(pc);
> if (frame == NULL) {
> goto done;
> @@ -875,7 +942,7 @@ ngx_quic_stream_cleanup_handler(void *da
> 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->close_stream_code;
>
> ngx_quic_queue_frame(qc, frame);
> }
> diff --git a/src/http/modules/ngx_http_quic_module.c b/src/http/modules/ngx_http_quic_module.c
> --- a/src/http/modules/ngx_http_quic_module.c
> +++ b/src/http/modules/ngx_http_quic_module.c
> @@ -314,6 +314,7 @@ ngx_http_quic_create_srv_conf(ngx_conf_t
> * conf->tp.sr_enabled = 0
> * conf->tp.preferred_address = NULL
> * conf->host_key = { 0, NULL }
> + * cong->reject_uni_stream_code = 0;
> */
>
> conf->tp.max_idle_timeout = NGX_CONF_UNSET_MSEC;
> @@ -331,6 +332,8 @@ ngx_http_quic_create_srv_conf(ngx_conf_t
>
> conf->retry = NGX_CONF_UNSET;
> conf->gso_enabled = NGX_CONF_UNSET;
> + conf->close_stream_code = NGX_HTTP_V3_ERR_NO_ERROR;
> + conf->reject_bidi_stream_code = NGX_HTTP_V3_ERR_REQUEST_REJECTED;
>
> return conf;
> }
> diff --git a/src/stream/ngx_stream_quic_module.c b/src/stream/ngx_stream_quic_module.c
> --- a/src/stream/ngx_stream_quic_module.c
> +++ b/src/stream/ngx_stream_quic_module.c
> @@ -241,6 +241,9 @@ ngx_stream_quic_create_srv_conf(ngx_conf
> * conf->tp.retry_scid = { 0, NULL };
> * conf->tp.preferred_address = NULL
> * conf->host_key = { 0, NULL }
> + * conf->close_stream_code = 0;
> + * conf->reject_uni_stream_code = 0;
> + * conf->reject_bidi_stream_code = 0;
> */
>
> conf->tp.max_idle_timeout = NGX_CONF_UNSET_MSEC;
Overal patch looks good to me
More information about the nginx-devel
mailing list