diff --git a/bin/varnishd/cache/cache_transport.h b/bin/varnishd/cache/cache_transport.h index 0e5f03c65ef..79f0a7b2d5b 100644 --- a/bin/varnishd/cache/cache_transport.h +++ b/bin/varnishd/cache/cache_transport.h @@ -44,6 +44,7 @@ typedef void vtr_sess_panic_f (struct vsb *, const struct sess *); typedef void vtr_req_panic_f (struct vsb *, const struct req *); typedef void vtr_req_fail_f (struct req *, stream_close_t); typedef void vtr_reembark_f (struct worker *, struct req *); +typedef int vtr_poll_f (struct req *); typedef int vtr_minimal_response_f (struct req *, uint16_t status); struct transport { @@ -64,6 +65,7 @@ struct transport { vtr_sess_panic_f *sess_panic; vtr_req_panic_f *req_panic; vtr_reembark_f *reembark; + vtr_poll_f *poll; vtr_minimal_response_f *minimal_response; VTAILQ_ENTRY(transport) list; diff --git a/bin/varnishd/cache/cache_vrt_vcl.c b/bin/varnishd/cache/cache_vrt_vcl.c index c98274e9d57..85b594b7d86 100644 --- a/bin/varnishd/cache/cache_vrt_vcl.c +++ b/bin/varnishd/cache/cache_vrt_vcl.c @@ -42,6 +42,7 @@ #include "vbm.h" #include "cache_director.h" +#include "cache_transport.h" #include "cache_vcl.h" #include "vcc_interface.h" @@ -521,6 +522,40 @@ VRT_VCL_Allow_Discard(struct vclref **refp) FREE_OBJ(ref); } +/*-------------------------------------------------------------------- + */ + +static int +req_poll(struct worker *wrk, struct req *req) +{ + struct req *top; + + /* NB: Since a fail transition leads to vcl_synth, the request may be + * short-circuited twice. + */ + if (req->req_reset) { + wrk->vpi->handling = VCL_RET_FAIL; + return (-1); + } + + top = req->top->topreq; + CHECK_OBJ_NOTNULL(top, REQ_MAGIC); + CHECK_OBJ_NOTNULL(top->transport, TRANSPORT_MAGIC); + + if (!FEATURE(FEATURE_VCL_REQ_RESET)) + return (0); + if (top->transport->poll == NULL) + return (0); + if (top->transport->poll(top) >= 0) + return (0); + + VSLb_ts_req(req, "Reset", W_TIM_real(wrk)); + wrk->stats->req_reset++; + wrk->vpi->handling = VCL_RET_FAIL; + req->req_reset = 1; + return (-1); +} + /*-------------------------------------------------------------------- * Method functions to call into VCL programs. * @@ -552,6 +587,8 @@ vcl_call_method(struct worker *wrk, struct req *req, struct busyobj *bo, CHECK_OBJ_NOTNULL(req->sp, SESS_MAGIC); CHECK_OBJ_NOTNULL(req->vcl, VCL_MAGIC); CHECK_OBJ_NOTNULL(req->top, REQTOP_MAGIC); + if (req_poll(wrk, req)) + return; VCL_Req2Ctx(&ctx, req); } assert(ctx.now != 0); diff --git a/bin/varnishd/http2/cache_http2.h b/bin/varnishd/http2/cache_http2.h index eb6e8f6a9f3..ca2e6599301 100644 --- a/bin/varnishd/http2/cache_http2.h +++ b/bin/varnishd/http2/cache_http2.h @@ -49,6 +49,7 @@ struct h2_error_s { typedef const struct h2_error_s *h2_error; +#define H2_CUSTOM_ERRORS #define H2EC1(U,v,r,d) extern const struct h2_error_s H2CE_##U[1]; #define H2EC2(U,v,r,d) extern const struct h2_error_s H2SE_##U[1]; #define H2EC3(U,v,r,d) H2EC1(U,v,r,d) H2EC2(U,v,r,d) @@ -193,6 +194,15 @@ struct h2_sess { VTAILQ_HEAD(,h2_req) txqueue; h2_error error; + + // rst rate limit parameters, copied from h2_* parameters + vtim_dur rapid_reset; + int64_t rapid_reset_limit; + vtim_dur rapid_reset_period; + + // rst rate limit state + 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..edcc709d920 100644 --- a/bin/varnishd/http2/cache_http2_proto.c +++ b/bin/varnishd/http2/cache_http2_proto.c @@ -46,6 +46,7 @@ #include "vtcp.h" #include "vtim.h" +#define H2_CUSTOM_ERRORS #define H2EC1(U,v,r,d) const struct h2_error_s H2CE_##U[1] = {{#U,d,v,0,1,r}}; #define H2EC2(U,v,r,d) const struct h2_error_s H2SE_##U[1] = {{#U,d,v,1,0,r}}; #define H2EC3(U,v,r,d) H2EC1(U,v,r,d) H2EC2(U,v,r,d) @@ -320,9 +321,46 @@ 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 (h2->rapid_reset_limit == 0) + return (0); + + now = VTIM_real(); + CHECK_OBJ_NOTNULL(r2->req, REQ_MAGIC); + AN(r2->req->t_first); + if (now - r2->req->t_first > h2->rapid_reset) + return (0); + + d = now - h2->last_rst; + h2->rst_budget += h2->rapid_reset_limit * d / + h2->rapid_reset_period; + h2->rst_budget = vmin_t(double, h2->rst_budget, + 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_RAPID_RESET); + } + 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 +370,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..acbfdbb5192 100644 --- a/bin/varnishd/http2/cache_http2_session.c +++ b/bin/varnishd/http2/cache_http2_session.c @@ -128,6 +128,14 @@ h2_init_sess(struct sess *sp, h2->remote_settings = H2_proto_settings; h2->decode = decode; + h2->rapid_reset = cache_param->h2_rapid_reset; + h2->rapid_reset_limit = cache_param->h2_rapid_reset_limit; + h2->rapid_reset_period = cache_param->h2_rapid_reset_period; + + h2->rst_budget = 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)); *up = (uintptr_t)h2; @@ -438,6 +446,16 @@ h2_new_session(struct worker *wrk, void *arg) wrk->vsl = NULL; } +static int v_matchproto_(vtr_poll_f) +h2_poll(struct req *req) +{ + struct h2_req *r2; + + CHECK_OBJ_NOTNULL(req, REQ_MAGIC); + CAST_OBJ_NOTNULL(r2, req->transport_priv, H2_REQ_MAGIC); + return (r2->error ? -1 : 1); +} + struct transport HTTP2_transport = { .name = "HTTP/2", .magic = TRANSPORT_MAGIC, @@ -447,4 +465,5 @@ struct transport HTTP2_transport = { .req_body = h2_req_body, .req_fail = h2_req_fail, .sess_panic = h2_sess_panic, + .poll = h2_poll, }; diff --git a/bin/varnishtest/tests/r03996.vtc b/bin/varnishtest/tests/r03996.vtc new file mode 100644 index 00000000000..7faf78316d5 --- /dev/null +++ b/bin/varnishtest/tests/r03996.vtc @@ -0,0 +1,90 @@ +varnishtest "h2 rapid reset" + +barrier b1 sock 2 -cyclic +barrier b2 sock 5 -cyclic + +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 -cliok "param.set h2_rapid_reset 5" + +varnish v1 -vcl+backend { + import vtc; + + sub vcl_recv { + if (req.http.barrier) { + vtc.barrier_sync(req.http.barrier); + } + vtc.barrier_sync("${b2_sock}"); + } + +} -start + +client c1 { + stream 0 { + rxgoaway + expect goaway.err == ENHANCE_YOUR_CALM + } -start + + stream 1 { + txreq -hdr barrier ${b1_sock} + barrier b1 sync + txrst + } -run + stream 3 { + txreq -hdr barrier ${b1_sock} + barrier b1 sync + txrst + } -run + stream 5 { + txreq -hdr barrier ${b1_sock} + barrier b1 sync + txrst + } -run + stream 7 { + txreq -hdr barrier ${b1_sock} + barrier b1 sync + txrst + } -run + + barrier b2 sync + stream 0 -wait +} -run + +varnish v1 -expect sc_rapid_reset == 1 + +varnish v1 -cliok "param.set feature -vcl_req_reset" + +client c2 { + 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 b2 sync + stream 0 -wait +} -run + +varnish v1 -expect sc_rapid_reset == 2 diff --git a/bin/varnishtest/tests/t02014.vtc b/bin/varnishtest/tests/t02014.vtc index f47eed65767..f4083c89077 100644 --- a/bin/varnishtest/tests/t02014.vtc +++ b/bin/varnishtest/tests/t02014.vtc @@ -1,6 +1,12 @@ varnishtest "Exercise h/2 sender flow control code" -barrier b1 sock 3 -cyclic +barrier b1 sock 3 +barrier b2 sock 3 +barrier b3 sock 3 +barrier b4 sock 3 + +barrier b2_err cond 2 +barrier b3_err cond 2 server s1 { rxreq @@ -23,7 +29,9 @@ varnish v1 -vcl+backend { } sub vcl_deliver { - vtc.barrier_sync("${b1_sock}"); + if (req.http.barrier) { + vtc.barrier_sync(req.http.barrier); + } } } -start @@ -43,7 +51,7 @@ client c1 { } -start stream 1 { - txreq + txreq -hdr barrier ${b1_sock} barrier b1 sync delay .5 txwinup -size 256 @@ -59,26 +67,44 @@ client c1 { stream 0 -wait } -run +varnish v1 -vsl_catchup + +logexpect l2 -v v1 -g raw { + expect * * ReqMethod GET + expect * = VCL_call DELIVER +} -start + client c2 { stream 0 { - barrier b1 sync + barrier b2 sync } -start stream 1 { - txreq + txreq -hdr barrier ${b2_sock} + barrier b2_err sync txdata -data "fail" rxrst expect rst.err == STREAM_CLOSED - barrier b1 sync + barrier b2 sync } -run stream 0 -wait -} -run +} -start + +logexpect l2 -wait +barrier b2_err sync + +client c2 -wait + +logexpect l3 -v v1 -g raw { + expect * * ReqMethod POST + expect * = VCL_call DELIVER +} -start client c3 { stream 0 { - barrier b1 sync - barrier b1 sync + barrier b3 sync + barrier b4 sync delay .5 txwinup -size 256 delay .5 @@ -89,17 +115,18 @@ client c3 { } -start stream 1 { - txreq -req "POST" -nostrend + txreq -req "POST" -hdr barrier ${b3_sock} -nostrend txdata -data "ok" + barrier b3_err sync txdata -data "fail" rxrst expect rst.err == STREAM_CLOSED - barrier b1 sync + barrier b3 sync } -run stream 3 { - txreq - barrier b1 sync + txreq -hdr barrier ${b4_sock} + barrier b4 sync delay .5 txwinup -size 256 delay .5 @@ -112,4 +139,9 @@ client c3 { } -run stream 0 -wait -} -run +} -start + +logexpect l3 -wait +barrier b3_err sync + +client c3 -wait diff --git a/bin/varnishtest/tests/t02025.vtc b/bin/varnishtest/tests/t02025.vtc new file mode 100644 index 00000000000..578dbf5c84b --- /dev/null +++ b/bin/varnishtest/tests/t02025.vtc @@ -0,0 +1,52 @@ +varnishtest "h2 reset interrupt" + +barrier b1 sock 2 +barrier b2 sock 2 + +varnish v1 -cliok "param.set feature +http2" +varnish v1 -cliok "param.set debug +syncvsl" +varnish v1 -vcl { + import vtc; + + backend be none; + + sub vcl_recv { + vtc.barrier_sync("${b1_sock}"); + vtc.barrier_sync("${b2_sock}"); + } + + sub vcl_miss { + vtc.panic("unreachable"); + } +} -start + +logexpect l1 -v v1 -g raw -i Debug { + expect * * Debug "^H2RXF RST_STREAM" +} -start + +client c1 { + stream 1 { + txreq + barrier b1 sync + txrst + } -run + expect_close +} -start + +logexpect l1 -wait +barrier b2 sync + +client c1 -wait + +varnish v1 -vsl_catchup +varnish v1 -expect req_reset == 1 + +# NB: The varnishncsa command below shows a minimal pattern to collect +# "rapid reset" suspects per session, with the IP address. Here rapid +# is interpreted as before a second elapsed. Session VXIDs showing up +# numerous times become increasingly more suspicious. The format can of +# course be extended to add anything else useful for data mining. +shell -expect "1000 ${localhost}" { + varnishncsa -n ${v1_name} -d \ + -q 'Timestamp:Reset[2] < 1.0' -F '%{VSL:Begin[2]}x %h' +} diff --git a/doc/sphinx/reference/vsl.rst b/doc/sphinx/reference/vsl.rst index e8ea7a36d89..98c126d2d1d 100644 --- a/doc/sphinx/reference/vsl.rst +++ b/doc/sphinx/reference/vsl.rst @@ -76,6 +76,11 @@ Resp Restart Client request is being restarted. +Reset + The client closed its connection, reset its stream or caused + a stream error that forced Varnish to reset the stream. Request + processing is interrupted and considered failed. + Pipe handling timestamps ~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/include/tbl/feature_bits.h b/include/tbl/feature_bits.h index 1ff2a361fbc..808efe9174c 100644 --- a/include/tbl/feature_bits.h +++ b/include/tbl/feature_bits.h @@ -91,6 +91,11 @@ FEATURE_BIT(TRACE, trace, "Required for tracing vcl_init / vcl_fini" ) +FEATURE_BIT(VCL_REQ_RESET, vcl_req_reset, + "Stop processing client VCL once the client is gone. " + "When this happens MAIN.req_reset is incremented." +) + #undef FEATURE_BIT /*lint -restore */ diff --git a/include/tbl/h2_error.h b/include/tbl/h2_error.h index 2cd06f01107..c6c6bed8918 100644 --- a/include/tbl/h2_error.h +++ b/include/tbl/h2_error.h @@ -147,5 +147,17 @@ H2_ERROR( /* descr */ "Use HTTP/1.1 for the request" ) +#ifdef H2_CUSTOM_ERRORS +H2_ERROR( + /* name */ RAPID_RESET, + /* val */ 11, /* ENHANCE_YOUR_CALM */ + /* types */ 1, + /* reason */ SC_RAPID_RESET, + /* descr */ "http/2 rapid reset detected" +) + +# undef H2_CUSTOM_ERRORS +#endif + #undef H2_ERROR /*lint -restore */ diff --git a/include/tbl/params.h b/include/tbl/params.h index 83054eef3de..073e5be3b38 100644 --- a/include/tbl/params.h +++ b/include/tbl/params.h @@ -316,7 +316,7 @@ PARAM_SIMPLE( /* type */ bytes_u, /* min */ "128b", /* max */ "99999999b", - /* def */ "48k", + /* def */ "64k", /* units */ "bytes", /* descr */ "Maximum size of CLI response. If the response exceeds this " @@ -1257,6 +1257,52 @@ PARAM_SIMPLE( "HTTP2 maximum size of an uncompressed header list." ) +#define H2_RR_INFO \ + "Changes to this parameter affect the default for new HTTP2 " \ + "sessions. vmod_h2(3) can be used to adjust it from VCL." + +PARAM_SIMPLE( + /* name */ h2_rapid_reset, + /* typ */ timeout, + /* min */ "0.000", + /* max */ NULL, + /* def */ "1.000", + /* units */ "seconds", + /* descr */ + "The upper threshold for how soon an http/2 RST_STREAM frame has " + "to be parsed after a HEADERS frame for it to be treated as " + "suspect and subjected to the rate limits specified by " + "h2_rapid_reset_limit and h2_rapid_reset_period.\n" H2_RR_INFO, + /* flags */ EXPERIMENTAL|DELAYED_EFFECT, +) + +PARAM_SIMPLE( + /* name */ h2_rapid_reset_limit, + /* typ */ uint, + /* min */ "0", + /* max */ NULL, + /* def */ "100", + /* 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.\n" H2_RR_INFO, + /* flags */ EXPERIMENTAL|DELAYED_EFFECT, +) + +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.\n" H2_RR_INFO, + /* flags */ EXPERIMENTAL|DELAYED_EFFECT|WIZARD, +) + /*-------------------------------------------------------------------- * Memory pool parameters */ @@ -1782,7 +1828,9 @@ PARAM_PRE PARAM_BITS( /* name */ feature, /* fld */ feature_bits, - /* def */ "+validate_headers", + /* def */ + "+validate_headers," + "+vcl_req_reset", /* descr */ "Enable/Disable various minor features.\n" "\tdefault\tSet default value\n" diff --git a/include/tbl/req_flags.h b/include/tbl/req_flags.h index 88eaff6391c..6a9e6acbb18 100644 --- a/include/tbl/req_flags.h +++ b/include/tbl/req_flags.h @@ -40,6 +40,7 @@ REQ_FLAG(is_hit, 0, 0, "") REQ_FLAG(waitinglist, 0, 0, "") REQ_FLAG(want100cont, 0, 0, "") REQ_FLAG(late100cont, 0, 0, "") +REQ_FLAG(req_reset, 0, 0, "") #define REQ_BEREQ_FLAG(lower, vcl_r, vcl_w, doc) \ REQ_FLAG(lower, vcl_r, vcl_w, doc) #include "tbl/req_bereq_flags.h" diff --git a/include/tbl/sess_close.h b/include/tbl/sess_close.h index b91c93951c9..f15d34a64aa 100644 --- a/include/tbl/sess_close.h +++ b/include/tbl/sess_close.h @@ -50,6 +50,7 @@ SESS_CLOSE(PIPE_OVERFLOW, pipe_overflow,1, "Session pipe overflow") SESS_CLOSE(RANGE_SHORT, range_short, 1, "Insufficient data for range") SESS_CLOSE(REQ_HTTP20, req_http20, 1, "HTTP2 not accepted") SESS_CLOSE(VCL_FAILURE, vcl_failure, 1, "VCL failure") +SESS_CLOSE(RAPID_RESET, rapid_reset, 1, "HTTP2 rapid reset") #undef SESS_CLOSE /*lint -restore */ diff --git a/lib/libvsc/VSC_main.vsc b/lib/libvsc/VSC_main.vsc index a4049fccb25..14f3e266fc3 100644 --- a/lib/libvsc/VSC_main.vsc +++ b/lib/libvsc/VSC_main.vsc @@ -345,6 +345,15 @@ Number of times an HTTP/2 stream was refused because the queue was too long already. See also parameter thread_queue_limit. +.. varnish_vsc:: req_reset + :group: wrk + :oneliner: Requests reset + + Number of times a client left before the VCL processing of its + requests completed. For HTTP/2 sessions, either the stream was + reset by an RST_STREAM frame from the client, or a stream or + connection error occurred. + .. varnish_vsc:: n_object :type: gauge :group: wrk @@ -634,6 +643,14 @@ Number of session closes with Error VCL_FAILURE (VCL failure) +.. varnish_vsc:: sc_rapid_reset + :level: diag + :oneliner: Session Err RAPID_RESET + + Number of times we failed an http/2 session because it hit its + configured limits for the number of permitted rapid stream + resets. + .. varnish_vsc:: client_resp_500 :level: diag :group: wrk diff --git a/vmod/Makefile.am b/vmod/Makefile.am index 694e6c284ac..eab8cb0c29b 100644 --- a/vmod/Makefile.am +++ b/vmod/Makefile.am @@ -24,6 +24,7 @@ vmod_LTLIBRARIES = vmod_vcc_files = include $(srcdir)/automake_boilerplate_blob.am +include $(srcdir)/automake_boilerplate_h2.am include $(srcdir)/automake_boilerplate_cookie.am include $(srcdir)/automake_boilerplate_debug.am include $(srcdir)/automake_boilerplate_directors.am diff --git a/vmod/automake_boilerplate_h2.am b/vmod/automake_boilerplate_h2.am new file mode 100644 index 00000000000..fac5fd51710 --- /dev/null +++ b/vmod/automake_boilerplate_h2.am @@ -0,0 +1,42 @@ +# Generated by vmodtool.py --boilerplate. + +vmod_h2_vcc ?= $(srcdir)/vmod_h2.vcc + +vmod_vcc_files += $(vmod_h2_vcc) + +vmod_LTLIBRARIES += libvmod_h2.la + +libvmod_h2_la_SOURCES = \ + vmod_h2.c + +libvmod_h2_la_CFLAGS = + +vmodtoolargs_h2 ?= --strict --boilerplate -o vcc_h2_if +vmod_h2_symbols_regex ?= Vmod_h2_Data + +libvmod_h2_la_LDFLAGS = \ + -export-symbols-regex $(vmod_h2_symbols_regex) \ + $(AM_LDFLAGS) \ + $(VMOD_LDFLAGS) + +nodist_libvmod_h2_la_SOURCES = vcc_h2_if.c vcc_h2_if.h + +EXTRA_libvmod_h2_la_DEPENDENCIES = $(nodist_libvmod_h2_la_SOURCES) + +EXTRA_DIST += automake_boilerplate_h2.am + +$(libvmod_h2_la_OBJECTS): vcc_h2_if.h + +vcc_h2_if.h vmod_h2.rst vmod_h2.man.rst: vcc_h2_if.c + +# A doc-change will not update mtime on the .h and .c files, so a +# touch(1) is necessary to signal that vmodtool was in fact run. +vcc_h2_if.c: $(VMODTOOL) $(srcdir)/vmod_h2.vcc + @PYTHON@ $(VMODTOOL) $(vmodtoolargs_h2) $(srcdir)/vmod_h2.vcc + touch vcc_h2_if.c + +clean-local: clean-vmod-h2 + +clean-vmod-h2: + rm -f $(nodist_libvmod_h2_la_SOURCES) + rm -f vmod_h2.rst vmod_h2.man.rst diff --git a/vmod/tests/h2_b00000.vtc b/vmod/tests/h2_b00000.vtc new file mode 100644 index 00000000000..aedb6aad8c5 --- /dev/null +++ b/vmod/tests/h2_b00000.vtc @@ -0,0 +1,90 @@ +varnishtest "VMOD h2 basics" + +varnish v1 -arg "-p feature=+http2" -vcl { + import h2; + + backend proforma none; + + sub vcl_recv { + return(synth(200)); + } + + sub vcl_synth { + set resp.http.http2-is = h2.is(); + set resp.body = ""; + return (deliver); + } +} -start + +client c1 { + txreq + rxresp + expect resp.status == 200 + expect resp.http.http2-is == false +} -start + +client c2 { + stream 7 { + txreq + rxresp + expect resp.status == 200 + expect resp.http.http2-is == true + } -run +} -start + +client c1 -wait +client c2 -wait + +# coverage +varnish v1 -vcl { + import h2; + + backend proforma none; + + sub vcl_recv { + return(synth(200)); + } + + sub vcl_synth { + set resp.http.rapid-reset-o = h2.rapid_reset(10ms); + set resp.http.rapid-reset-n = h2.rapid_reset(); + set resp.http.rapid-reset-limit-o = h2.rapid_reset_limit(10); + set resp.http.rapid-reset-limit-n = h2.rapid_reset_limit(); + set resp.http.rapid-reset-period-o = h2.rapid_reset_period(10s); + set resp.http.rapid-reset-period-n = h2.rapid_reset_period(); + set resp.http.rapid-reset-budget = h2.rapid_reset_budget(); + set resp.body = ""; + return (deliver); + } +} + +client c1 { + txreq + rxresp + expect resp.status == 200 + expect resp.http.rapid-reset-o == -1.000 + expect resp.http.rapid-reset-n == -1.000 + expect resp.http.rapid-reset-limit-o == -1 + expect resp.http.rapid-reset-limit-n == -1 + expect resp.http.rapid-reset-period-o == -1.000 + expect resp.http.rapid-reset-period-n == -1.000 + expect resp.http.rapid-reset-budget == -1.000 +} -start + +client c2 { + stream 7 { + txreq + rxresp + expect resp.status == 200 + expect resp.http.rapid-reset-o == 1.000 + expect resp.http.rapid-reset-n == 0.010 + expect resp.http.rapid-reset-limit-o == 100 + expect resp.http.rapid-reset-limit-n == 10 + expect resp.http.rapid-reset-period-o == 60.000 + expect resp.http.rapid-reset-period-n == 10.000 + expect resp.http.rapid-reset-budget == 10.000 + } -run +} -start + +client c1 -wait +client c2 -wait diff --git a/vmod/vmod_h2.c b/vmod/vmod_h2.c new file mode 100644 index 00000000000..45642f14023 --- /dev/null +++ b/vmod/vmod_h2.c @@ -0,0 +1,102 @@ +/*- + * Copyright 2023 UPLEX - Nils Goroll Systemoptimierung + * All rights reserved. + * + * Author: Nils Goroll + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "config.h" + +#include "cache/cache_varnishd.h" + +#include "vcc_h2_if.h" + +#include "cache/cache_transport.h" +#include "http2/cache_http2.h" + +static struct h2_sess * +h2get(VRT_CTX) +{ + struct h2_sess *h2; + uintptr_t *up; + + CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); + CHECK_OBJ_NOTNULL(ctx->req, REQ_MAGIC); // $Restrict client + if (ctx->req->transport != &HTTP2_transport) + return (NULL); + AZ(SES_Get_proto_priv(ctx->req->sp, &up)); + CAST_OBJ_NOTNULL(h2, (void *)*up, H2_SESS_MAGIC); + return (h2); +} +VCL_BOOL +vmod_is(VRT_CTX) +{ + struct h2_sess *h2 = h2get(ctx); + + return (h2 != NULL); +} + +#define GETSET(type, name, argname) \ +type \ +vmod_ ## name(VRT_CTX, struct VARGS(name) *args) \ +{ \ + struct h2_sess *h2 = h2get(ctx); \ + type r; \ + \ + (void)args; \ + \ + if (h2 == NULL) \ + return (-1); \ + \ + if (! args->valid_ ## argname) \ + return (h2->name); \ + if (h2->name == args->argname) \ + return (h2->name); \ + \ + Lck_Lock(&h2->sess->mtx); \ + r = h2->name; \ + if (h2->name != args->argname) { \ + h2->name = args->argname; \ + h2->rst_budget = h2->rapid_reset_limit; \ + h2->last_rst = ctx->now; \ + } \ + Lck_Unlock(&h2->sess->mtx); \ + return (r); \ +} + +GETSET(VCL_DURATION, rapid_reset, threshold) +GETSET(VCL_INT, rapid_reset_limit, number) +GETSET(VCL_DURATION, rapid_reset_period, duration) + +VCL_REAL +vmod_rapid_reset_budget(VRT_CTX) +{ + struct h2_sess *h2 = h2get(ctx); + + if (h2 == NULL) + return (-1); + + return (h2->rst_budget); +} diff --git a/vmod/vmod_h2.vcc b/vmod/vmod_h2.vcc new file mode 100644 index 00000000000..28f821fe7f8 --- /dev/null +++ b/vmod/vmod_h2.vcc @@ -0,0 +1,93 @@ +#- +# Copyright 2023 UPLEX - Nils Goroll Systemoptimierung +# All rights reserved. +# +# Author: Nils Goroll +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +$ABI strict +$Module h2 3 "Module to control the built-in HTTP2 transport" + +DESCRIPTION +=========== + +This VMOD contains functions to control the HTTP2 transport built into +Varnish-Cache. + +$Function BOOL is() +$Restrict client + +Returns true when called on a session handled by the built-in HTTP2 transport. + +$Function DURATION rapid_reset([DURATION threshold]) +$Restrict client + +Get and optionally set the ``h2_rapid_reset`` parameter (See +:ref:`varnishd(1)`) for this HTTP2 session only. + +Returns -1 when used outside the HTTP2 transport. Otherwise returns +the previous value. + +If the call leads to a change in the rate limit parameters, the +current budget as retuned by +`h2.rapid_reset_budget()`_ is reset. + +$Function INT rapid_reset_limit([INT number]) +$Restrict client + +Get and optionally set the ``h2_rapid_reset_limit`` parameter (See +:ref:`varnishd(1)`) for this HTTP2 session only. + +Returns -1 when used outside the HTTP2 transport. Otherwise returns +the previous value. + +If the call leads to a change in the rate limit parameters, the +current budget as retuned by +`h2.rapid_reset_budget()`_ is reset. + +$Function DURATION rapid_reset_period([DURATION duration]) +$Restrict client + +Get and optionally set the ``h2_rapid_reset_period`` parameter (See +:ref:`varnishd(1)`) for this HTTP2 session only. + +Returns -1 when used outside the HTTP2 transport. Otherwise returns +the previous value. + +If the call leads to a change in the rate limit parameters, the +current budget as retuned by +`h2.rapid_reset_budget()`_ is reset. + +$Function REAL rapid_reset_budget() +$Restrict client + +Return how many RST frames classified as "rapid" the client is still +allowed to send before the session is going to be closed. + +SEE ALSO +======== + +* :ref:`varnishd(1)` +* :ref:`vsl(7)`