[MINOR] tcp: add per-source connection rate limiting

This change makes use of the stick-tables to keep track of any source
address activity. Two ACLs make it possible to check the count of an
entry or update it and act accordingly. The typical usage will be to
reject a TCP request upon match of an excess value.
diff --git a/src/proto_tcp.c b/src/proto_tcp.c
index 1c93396..80c10fd 100644
--- a/src/proto_tcp.c
+++ b/src/proto_tcp.c
@@ -46,7 +46,9 @@
 #include <proto/protocols.h>
 #include <proto/proto_tcp.h>
 #include <proto/proxy.h>
+#include <proto/stick_table.h>
 #include <proto/stream_sock.h>
+#include <proto/task.h>
 
 #ifdef CONFIG_HAP_CTTPROXY
 #include <import/ip_tproxy.h>
@@ -1018,6 +1020,87 @@
 	return 1;
 }
 
+/* set test->i to the number of connections from the session's source address
+ * in the table pointed to by expr.
+ */
+static int
+acl_fetch_src_count(struct proxy *px, struct session *l4, void *l7, int dir,
+		    struct acl_expr *expr, struct acl_test *test)
+{
+	struct stksess *ts;
+
+	/* right now we only support IPv4 */
+	if (l4->cli_addr.ss_family != AF_INET)
+		return 0;
+
+	if (expr->arg_len) {
+		/* another table was designated, we must look for it */
+		for (px = proxy; px; px = px->next)
+			if (strcmp(px->id, expr->arg.str) == 0)
+				break;
+	}
+	if (!px)
+		return 0; /* table not found */
+
+	static_table_key.key = (void *)&((struct sockaddr_in *)&l4->frt_addr)->sin_addr;
+	test->flags = ACL_TEST_F_VOL_TEST;
+	test->i = 0;
+	if ((ts = stktable_lookup_key(&px->table, &static_table_key)) != NULL) {
+		void *ptr = stktable_data_ptr(&px->table, ts, STKTABLE_DT_CONN_CUM);
+		if (!ptr)
+			return 0; /* parameter not stored */
+		test->i = stktable_data_cast(ptr, conn_cum);
+	}
+
+	return 1;
+}
+
+/* set test->i to the number of connections from the session's source address
+ * in the table pointed to by expr, after updating it.
+ */
+static int
+acl_fetch_src_update_count(struct proxy *px, struct session *l4, void *l7, int dir,
+			   struct acl_expr *expr, struct acl_test *test)
+{
+	struct stksess *ts;
+	void *ptr;
+
+	/* right now we only support IPv4 */
+	if (l4->cli_addr.ss_family != AF_INET)
+		return 0;
+
+	if (expr->arg_len) {
+		/* another table was designated, we must look for it */
+		for (px = proxy; px; px = px->next)
+			if (strcmp(px->id, expr->arg.str) == 0)
+				break;
+	}
+	if (!px)
+		return 0;
+
+	static_table_key.key = (void *)&((struct sockaddr_in *)&l4->frt_addr)->sin_addr;
+	if ((ts = stktable_lookup_key(&px->table, &static_table_key)) == NULL) {
+		/* entry does not exist, initialize a new one */
+		ts = stksess_new(&px->table, &static_table_key);
+		if (!ts)
+			return 0;
+		stktable_store(&px->table, ts);
+	}
+	else if (px->table.expire) {
+		/* if entries can expire, let's update the entry and the table */
+		ts->expire = tick_add(now_ms, MS_TO_TICKS(px->table.expire));
+		px->table.exp_task->expire = px->table.exp_next = tick_first(ts->expire, px->table.exp_next);
+		task_queue(px->table.exp_task);
+	}
+
+	ptr = stktable_data_ptr(&px->table, ts, STKTABLE_DT_CONN_CUM);
+	if (!ptr)
+		return 0; /* parameter not stored in this table */
+
+	test->i = ++stktable_data_cast(ptr, conn_cum);
+	test->flags = ACL_TEST_F_VOL_TEST;
+	return 1;
+}
 
 static struct cfg_kw_list cfg_kws = {{ },{
 	{ CFG_LISTEN, "tcp-request", tcp_parse_tcp_req },
@@ -1030,6 +1113,8 @@
 	{ "src",        acl_parse_ip,    acl_fetch_src,      acl_match_ip,  ACL_USE_TCP4_PERMANENT|ACL_MAY_LOOKUP },
 	{ "dst",        acl_parse_ip,    acl_fetch_dst,      acl_match_ip,  ACL_USE_TCP4_PERMANENT|ACL_MAY_LOOKUP },
 	{ "dst_port",   acl_parse_int,   acl_fetch_dport,    acl_match_int, ACL_USE_TCP_PERMANENT  },
+	{ "src_count",  acl_parse_int,   acl_fetch_src_count,acl_match_int, ACL_USE_TCP4_PERMANENT },
+	{ "src_update_count",  acl_parse_int,   acl_fetch_src_update_count, acl_match_int, ACL_USE_TCP4_PERMANENT },
 	{ NULL, NULL, NULL, NULL },
 }};