MINOR: mux-h2: report detected error on stats

Implement counters for h2 protocol error on connection or stream level.
Also count the total number of rst_stream and goaway frames sent by the
mux in response to a detected error.
diff --git a/src/mux_h2.c b/src/mux_h2.c
index 3fa9486..0b2be7a 100644
--- a/src/mux_h2.c
+++ b/src/mux_h2.c
@@ -386,6 +386,11 @@
 	H2_ST_RST_STREAM_RCVD,
 	H2_ST_GOAWAY_RCVD,
 
+	H2_ST_CONN_PROTO_ERR,
+	H2_ST_STRM_PROTO_ERR,
+	H2_ST_RST_STREAM_RESP,
+	H2_ST_GOAWAY_RESP,
+
 	H2_STATS_COUNT /* must be the last member of the enum */
 };
 
@@ -400,6 +405,15 @@
 	                            .desc = "Total number of received rst_stream frames" },
 	[H2_ST_GOAWAY_RCVD]     = { .name = "h2_goaway_rcvd",
 	                            .desc = "Total number of received goaway frames" },
+
+	[H2_ST_CONN_PROTO_ERR]  = { .name = "h2_detected_conn_protocol_errors",
+	                            .desc = "Total number of connection protocol errors" },
+	[H2_ST_STRM_PROTO_ERR]  = { .name = "h2_detected_strm_protocol_errors",
+	                            .desc = "Total number of stream protocol errors" },
+	[H2_ST_RST_STREAM_RESP] = { .name = "h2_rst_stream_resp",
+	                            .desc = "Total number of rst_stream sent on detected error" },
+	[H2_ST_GOAWAY_RESP]     = { .name = "h2_goaway_resp",
+	                            .desc = "Total number of goaway sent on detected error" },
 };
 
 static struct h2_counters {
@@ -408,6 +422,11 @@
 	long long settings_rcvd;   /* total number of settings frame received */
 	long long rst_stream_rcvd; /* total number of rst_stream frame received */
 	long long goaway_rcvd;     /* total number of goaway frame received */
+
+	long long conn_proto_err;  /* total number of protocol errors detected */
+	long long strm_proto_err;  /* total number of protocol errors detected */
+	long long rst_stream_resp; /* total number of rst_stream frame sent on error */
+	long long goaway_resp;     /* total number of goaway frame sent on error */
 } h2_counters;
 
 static void h2_fill_stats(void *data, struct field *stats)
@@ -419,6 +438,11 @@
 	stats[H2_ST_SETTINGS_RCVD]   = mkf_u64(FN_COUNTER, counters->settings_rcvd);
 	stats[H2_ST_RST_STREAM_RCVD] = mkf_u64(FN_COUNTER, counters->rst_stream_rcvd);
 	stats[H2_ST_GOAWAY_RCVD]     = mkf_u64(FN_COUNTER, counters->goaway_rcvd);
+
+	stats[H2_ST_CONN_PROTO_ERR]  = mkf_u64(FN_COUNTER, counters->conn_proto_err);
+	stats[H2_ST_STRM_PROTO_ERR]  = mkf_u64(FN_COUNTER, counters->strm_proto_err);
+	stats[H2_ST_RST_STREAM_RESP] = mkf_u64(FN_COUNTER, counters->rst_stream_resp);
+	stats[H2_ST_GOAWAY_RESP]     = mkf_u64(FN_COUNTER, counters->goaway_resp);
 }
 
 static struct stats_module h2_stats_module = {
@@ -1619,8 +1643,10 @@
 		if (ret1 < 0)
 			sess_log(h2c->conn->owner);
 
-		if (ret1 < 0 || conn_xprt_read0_pending(h2c->conn))
+		if (ret1 < 0 || conn_xprt_read0_pending(h2c->conn)) {
 			h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
+			HA_ATOMIC_ADD(&h2c->px_counters->conn_proto_err, 1);
+		}
 		ret2 = 0;
 		goto out;
 	}
