Skip to content
This repository was archived by the owner on Feb 16, 2026. It is now read-only.

Commit a6a5cd5

Browse files
committed
h2: Add a rate limit facility for h/2 RST handling
This adds parameters h2_rst_allowance and h2_rst_allowance_period, which govern the rate of which we allow clients to reset h/2 streams. If the limit is exceeded the connection is closed. Mitigates: #3996
1 parent 7332bdf commit a6a5cd5

5 files changed

Lines changed: 116 additions & 1 deletion

File tree

bin/varnishd/http2/cache_http2.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ struct h2_sess {
193193
VTAILQ_HEAD(,h2_req) txqueue;
194194

195195
h2_error error;
196+
double rst_budget;
197+
vtim_real last_rst;
196198
};
197199

198200
#define ASSERT_RXTHR(h2) do {assert(h2->rxthr == pthread_self());} while(0)

bin/varnishd/http2/cache_http2_proto.c

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,9 +320,41 @@ h2_rx_push_promise(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
320320
/**********************************************************************
321321
*/
322322

323+
static h2_error
324+
h2_rapid_reset(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
325+
{
326+
vtim_real now;
327+
vtim_dur d;
328+
329+
CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
330+
ASSERT_RXTHR(h2);
331+
CHECK_OBJ_NOTNULL(r2, H2_REQ_MAGIC);
332+
333+
if (cache_param->h2_rapid_reset_limit == 0)
334+
return (0);
335+
336+
now = VTIM_real();
337+
d = now - h2->last_rst;
338+
h2->rst_budget += cache_param->h2_rapid_reset_limit * d /
339+
cache_param->h2_rapid_reset_period;
340+
h2->rst_budget = vmin_t(double, h2->rst_budget,
341+
cache_param->h2_rapid_reset_limit);
342+
h2->last_rst = now;
343+
344+
if (h2->rst_budget < 1.0) {
345+
Lck_Lock(&h2->sess->mtx);
346+
VSLb(h2->vsl, SLT_Error, "H2: Hit RST limit. Closing session.");
347+
Lck_Unlock(&h2->sess->mtx);
348+
return (H2CE_ENHANCE_YOUR_CALM);
349+
}
350+
h2->rst_budget -= 1.0;
351+
return (0);
352+
}
353+
323354
static h2_error v_matchproto_(h2_rxframe_f)
324355
h2_rx_rst_stream(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
325356
{
357+
h2_error h2e;
326358

327359
CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
328360
ASSERT_RXTHR(h2);
@@ -332,8 +364,9 @@ h2_rx_rst_stream(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
332364
return (H2CE_FRAME_SIZE_ERROR);
333365
if (r2 == NULL)
334366
return (0);
367+
h2e = h2_rapid_reset(wrk, h2, r2);
335368
h2_kill_req(wrk, h2, r2, h2_streamerror(vbe32dec(h2->rxf_data)));
336-
return (0);
369+
return (h2e);
337370
}
338371

339372
/**********************************************************************

bin/varnishd/http2/cache_http2_session.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ h2_init_sess(struct sess *sp,
127127
h2_local_settings(&h2->local_settings);
128128
h2->remote_settings = H2_proto_settings;
129129
h2->decode = decode;
130+
h2->rst_budget = cache_param->h2_rapid_reset_limit;
131+
h2->last_rst = sp->t_open;
132+
AZ(isnan(h2->last_rst));
130133

131134
AZ(VHT_Init(h2->dectbl, h2->local_settings.header_table_size));
132135

bin/varnishtest/tests/r03996.vtc

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
varnishtest "h2 rapid reset"
2+
3+
barrier b1 sock 5
4+
5+
server s1 {
6+
rxreq
7+
txresp
8+
} -start
9+
10+
varnish v1 -cliok "param.set feature +http2"
11+
varnish v1 -cliok "param.set debug +syncvsl"
12+
varnish v1 -cliok "param.set h2_rapid_reset_limit 3"
13+
14+
varnish v1 -vcl+backend {
15+
import vtc;
16+
17+
sub vcl_recv {
18+
vtc.barrier_sync("${b1_sock}");
19+
}
20+
21+
} -start
22+
23+
client c1 {
24+
stream 0 {
25+
rxgoaway
26+
expect goaway.err == ENHANCE_YOUR_CALM
27+
} -start
28+
29+
stream 1 {
30+
txreq
31+
txrst
32+
} -run
33+
stream 3 {
34+
txreq
35+
txrst
36+
} -run
37+
stream 5 {
38+
txreq
39+
txrst
40+
} -run
41+
stream 7 {
42+
txreq
43+
txrst
44+
} -run
45+
46+
barrier b1 sync
47+
stream 0 -wait
48+
} -run
49+

include/tbl/params.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1257,6 +1257,34 @@ PARAM_SIMPLE(
12571257
"HTTP2 maximum size of an uncompressed header list."
12581258
)
12591259

1260+
PARAM_SIMPLE(
1261+
/* name */ h2_rapid_reset_limit,
1262+
/* typ */ uint,
1263+
/* min */ "0",
1264+
/* max */ NULL,
1265+
/* def */ "0",
1266+
/* units */ NULL,
1267+
/* descr */
1268+
"HTTP2 RST Allowance.\n"
1269+
"Specifies the maximum number of allowed stream resets issued by\n"
1270+
"a client over a time period before the connection is closed.\n"
1271+
"Setting this parameter to 0 disables the limit.",
1272+
/* flags */ EXPERIMENTAL,
1273+
)
1274+
1275+
PARAM_SIMPLE(
1276+
/* name */ h2_rapid_reset_period,
1277+
/* typ */ timeout,
1278+
/* min */ "1.000",
1279+
/* max */ NULL,
1280+
/* def */ "60.000",
1281+
/* units */ "seconds",
1282+
/* descr */
1283+
"HTTP2 sliding window duration for h2_rapid_reset_limit.",
1284+
/* flags */ EXPERIMENTAL|WIZARD,
1285+
)
1286+
1287+
12601288
/*--------------------------------------------------------------------
12611289
* Memory pool parameters
12621290
*/

0 commit comments

Comments
 (0)