[MINOR] session-counters: add a general purpose counter (gpc0)

This counter may be used to track anything. Two sets of ACLs are available
to manage it, one gets its value, and the other one increments its value
and returns it. In the second case, the entry is created if it did not
exist.

Thus it is possible for example to mark a source as being an abuser and
to keep it marked as long as it does not wait for the entry to expire :

	# The rules below use gpc0 to track abusers, and reject them if
	# a source has been marked as such. The track-counters statement
	# automatically refreshes the entry which will not expire until a
	# 1-minute silence is respected from the source. The second rule
	# evaluates the second part if the first one is true, so GPC0 will
	# be increased once the conn_rate is above 100/5s.
	stick-table type ip size 200k expire 1m store conn_rate(5s),gpc0
	tcp-request track-counters src
	tcp-request reject if { trk_get_gpc0 gt 0 }
	tcp-request reject if { trk_conn_rate gt 100 } { trk_inc_gpc0 gt 0}

Alternatively, it is possible to let the entry expire even in presence of
traffic by swapping the check for gpc0 and the track-counters statement :

	stick-table type ip size 200k expire 1m store conn_rate(5s),gpc0
	tcp-request reject if { src_get_gpc0 gt 0 }
	tcp-request track-counters src
	tcp-request reject if { trk_conn_rate gt 100 } { trk_inc_gpc0 gt 0}

It is also possible not to track counters at all, but entry lookups will
then be performed more often :

	stick-table type ip size 200k expire 1m store conn_rate(5s),gpc0
	tcp-request reject if { src_get_gpc0 gt 0 }
	tcp-request reject if { src_conn_rate gt 100 } { src_inc_gpc0 gt 0}

The '0' at the end of the counter name is there because if we find that more
counters may be useful, other ones will be added.
diff --git a/src/session.c b/src/session.c
index 2c11576..aaa0622 100644
--- a/src/session.c
+++ b/src/session.c
@@ -2102,6 +2102,106 @@
 /*           All supported ACL keywords must be declared here.          */
 /************************************************************************/
 
+/* set test->i to the General Purpose Counter 0 value in the stksess entry <ts> */
+static int
+acl_fetch_get_gpc0(struct stktable *table, struct acl_test *test, struct stksess *ts)
+{
+	test->flags = ACL_TEST_F_VOL_TEST;
+	test->i = 0;
+	if (ts != NULL) {
+		void *ptr = stktable_data_ptr(table, ts, STKTABLE_DT_GPC0);
+		if (!ptr)
+			return 0; /* parameter not stored */
+		test->i = stktable_data_cast(ptr, gpc0);
+	}
+	return 1;
+}
+
+/* set test->i to the General Purpose Counter 0 value from the session's tracked
+ * counters.
+ */
+static int
+acl_fetch_trk_get_gpc0(struct proxy *px, struct session *l4, void *l7, int dir,
+		       struct acl_expr *expr, struct acl_test *test)
+{
+	if (!l4->tracked_counters)
+		return 0;
+	return acl_fetch_get_gpc0(l4->tracked_table, test, l4->tracked_counters);
+}
+
+/* set test->i to the General Purpose Counter 0 value from the session's source
+ * address in the table pointed to by expr.
+ */
+static int
+acl_fetch_src_get_gpc0(struct proxy *px, struct session *l4, void *l7, int dir,
+		       struct acl_expr *expr, struct acl_test *test)
+{
+	struct stktable_key *key;
+
+	key = tcpv4_src_to_stktable_key(l4);
+	if (!key)
+		return 0; /* only TCPv4 is supported right now */
+
+	if (expr->arg_len)
+		px = find_stktable(expr->arg.str);
+
+	if (!px)
+		return 0; /* table not found */
+
+	return acl_fetch_get_gpc0(&px->table, test, stktable_lookup_key(&px->table, key));
+}
+
+/* Increment the General Purpose Counter 0 value in the stksess entry <ts> and
+ * return it into test->i.
+ */
+static int
+acl_fetch_inc_gpc0(struct stktable *table, struct acl_test *test, struct stksess *ts)
+{
+	test->flags = ACL_TEST_F_VOL_TEST;
+	test->i = 0;
+	if (ts != NULL) {
+		void *ptr = stktable_data_ptr(table, ts, STKTABLE_DT_GPC0);
+		if (!ptr)
+			return 0; /* parameter not stored */
+		test->i = ++stktable_data_cast(ptr, gpc0);
+	}
+	return 1;
+}
+
+/* Increment the General Purpose Counter 0 value from the session's tracked
+ * counters and return it into test->i.
+ */
+static int
+acl_fetch_trk_inc_gpc0(struct proxy *px, struct session *l4, void *l7, int dir,
+		       struct acl_expr *expr, struct acl_test *test)
+{
+	if (!l4->tracked_counters)
+		return 0;
+	return acl_fetch_inc_gpc0(l4->tracked_table, test, l4->tracked_counters);
+}
+
+/* Increment the General Purpose Counter 0 value from the session's source
+ * address in the table pointed to by expr, and return it into test->i.
+ */
+static int
+acl_fetch_src_inc_gpc0(struct proxy *px, struct session *l4, void *l7, int dir,
+		       struct acl_expr *expr, struct acl_test *test)
+{
+	struct stktable_key *key;
+
+	key = tcpv4_src_to_stktable_key(l4);
+	if (!key)
+		return 0; /* only TCPv4 is supported right now */
+
+	if (expr->arg_len)
+		px = find_stktable(expr->arg.str);
+
+	if (!px)
+		return 0; /* table not found */
+
+	return acl_fetch_inc_gpc0(&px->table, test, stktable_update_key(&px->table, key));
+}
+
 /* set test->i to the cumulated number of connections in the stksess entry <ts> */
 static int
 acl_fetch_conn_cnt(struct stktable *table, struct acl_test *test, struct stksess *ts)
@@ -2594,6 +2694,10 @@
 
 /* Note: must not be declared <const> as its list will be overwritten */
 static struct acl_kw_list acl_kws = {{ },{
+	{ "trk_get_gpc0",       acl_parse_int,   acl_fetch_trk_get_gpc0,      acl_match_int, ACL_USE_NOTHING },
+	{ "src_get_gpc0",       acl_parse_int,   acl_fetch_src_get_gpc0,      acl_match_int, ACL_USE_TCP4_VOLATILE },
+	{ "trk_inc_gpc0",       acl_parse_int,   acl_fetch_trk_inc_gpc0,      acl_match_int, ACL_USE_NOTHING },
+	{ "src_inc_gpc0",       acl_parse_int,   acl_fetch_src_inc_gpc0,      acl_match_int, ACL_USE_TCP4_VOLATILE },
 	{ "trk_conn_cnt",       acl_parse_int,   acl_fetch_trk_conn_cnt,      acl_match_int, ACL_USE_NOTHING },
 	{ "src_conn_cnt",       acl_parse_int,   acl_fetch_src_conn_cnt,      acl_match_int, ACL_USE_TCP4_VOLATILE },
 	{ "trk_conn_rate",      acl_parse_int,   acl_fetch_trk_conn_rate,     acl_match_int, ACL_USE_NOTHING },