diff --git a/bin/varnishd/http2/cache_http2.h b/bin/varnishd/http2/cache_http2.h index eb6e8f6a9f3..9457c99f2dd 100644 --- a/bin/varnishd/http2/cache_http2.h +++ b/bin/varnishd/http2/cache_http2.h @@ -193,6 +193,8 @@ struct h2_sess { VTAILQ_HEAD(,h2_req) txqueue; h2_error error; + double rst_budget; + vtim_real last_rst; }; #define ASSERT_RXTHR(h2) do {assert(h2->rxthr == pthread_self());} while(0) diff --git a/bin/varnishd/http2/cache_http2_proto.c b/bin/varnishd/http2/cache_http2_proto.c index c62a8c429fa..2720824980b 100644 --- a/bin/varnishd/http2/cache_http2_proto.c +++ b/bin/varnishd/http2/cache_http2_proto.c @@ -320,9 +320,41 @@ h2_rx_push_promise(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2) /********************************************************************** */ +static h2_error +h2_rapid_reset(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2) +{ + vtim_real now; + vtim_dur d; + + CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC); + ASSERT_RXTHR(h2); + CHECK_OBJ_NOTNULL(r2, H2_REQ_MAGIC); + + if (cache_param->h2_rapid_reset_limit == 0) + return (0); + + now = VTIM_real(); + d = now - h2->last_rst; + h2->rst_budget += cache_param->h2_rapid_reset_limit * d / + cache_param->h2_rapid_reset_period; + h2->rst_budget = vmin_t(double, h2->rst_budget, + cache_param->h2_rapid_reset_limit); + h2->last_rst = now; + + if (h2->rst_budget < 1.0) { + Lck_Lock(&h2->sess->mtx); + VSLb(h2->vsl, SLT_Error, "H2: Hit RST limit. Closing session."); + Lck_Unlock(&h2->sess->mtx); + return (H2CE_ENHANCE_YOUR_CALM); + } + h2->rst_budget -= 1.0; + return (0); +} + static h2_error v_matchproto_(h2_rxframe_f) h2_rx_rst_stream(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2) { + h2_error h2e; CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC); ASSERT_RXTHR(h2); @@ -332,8 +364,9 @@ h2_rx_rst_stream(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2) return (H2CE_FRAME_SIZE_ERROR); if (r2 == NULL) return (0); + h2e = h2_rapid_reset(wrk, h2, r2); h2_kill_req(wrk, h2, r2, h2_streamerror(vbe32dec(h2->rxf_data))); - return (0); + return (h2e); } /********************************************************************** diff --git a/bin/varnishd/http2/cache_http2_session.c b/bin/varnishd/http2/cache_http2_session.c index e3e3c116c8b..672b40d6435 100644 --- a/bin/varnishd/http2/cache_http2_session.c +++ b/bin/varnishd/http2/cache_http2_session.c @@ -127,6 +127,9 @@ h2_init_sess(struct sess *sp, h2_local_settings(&h2->local_settings); h2->remote_settings = H2_proto_settings; h2->decode = decode; + h2->rst_budget = cache_param->h2_rapid_reset_limit; + h2->last_rst = sp->t_open; + AZ(isnan(h2->last_rst)); AZ(VHT_Init(h2->dectbl, h2->local_settings.header_table_size)); diff --git a/bin/varnishtest/tests/r03996.vtc b/bin/varnishtest/tests/r03996.vtc new file mode 100644 index 00000000000..5864c837f00 --- /dev/null +++ b/bin/varnishtest/tests/r03996.vtc @@ -0,0 +1,49 @@ +varnishtest "h2 rapid reset" + +barrier b1 sock 5 + +server s1 { + rxreq + txresp +} -start + +varnish v1 -cliok "param.set feature +http2" +varnish v1 -cliok "param.set debug +syncvsl" +varnish v1 -cliok "param.set h2_rapid_reset_limit 3" + +varnish v1 -vcl+backend { + import vtc; + + sub vcl_recv { + vtc.barrier_sync("${b1_sock}"); + } + +} -start + +client c1 { + stream 0 { + rxgoaway + expect goaway.err == ENHANCE_YOUR_CALM + } -start + + stream 1 { + txreq + txrst + } -run + stream 3 { + txreq + txrst + } -run + stream 5 { + txreq + txrst + } -run + stream 7 { + txreq + txrst + } -run + + barrier b1 sync + stream 0 -wait +} -run + diff --git a/include/tbl/params.h b/include/tbl/params.h index cb7b7f5aa0b..cbea1bc31b6 100644 --- a/include/tbl/params.h +++ b/include/tbl/params.h @@ -1257,6 +1257,34 @@ PARAM_SIMPLE( "HTTP2 maximum size of an uncompressed header list." ) +PARAM_SIMPLE( + /* name */ h2_rapid_reset_limit, + /* typ */ uint, + /* min */ "0", + /* max */ NULL, + /* def */ "0", + /* units */ NULL, + /* descr */ + "HTTP2 RST Allowance.\n" + "Specifies the maximum number of allowed stream resets issued by\n" + "a client over a time period before the connection is closed.\n" + "Setting this parameter to 0 disables the limit.", + /* flags */ EXPERIMENTAL, +) + +PARAM_SIMPLE( + /* name */ h2_rapid_reset_period, + /* typ */ timeout, + /* min */ "1.000", + /* max */ NULL, + /* def */ "60.000", + /* units */ "seconds", + /* descr */ + "HTTP2 sliding window duration for h2_rapid_reset_limit.", + /* flags */ EXPERIMENTAL|WIZARD, +) + + /*-------------------------------------------------------------------- * Memory pool parameters */