--- a/bin/varnishd/VSC_main.vsc
+++ b/bin/varnishd/VSC_main.vsc
@@ -547,6 +547,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:: shm_records
 	:level:	diag
 	:oneliner:	SHM records
--- a/bin/varnishd/http2/cache_http2.h
+++ b/bin/varnishd/http2/cache_http2.h
@@ -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)
@@ -190,6 +191,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)
--- a/bin/varnishd/http2/cache_http2_proto.c
+++ b/bin/varnishd/http2/cache_http2_proto.c
@@ -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)
@@ -326,9 +327,46 @@ h2_rx_push_promise(struct worker *wrk, s
 /**********************************************************************
  */
 
+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);
@@ -338,8 +376,9 @@ h2_rx_rst_stream(struct worker *wrk, str
 		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);
 }
 
 /**********************************************************************
--- /dev/null
+++ b/bin/varnishtest/tests/r03996.vtc
@@ -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
--- a/bin/varnishtest/vmods.h
+++ b/bin/varnishtest/vmods.h
@@ -35,3 +35,4 @@ VTC_VMOD(vtc)
 VTC_VMOD(blob)
 VTC_VMOD(unix)
 VTC_VMOD(proxy)
+VTC_VMOD(h2)
--- /dev/null
+++ b/bin/varnishtest/vmodtests/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
--- a/configure.ac
+++ b/configure.ac
@@ -835,6 +835,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
--- a/include/tbl/h2_error.h
+++ b/include/tbl/h2_error.h
@@ -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 */
--- a/include/tbl/params.h
+++ b/include/tbl/params.h
@@ -1923,6 +1923,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 */
--- a/include/tbl/sess_close.h
+++ b/include/tbl/sess_close.h
@@ -47,6 +47,7 @@ SESS_CLOSE(PIPE_OVERFLOW, 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 */
--- a/include/vdef.h
+++ b/include/vdef.h
@@ -111,6 +111,55 @@
 #define RUP2(x, y)  (((x)+((y)-1))&(~((uintptr_t)(y)-1UL))) /* PWR2(y) true */
 
 /**********************************************************************
+ * Find the minimum or maximum values.
+ * Only evaluate the expression once and perform type checking.
+ */
+
+/* ref: https://stackoverflow.com/a/17624752 */
+
+#define VINDIRECT(a, b, c)	a ## b ## c
+#define VCOMBINE(a, b, c)	VINDIRECT(a, b, c)
+
+#if defined(__COUNTER__)
+#	define VUNIQ_NAME(base)	VCOMBINE(base, __LINE__, __COUNTER__)
+#else
+#	define VUNIQ_NAME(base)	VCOMBINE(base, __LINE__, 0)
+#endif
+
+/* ref: https://gcc.gnu.org/onlinedocs/gcc/Typeof.html */
+
+#define _vmin(a, b, _va, _vb)						\
+({									\
+	typeof (a) _va = (a);						\
+	typeof (b) _vb = (b);						\
+	(void)(&_va == &_vb);						\
+	_va < _vb ? _va : _vb;						\
+})
+
+#define _vmax(a, b, _va, _vb)						\
+({									\
+	typeof (a) _va = (a);						\
+	typeof (b) _vb = (b);						\
+	(void)(&_va == &_vb);						\
+	_va > _vb ? _va : _vb;						\
+})
+
+#define vmin(a, b)		_vmin((a), (b), VUNIQ_NAME(_vmina),	\
+    VUNIQ_NAME(_vminb))
+#define vmax(a, b)		_vmax((a), (b), VUNIQ_NAME(_vmaxa),	\
+    VUNIQ_NAME(_vmaxb))
+
+#define vmin_t(type, a, b)	vmin((type)(a), (type)(b))
+#define vmax_t(type, a, b)	vmax((type)(a), (type)(b))
+
+/**********************************************************************
+ * Clamp the value between two limits.
+ */
+
+#define vlimit(a, l, u)		vmax((l), vmin((a), (u)))
+#define vlimit_t(type, a, l, u)	vmax_t(type, (l), vmin_t(type, (a), (u)))
+
+/**********************************************************************
  * FlexeLint and compiler shutuppery
  */
 
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -12,4 +12,5 @@ SUBDIRS = \
 	libvmod_vtc \
 	libvmod_blob \
 	libvmod_unix \
