[PATCH 1 of 3] HTTP/3: adjusted QUIC connection finalization

Roman Arutyunyan arut at nginx.com
Tue Nov 9 07:32:12 UTC 2021


On Mon, Nov 08, 2021 at 07:25:43PM +0300, Sergey Kandaurov wrote:
> On Mon, Oct 18, 2021 at 03:48:28PM +0300, Roman Arutyunyan wrote:
> > # HG changeset patch
> > # User Roman Arutyunyan <arut at nginx.com>
> > # Date 1634559753 -10800
> > #      Mon Oct 18 15:22:33 2021 +0300
> > # Branch quic
> > # Node ID 8739f475583031399879ef0af2eb5af76008449e
> > # Parent  404de224517e33f685613d6425dcdb3c8ef5b97e
> > HTTP/3: adjusted QUIC connection finalization.
> > 
> > When an HTTP/3 function returns an error in context of a QUIC stream, it's
> > this function's responsibility now to finalize the entire QUIC connection
> > with the right code, if required.  Previously, QUIC connection finalization
> > could be done both outside and inside such functions.  The new rule follows
> > a similar rule for logging, leads to cleaner code, and allows to provide more
> > details about the error.
> > 
> > While here, a few error cases are no longer treated as fatal and QUIC connection
> > is no longer finalized in these cases.  A few other cases now lead to
> > stream reset instead of connection finalization.
> > 
> > diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c
> > --- a/src/http/v3/ngx_http_v3.c
> > +++ b/src/http/v3/ngx_http_v3.c
> > @@ -33,7 +33,7 @@ ngx_http_v3_init_session(ngx_connection_
> >  
> >      h3c = ngx_pcalloc(pc->pool, sizeof(ngx_http_v3_session_t));
> >      if (h3c == NULL) {
> > -        return NGX_ERROR;
> > +        goto failed;
> >      }
> >  
> >      h3c->max_push_id = (uint64_t) -1;
> > @@ -49,7 +49,7 @@ ngx_http_v3_init_session(ngx_connection_
> >  
> >      cln = ngx_pool_cleanup_add(pc->pool, 0);
> >      if (cln == NULL) {
> > -        return NGX_ERROR;
> > +        goto failed;
> >      }
> >  
> >      cln->handler = ngx_http_v3_cleanup_session;
> > @@ -58,6 +58,14 @@ ngx_http_v3_init_session(ngx_connection_
> >      hc->v3_session = h3c;
> >  
> >      return ngx_http_v3_send_settings(c);
> > +
> > +failed:
> > +
> > +    ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create http3 session");
> > +
> > +    ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
> > +                                    "failed to create http3 session");
> > +    return NGX_ERROR;
> >  }
> >  
> >  
> > diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c
> > --- a/src/http/v3/ngx_http_v3_request.c
> > +++ b/src/http/v3/ngx_http_v3_request.c
> > @@ -65,8 +65,6 @@ ngx_http_v3_init(ngx_connection_t *c)
> >      ngx_http_core_srv_conf_t  *cscf;
> >  
> >      if (ngx_http_v3_init_session(c) != NGX_OK) {
> > -        ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
> > -                                        "internal error");
> >          ngx_http_close_connection(c);
> >          return;
> >      }
> > @@ -110,8 +108,6 @@ ngx_http_v3_init(ngx_connection_t *c)
> >          h3c->goaway = 1;
> >  
> >          if (ngx_http_v3_send_goaway(c, (n + 1) << 2) != NGX_OK) {
> > -            ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
> > -                                            "goaway error");
> >              ngx_http_close_connection(c);
> >              return;
> >          }
> > @@ -287,15 +283,14 @@ ngx_http_v3_process_request(ngx_event_t 
> >          rc = ngx_http_v3_parse_headers(c, st, b);
> >  
> >          if (rc > 0) {
> > -            ngx_http_v3_finalize_connection(c, rc,
> > -                                            "could not parse request headers");
> > +            ngx_quic_reset_stream(c, rc);
> > +            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
> > +                          "client sent invalid header");
> >              ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
> >              break;
> >          }
> >  
> >          if (rc == NGX_ERROR) {
> > -            ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
> > -                                            "internal error");
> >              ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
> >              break;
> >          }
> > @@ -1167,17 +1162,13 @@ ngx_http_v3_request_body_filter(ngx_http
> >                  }
> >  
> >                  if (rc > 0) {
> > -                    ngx_http_v3_finalize_connection(r->connection, rc,
> > -                                                   "client sent invalid body");
> > +                    ngx_quic_reset_stream(r->connection, rc);
> >                      ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
> >                                    "client sent invalid body");
> >                      return NGX_HTTP_BAD_REQUEST;
> >                  }
> >  
> >                  if (rc == NGX_ERROR) {
> > -                    ngx_http_v3_finalize_connection(r->connection,
> > -                                                NGX_HTTP_V3_ERR_INTERNAL_ERROR,
> > -                                                "internal error");
> >                      return NGX_HTTP_INTERNAL_SERVER_ERROR;
> >                  }
> >  
> > diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c
> > --- a/src/http/v3/ngx_http_v3_streams.c
> > +++ b/src/http/v3/ngx_http_v3_streams.c
> > @@ -283,7 +283,7 @@ ngx_http_v3_create_push_stream(ngx_conne
> >  
> >      sc = ngx_quic_open_stream(c, 0);
> >      if (sc == NULL) {
> > -        return NULL;
> > +        goto failed;
> >      }
> >  
> >      p = buf;
> > @@ -318,7 +318,13 @@ ngx_http_v3_create_push_stream(ngx_conne
> >  
> >  failed:
> >  
> > -    ngx_http_v3_close_uni_stream(sc);
> > +    ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create push stream");
> > +
> > +    ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR,
> > +                                    "failed to create push stream");
> > +    if (sc) {
> > +        ngx_http_v3_close_uni_stream(sc);
> > +    }
> >  
> >      return NULL;
> >  }
> > @@ -368,7 +374,7 @@ ngx_http_v3_get_uni_stream(ngx_connectio
> >  
> >      sc = ngx_quic_open_stream(c, 0);
> >      if (sc == NULL) {
> > -        return NULL;
> > +        goto failed;
> >      }
> >  
> >      sc->quic->cancelable = 1;
> > @@ -405,7 +411,13 @@ ngx_http_v3_get_uni_stream(ngx_connectio
> >  
> >  failed:
> >  
> > -    ngx_http_v3_close_uni_stream(sc);
> > +    ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create server stream");
> > +
> > +    ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR,
> > +                                    "failed to create server stream");
> > +    if (sc) {
> > +        ngx_http_v3_close_uni_stream(sc);
> > +    }
> >  
> >      return NULL;
> >  }
> > @@ -424,7 +436,7 @@ ngx_http_v3_send_settings(ngx_connection
> >  
> >      cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL);
> >      if (cc == NULL) {
> > -        return NGX_DECLINED;
> > +        return NGX_ERROR;
> >      }
> >  
> >      h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module);
> > @@ -457,6 +469,10 @@ ngx_http_v3_send_settings(ngx_connection
> >  
> >  failed:
> >  
> > +    ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send settings");
> > +
> > +    ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
> > +                                    "failed to send settings");
> >      ngx_http_v3_close_uni_stream(cc);
> >  
> >      return NGX_ERROR;
> > @@ -475,7 +491,7 @@ ngx_http_v3_send_goaway(ngx_connection_t
> >  
> >      cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL);
> >      if (cc == NULL) {
> > -        return NGX_DECLINED;
> > +        return NGX_ERROR;
> 
> This case now misses sending CONNECTION_CLOSE (no further stream processing),
> Instead, the last allowed stream connection is now closed while initializing
> with sending FIN with off:0 in its cleanup function which confuses clients
> due to H3_FRAME_UNEXPECTED.  It probably needs to be converted to goto failed
> with conditional uni stream close.
> 
> Same in ngx_http_v3_send_settings() around ngx_http_v3_get_uni_stream(), but
> there it just leads to control stream silent closure with no visible effects
> on the wire.  Yet, with teh 2nd patch applied that updates stream reset logic,
> the error causes to emit RESET_STREAM(H3_INTERNAL_ERROR), which is a protocol
> violation when operating on a critical (control) stream.

Can you elaborate on these?  The ngx_http_v3_get_uni_stream() function now
closes the QUIC connection by calling ngx_http_v3_finalize_connection() in case
of error.  So its callers don't need to do this.

> >      }
> >  
> >      n = ngx_http_v3_encode_varlen_int(NULL, id);
> > @@ -495,6 +511,10 @@ ngx_http_v3_send_goaway(ngx_connection_t
> >  
> >  failed:
> >  
> > +    ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send goaway");
> > +
> > +    ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
> > +                                    "failed to send goaway");
> >      ngx_http_v3_close_uni_stream(cc);
> >  
> >      return NGX_ERROR;
> > @@ -510,7 +530,7 @@ ngx_http_v3_send_ack_section(ngx_connect
> >      ngx_http_v3_session_t  *h3c;
> >  
> >      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
> > -                   "http3 client ack section %ui", stream_id);
> > +                   "http3 send section acknowledgement %ui", stream_id);
> >  
> >      dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER);
> >      if (dc == NULL) {
> > @@ -524,11 +544,21 @@ ngx_http_v3_send_ack_section(ngx_connect
> >      h3c->total_bytes += n;
> >  
> >      if (dc->send(dc, buf, n) != (ssize_t) n) {
> > -        ngx_http_v3_close_uni_stream(dc);
> > -        return NGX_ERROR;
> > +        goto failed;
> >      }
> >  
> >      return NGX_OK;
> > +
> > +failed:
> > +
> > +    ngx_log_error(NGX_LOG_ERR, c->log, 0,
> > +                  "failed to send section acknowledgement");
> > +
> > +    ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
> > +                                    "failed to send section acknowledgement");
> > +    ngx_http_v3_close_uni_stream(dc);
> > +
> > +    return NGX_ERROR;
> >  }
> >  
> >  
> > @@ -541,7 +571,7 @@ ngx_http_v3_send_cancel_stream(ngx_conne
> >      ngx_http_v3_session_t  *h3c;
> >  
> >      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
> > -                   "http3 client cancel stream %ui", stream_id);
> > +                   "http3 send stream cancellation %ui", stream_id);
> >  
> >      dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER);
> >      if (dc == NULL) {
> > @@ -555,11 +585,20 @@ ngx_http_v3_send_cancel_stream(ngx_conne
> >      h3c->total_bytes += n;
> >  
> >      if (dc->send(dc, buf, n) != (ssize_t) n) {
> > -        ngx_http_v3_close_uni_stream(dc);
> > -        return NGX_ERROR;
> > +        goto failed;
> >      }
> >  
> >      return NGX_OK;
> > +
> > +failed:
> > +
> > +    ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send stream cancellation");
> > +
> > +    ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
> > +                                    "failed to send stream cancellation");
> > +    ngx_http_v3_close_uni_stream(dc);
> > +
> > +    return NGX_ERROR;
> >  }
> >  
> >  
> > @@ -572,7 +611,7 @@ ngx_http_v3_send_inc_insert_count(ngx_co
> >      ngx_http_v3_session_t  *h3c;
> >  
> >      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
> > -                   "http3 client increment insert count %ui", inc);
> > +                   "http3 send insert count increment %ui", inc);
> >  
> >      dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER);
> >      if (dc == NULL) {
> > @@ -586,11 +625,21 @@ ngx_http_v3_send_inc_insert_count(ngx_co
> >      h3c->total_bytes += n;
> >  
> >      if (dc->send(dc, buf, n) != (ssize_t) n) {
> > -        ngx_http_v3_close_uni_stream(dc);
> > -        return NGX_ERROR;
> > +        goto failed;
> >      }
> >  
> >      return NGX_OK;
> > +
> > +failed:
> > +
> > +    ngx_log_error(NGX_LOG_ERR, c->log, 0,
> > +                  "failed to send insert count increment");
> > +
> > +    ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD,
> > +                                    "failed to send insert count increment");
> > +    ngx_http_v3_close_uni_stream(dc);
> > +
> > +    return NGX_ERROR;
> >  }
> >  
> >  
> > diff --git a/src/http/v3/ngx_http_v3_tables.c b/src/http/v3/ngx_http_v3_tables.c
> > --- a/src/http/v3/ngx_http_v3_tables.c
> > +++ b/src/http/v3/ngx_http_v3_tables.c
> > @@ -589,6 +589,10 @@ ngx_http_v3_check_insert_count(ngx_conne
> >          if (h3c->nblocked == h3scf->max_blocked_streams) {
> >              ngx_log_error(NGX_LOG_INFO, c->log, 0,
> >                            "client exceeded http3_max_blocked_streams limit");
> > +
> > +            ngx_http_v3_finalize_connection(c,
> > +                                          NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED,
> > +                                          "too many blocked streams");
> >              return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED;
> >          }
> >  
> _______________________________________________
> nginx-devel mailing list
> nginx-devel at nginx.org
> http://mailman.nginx.org/mailman/listinfo/nginx-devel

-- 
Roman Arutyunyan


More information about the nginx-devel mailing list