From f960bccb5c3558ad9c49d7d01ac689c1c614f741 Mon Sep 17 00:00:00 2001
From: Martin Blix Grydeland <martin@varnish-software.com>
Date: Tue, 1 Jul 2025 15:32:25 +0200
Subject: H2: Make rapid reset handling be calleable from any context

This patch splits the rapid reset handling into a check and a charge
step. The check determines if this was a benign reset, that is whether it
should be charged against the budgest or not.

The charge step subtracts from the budget, and handles raises an error
when exceeded. On error it will send a GOAWAY frame on the session
immediately. To allow an error to be sent from this function, and to give
protection to the rapid reset state variables, it is required that the
caller holds the send mutex when calling.

diff --git a/bin/varnishd/http2/cache_http2.h b/bin/varnishd/http2/cache_http2.h
index ea25e89bd6..4cfa718660 100644
--- a/bin/varnishd/http2/cache_http2.h
+++ b/bin/varnishd/http2/cache_http2.h
@@ -250,6 +250,7 @@ h2_error h2h_decode_bytes(struct h2_sess *h2, const uint8_t *ptr,
     size_t len);
 
 /* cache_http2_send.c */
+#define H2_SEND_HELD(h2, r2) (VTAILQ_FIRST(&(h2)->txqueue) == (r2))
 void H2_Send_Get(struct worker *, struct h2_sess *, struct h2_req *);
 void H2_Send_Rel(struct h2_sess *, const struct h2_req *);
 
@@ -273,6 +274,10 @@ void h2_kill_req(struct worker *, struct h2_sess *, struct h2_req *, h2_error);
 int h2_rxframe(struct worker *, struct h2_sess *);
 h2_error h2_set_setting(struct h2_sess *, const uint8_t *);
 void h2_req_body(struct req*);
+int h2_rapid_reset_check(struct worker *wrk, struct h2_sess *h2,
+    const struct h2_req *r2);
+h2_error h2_rapid_reset_charge(struct worker *wrk, struct h2_sess *h2,
+    const struct h2_req *r2);
 task_func_t h2_do_req;
 #ifdef TRANSPORT_MAGIC
 vtr_req_fail_f h2_req_fail;
diff --git a/bin/varnishd/http2/cache_http2_proto.c b/bin/varnishd/http2/cache_http2_proto.c
index 884ed33e90..b8c3535a33 100644
--- a/bin/varnishd/http2/cache_http2_proto.c
+++ b/bin/varnishd/http2/cache_http2_proto.c
@@ -338,14 +338,14 @@ 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)
+int
+h2_rapid_reset_check(struct worker *wrk, struct h2_sess *h2,
+    const struct h2_req *r2)
 {
 	vtim_real now;
-	vtim_dur d;
 
 	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
-	ASSERT_RXTHR(h2);
+	CHECK_OBJ_NOTNULL(h2, H2_SESS_MAGIC);
 	CHECK_OBJ_NOTNULL(r2, H2_REQ_MAGIC);
 
 	if (h2->rapid_reset_limit == 0)
@@ -357,6 +357,23 @@ h2_rapid_reset(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
 	if (now - r2->req->t_first > h2->rapid_reset)
 		return (0);
 
+	return (1);
+}
+
+h2_error
+h2_rapid_reset_charge(struct worker *wrk, struct h2_sess *h2,
+    const struct h2_req *r2)
+{
+	vtim_real now;
+	vtim_dur d;
+	h2_error h2e = NULL;
+
+	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
+	AN(H2_SEND_HELD(h2, r2));
+	CHECK_OBJ_NOTNULL(r2, H2_REQ_MAGIC);
+
+	now = VTIM_real();
+
 	d = now - h2->last_rst;
 	h2->rst_budget += h2->rapid_reset_limit * d /
 	    h2->rapid_reset_period;
@@ -364,18 +381,21 @@ h2_rapid_reset(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
 	    h2->rapid_reset_limit);
 	h2->last_rst = now;
 
-	if (h2->rst_budget < 1.0) {
+	h2->rst_budget -= 1.0;
+
+	if (h2->rst_budget < 0) {
 		H2S_Lock_VSLb(h2, SLT_SessError, "H2: Hit RST limit. Closing session.");
-		return (H2CE_RAPID_RESET);
+		h2e = H2CE_RAPID_RESET;
+		H2_Send_GOAWAY(wrk, h2, r2, h2e);
 	}
-	h2->rst_budget -= 1.0;
-	return (0);
+
+	return (h2e);
 }
 
 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;
+	h2_error h2e = NULL;
 
 	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
 	ASSERT_RXTHR(h2);
@@ -387,7 +407,11 @@ h2_rx_rst_stream(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
 	}
 	if (r2 == NULL)
 		return (0);
-	h2e = h2_rapid_reset(wrk, h2, r2);
+	if (h2_rapid_reset_check(wrk, h2, r2)) {
+		H2_Send_Get(wrk, h2, r2);
+		h2e = h2_rapid_reset_charge(wrk, h2, r2);
+		H2_Send_Rel(h2, r2);
+	}
 	h2_kill_req(wrk, h2, r2, h2_streamerror(vbe32dec(h2->rxf_data)));
 	return (h2e);
 }
diff --git a/bin/varnishd/http2/cache_http2_send.c b/bin/varnishd/http2/cache_http2_send.c
index 5f7a11d352..41c45961b3 100644
--- a/bin/varnishd/http2/cache_http2_send.c
+++ b/bin/varnishd/http2/cache_http2_send.c
@@ -41,8 +41,6 @@
 #include "vend.h"
 #include "vtim.h"
 
-#define H2_SEND_HELD(h2, r2) (VTAILQ_FIRST(&(h2)->txqueue) == (r2))
-
 static h2_error
 h2_errcheck(const struct h2_req *r2, const struct h2_sess *h2)
 {