@@ -1755,6 +1781,7 @@
 	}
 	h2c->flags |= H2_CF_GOAWAY_SENT;
  out:
+	HA_ATOMIC_ADD(&h2c->px_counters->goaway_resp, 1);
 	TRACE_LEAVE(H2_EV_TX_FRAME|H2_EV_TX_GOAWAY, h2c->conn);
 	return ret;
 }
@@ -1899,6 +1926,7 @@
 	}
 
  out:
+	HA_ATOMIC_ADD(&h2c->px_counters->rst_stream_resp, 1);
 	TRACE_LEAVE(H2_EV_TX_FRAME|H2_EV_TX_RST, h2c->conn, h2s);
 	return ret;
 }
@@ -2099,6 +2127,7 @@
 		case H2_SETTINGS_MAX_FRAME_SIZE:
 			if (arg < 16384 || arg > 16777215) { // RFC7540#6.5.2
 				error = H2_ERR_PROTOCOL_ERROR;
+				HA_ATOMIC_ADD(&h2c->px_counters->conn_proto_err, 1);
 				goto fail;
 			}
 			h2c->mfs = arg;
@@ -2106,6 +2135,7 @@
 		case H2_SETTINGS_ENABLE_PUSH:
 			if (arg < 0 || arg > 1) { // RFC7540#6.5.2
 				error = H2_ERR_PROTOCOL_ERROR;
+				HA_ATOMIC_ADD(&h2c->px_counters->conn_proto_err, 1);
 				goto fail;
 			}
 			break;
@@ -2375,6 +2405,7 @@
 
 		if (!inc) {
 			error = H2_ERR_PROTOCOL_ERROR;
+			HA_ATOMIC_ADD(&h2c->px_counters->strm_proto_err, 1);
 			goto strm_err;
 		}
 
@@ -2396,6 +2427,7 @@
 		/* connection window update */
 		if (!inc) {
 			error = H2_ERR_PROTOCOL_ERROR;
+			HA_ATOMIC_ADD(&h2c->px_counters->conn_proto_err, 1);
 			goto conn_err;
 		}
 
@@ -2467,6 +2499,7 @@
 	if (h2_get_n32(&h2c->dbuf, 0) == h2c->dsi) {
 		/* 7540#5.3 : can't depend on itself */
 		h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
+		HA_ATOMIC_ADD(&h2c->px_counters->strm_proto_err, 1);
 		TRACE_DEVEL("leaving on error", H2_EV_RX_FRAME|H2_EV_RX_PRIO, h2c->conn);
 		return 0;
 	}
@@ -2561,6 +2594,7 @@
 	else if (h2c->dsi <= h2c->max_id || !(h2c->dsi & 1)) {
 		/* RFC7540#5.1.1 stream id > prev ones, and must be odd here */
 		error = H2_ERR_PROTOCOL_ERROR;
+		HA_ATOMIC_ADD(&h2c->px_counters->conn_proto_err, 1);
 		sess_log(h2c->conn->owner);
 		goto conn_err;
 	}
@@ -2690,6 +2724,7 @@
 		/* stream error : send RST_STREAM */
 		h2s_error(h2s, H2_ERR_PROTOCOL_ERROR);
 		h2c->st0 = H2_CS_FRAME_E;
+		HA_ATOMIC_ADD(&h2c->px_counters->strm_proto_err, 1);
 		goto fail;
 	}
 
@@ -2755,6 +2790,7 @@
 	if ((h2s->flags & H2_SF_DATA_CLEN) && (h2c->dfl - h2c->dpl) > h2s->body_len) {
 		/* RFC7540#8.1.2 */
 		error = H2_ERR_PROTOCOL_ERROR;
+		HA_ATOMIC_ADD(&h2c->px_counters->strm_proto_err, 1);
 		goto strm_err;
 	}
 
@@ -2798,6 +2834,7 @@
 		if (h2s->flags & H2_SF_DATA_CLEN && h2s->body_len) {
 			/* RFC7540#8.1.2 */
 			error = H2_ERR_PROTOCOL_ERROR;
+			HA_ATOMIC_ADD(&h2c->px_counters->strm_proto_err, 1);
 			goto strm_err;
 		}
 	}
