Skip to content
This repository was archived by the owner on Feb 16, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions bin/varnishd/VSC_main.vsc
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,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
Expand Down
11 changes: 10 additions & 1 deletion bin/varnishd/http2/cache_http2.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,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)
Expand Down Expand Up @@ -191,8 +192,16 @@ struct h2_sess {
VTAILQ_HEAD(,h2_req) txqueue;

h2_error error;

int open_streams;

// 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)
Expand Down
41 changes: 40 additions & 1 deletion bin/varnishd/http2/cache_http2_proto.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,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)
Expand Down Expand Up @@ -314,9 +315,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);
Expand All @@ -326,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);
}

/**********************************************************************
Expand Down
8 changes: 8 additions & 0 deletions bin/varnishd/http2/cache_http2_session.c
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,14 @@ h2_init_sess(const struct worker *wrk, 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;
Expand Down
59 changes: 59 additions & 0 deletions bin/varnishtest/tests/r03996.vtc
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
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
1 change: 1 addition & 0 deletions bin/varnishtest/vmods.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ VTC_VMOD(vtc)
VTC_VMOD(blob)
VTC_VMOD(unix)
VTC_VMOD(proxy)
VTC_VMOD(h2)
90 changes: 90 additions & 0 deletions bin/varnishtest/vmodtests/h2/b00000.vtc
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,7 @@ AC_CONFIG_FILES([
lib/libvmod_blob/Makefile
lib/libvmod_unix/Makefile
lib/libvmod_proxy/Makefile
lib/libvmod_h2/Makefile
man/Makefile
varnishapi.pc
varnishapi-uninstalled.pc
Expand Down
12 changes: 12 additions & 0 deletions include/tbl/h2_error.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,5 +145,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 */
54 changes: 54 additions & 0 deletions include/tbl/params.h
Original file line number Diff line number Diff line change
Expand Up @@ -1896,6 +1896,60 @@ PARAM(
)
#endif

#define H2_RR_INFO \
"Changes to this parameter affect the default for new HTTP2 " \
"sessions."

PARAM(
/* name */ h2_rapid_reset,
/* typ */ timeout,
/* min */ "0",
/* max */ NULL,
/* default */ "1.0",
/* units */ "seconds",
/* flags */ EXPERIMENTAL | DELAYED_EFFECT,
/* s-text */
"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." H2_RR_INFO,
/* l-text */ "",
/* func */ NULL
)


PARAM(
/* name */ h2_rapid_reset_limit,
/* typ */ uint,
/* min */ "0",
/* max */ NULL,
/* default */ "100",
/* units */ NULL,
/* flags */ EXPERIMENTAL | DELAYED_EFFECT,
/* s-text */
"HTTP2 RST Allowance.\n\n"
"Specifies the maximum number of allowed stream resets issued by "
"a client over a time period before the connection is closed. Setting "
"this parameter to 0 disables the limit." H2_RR_INFO,
/* l-text */ "",
/* func */ NULL
)


PARAM(
/* name */ h2_rapid_reset_period,
/* typ */ timeout,
/* min */ "1.000",
/* max */ NULL,
/* default */ "60.000",
/* units */ "seconds",
/* flags */ EXPERIMENTAL | DELAYED_EFFECT | WIZARD,
/* s-text */
"HTTP2 sliding window duration for h2_rapid_reset_limit." H2_RR_INFO,
/* l-text */ "",
/* func */ NULL
)

#undef PARAM

/*lint -restore */
1 change: 1 addition & 0 deletions include/tbl/sess_close.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,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 */
Loading