-	libvmod_proxy
+	libvmod_proxy \
+	libvmod_h2
--- /dev/null
+++ b/lib/libvmod_h2/Makefile.am
@@ -0,0 +1,3 @@
+
+libvmod_h2_la_SOURCES = vmod_h2.c
+include $(srcdir)/automake_boilerplate.am
--- /dev/null
+++ b/lib/libvmod_h2/automake_boilerplate.am
@@ -0,0 +1,37 @@
+
+# Boilerplate generated by vmodtool.py - changes will be overwritten
+
+AM_LDFLAGS  = $(AM_LT_LDFLAGS)
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/include \
+	-I$(top_srcdir)/bin/varnishd \
+	-I$(top_builddir)/include
+
+vmoddir = $(pkglibdir)/vmods
+vmodtool = $(top_srcdir)/lib/libvcc/vmodtool.py
+vmodtoolargs = --strict --boilerplate
+
+vmod_LTLIBRARIES = libvmod_h2.la
+
+libvmod_h2_la_CFLAGS =
+
+libvmod_h2_la_LDFLAGS = \
+	$(AM_LDFLAGS) \
+	$(VMOD_LDFLAGS)
+
+nodist_libvmod_h2_la_SOURCES = vcc_if.c vcc_if.h
+
+$(libvmod_h2_la_OBJECTS): vcc_if.h
+
+vcc_if.h vmod_h2.rst vmod_h2.man.rst: vcc_if.c
+
+vcc_if.c: $(vmodtool) $(srcdir)/vmod.vcc
+	@PYTHON@ $(vmodtool) $(vmodtoolargs) $(srcdir)/vmod.vcc
+
+EXTRA_DIST = vmod.vcc automake_boilerplate.am
+
+CLEANFILES = $(builddir)/vcc_if.c $(builddir)/vcc_if.h \
+	$(builddir)/vmod_h2.rst \
+	$(builddir)/vmod_h2.man.rst
+
--- /dev/null
+++ b/lib/libvmod_h2/vmod.vcc
@@ -0,0 +1,88 @@
+#-
+# Copyright 2023 UPLEX - Nils Goroll Systemoptimierung
+# All rights reserved.
+#
+# Author: Nils Goroll <nils.goroll@uplex.de>
+#
+# 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()
+
+Returns true when called on a session handled by the built-in HTTP2 transport.
+
+$Function DURATION rapid_reset([DURATION threshold])
+
+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])
+
+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])
+
+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()
+
+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)`
--- /dev/null
+++ b/lib/libvmod_h2/vmod_h2.c
@@ -0,0 +1,107 @@
+/*-
+ * Copyright 2023 UPLEX - Nils Goroll Systemoptimierung
+ * All rights reserved.
+ *
+ * Author: Nils Goroll <nils.goroll@uplex.de>
+ *
+ * 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_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);
+	if (ctx->req == NULL) {
+		VRT_fail(ctx,
+		    "vmod_h2 can only be called from client-side VCL.");
+		return (NULL);
+	}
+	CHECK_OBJ_NOTNULL(ctx->req, REQ_MAGIC);
+	if (ctx->req->transport != &H2_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 vmod_##name##_arg *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);
+}
--- a/man/Makefile.am
+++ b/man/Makefile.am
@@ -21,7 +21,8 @@ dist_man_MANS = \
 	vmod_vtc.3 \
 	vmod_blob.3 \
 	vmod_unix.3 \
-	vmod_proxy.3
+	vmod_proxy.3 \
+	vmod_h2.3
 
 CLEANFILES = $(dist_man_MANS)
 
@@ -109,4 +110,7 @@ vmod_unix.3: $(top_builddir)/lib/libvmod
 vmod_proxy.3: $(top_builddir)/lib/libvmod_proxy/vmod_proxy.man.rst
 	${RST2MAN} $(RST2ANY_FLAGS) $? $@
 
+vmod_h2.3: $(top_builddir)/lib/libvmod_h2/vmod_h2.man.rst
+	${RST2MAN} $(RST2ANY_FLAGS) $? $@
+
 .NOPATH: $(dist_man_MANS)
--- a/bin/varnishd/http2/cache_http2_session.c
+++ b/bin/varnishd/http2/cache_http2_session.c
@@ -128,6 +128,14 @@ h2_init_sess(const struct worker *wrk, s
 		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));
 