@@ -2833,6 +2870,7 @@
 			/* only log if no other stream can report the error */
 			sess_log(h2c->conn->owner);
 		}
+		HA_ATOMIC_ADD(&h2c->px_counters->strm_proto_err, 1);
 		TRACE_DEVEL("leaving in error (idle&!hdrs&!prio)", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn, h2s);
 		return 0;
 	}
@@ -2840,6 +2878,7 @@
 	if (h2s->st == H2_SS_IDLE && (h2c->flags & H2_CF_IS_BACK)) {
 		/* only PUSH_PROMISE would be permitted here */
 		h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
+		HA_ATOMIC_ADD(&h2c->px_counters->strm_proto_err, 1);
 		TRACE_DEVEL("leaving in error (idle&back)", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn, h2s);
 		return 0;
 	}
@@ -2851,10 +2890,13 @@
 		 * 6.2, 6.6 and 6.10 further mandate that HEADERS/
 		 * PUSH_PROMISE/CONTINUATION cause connection errors.
 		 */
-		if (h2_ft_bit(h2c->dft) & H2_FT_HDR_MASK)
+		if (h2_ft_bit(h2c->dft) & H2_FT_HDR_MASK) {
 			h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
-		else
+			HA_ATOMIC_ADD(&h2c->px_counters->strm_proto_err, 1);
+		}
+		else {
 			h2s_error(h2s, H2_ERR_STREAM_CLOSED);
+		}
 		TRACE_DEVEL("leaving in error (hrem&!wu&!rst&!prio)", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn, h2s);
 		return 0;
 	}
@@ -3000,6 +3042,7 @@
 				h2c->st0 = H2_CS_ERROR2;
 				if (!(h2c->flags & H2_CF_IS_BACK))
 					sess_log(h2c->conn->owner);
+				HA_ATOMIC_ADD(&h2c->px_counters->conn_proto_err, 1);
 				goto fail;
 			}
 
@@ -3085,6 +3128,7 @@
 					h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
 					if (!(h2c->flags & H2_CF_IS_BACK))
 						sess_log(h2c->conn->owner);
+					HA_ATOMIC_ADD(&h2c->px_counters->strm_proto_err, 1);
 					goto fail;
 				}
 
@@ -3187,6 +3231,7 @@
 			h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
 			if (!(h2c->flags & H2_CF_IS_BACK))
 				sess_log(h2c->conn->owner);
+			HA_ATOMIC_ADD(&h2c->px_counters->strm_proto_err, 1);
 			goto fail;
 
 		case H2_FT_HEADERS:
@@ -4402,6 +4447,7 @@
 			/* RFC7540#6.10: frame of unexpected type */
 			TRACE_STATE("not continuation!", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_RX_HDR|H2_EV_RX_CONT|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
 			h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
+			HA_ATOMIC_ADD(&h2c->px_counters->strm_proto_err, 1);
 			goto fail;
 		}
 
@@ -4409,6 +4455,7 @@
 			/* RFC7540#6.10: frame of different stream */
 			TRACE_STATE("different stream ID!", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_RX_HDR|H2_EV_RX_CONT|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
 			h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
+			HA_ATOMIC_ADD(&h2c->px_counters->strm_proto_err, 1);
 			goto fail;
 		}
 
@@ -4461,6 +4508,7 @@
 			/* RFC7540#5.3.1 : stream dep may not depend on itself */
 			TRACE_STATE("invalid stream dependency!", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
 			h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
+			HA_ATOMIC_ADD(&h2c->px_counters->strm_proto_err, 1);
 			goto fail;
 		}
 
@@ -4588,6 +4636,7 @@
 		/* It's a trailer but it's missing ES flag */
 		TRACE_STATE("missing EH on trailers frame", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
 		h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
+		HA_ATOMIC_ADD(&h2c->px_counters->strm_proto_err, 1);
 		goto fail;
 	}