[MAJOR] session: add track-counters to track counters related to the session

This patch adds the ability to set a pointer in the session to an
entry in a stick table which holds various counters related to a
specific pattern.

Right now the syntax matches the target syntax and only the "src"
pattern can be specified, to track counters related to the session's
IPv4 source address. There is a special function to extract it and
convert it to a key. But the goal is to be able to later support as
many patterns as for the stick rules, and get rid of the specific
function.

The "track-counters" directive may only be set in a "tcp-request"
statement right now. Only the first one applies. Probably that later
we'll support multi-criteria tracking for a single session and that
we'll have to name tracking pointers.

No counter is updated right now, only the refcount is. Some subsequent
patches will have to bring that feature.
diff --git a/include/proto/proto_tcp.h b/include/proto/proto_tcp.h
index 37d8ea8..1b46d37 100644
--- a/include/proto/proto_tcp.h
+++ b/include/proto/proto_tcp.h
@@ -25,6 +25,7 @@
 #include <common/config.h>
 #include <types/proto_tcp.h>
 #include <types/task.h>
+#include <proto/stick_table.h>
 
 int tcpv4_bind_socket(int fd, int flags, struct sockaddr_in *local, struct sockaddr_in *remote);
 void tcpv4_add_listener(struct listener *listener);
@@ -37,6 +38,22 @@
 int tcp_persist_rdp_cookie(struct session *s, struct buffer *req, int an_bit);
 int tcp_exec_req_rules(struct session *s);
 
+/* Converts the TCPv4 source address to a stick_table key usable for table
+ * lookups. Returns either NULL if the source cannot be converted (eg: not
+ * IPv4) or a pointer to the converted result in static_table_key in the
+ * appropriate format (IP).
+ */
+static inline struct stktable_key *tcpv4_src_to_stktable_key(struct session *s)
+{
+	/* right now we only support IPv4 */
+	if (s->cli_addr.ss_family != AF_INET)
+		return NULL;
+
+	static_table_key.key = (void *)&((struct sockaddr_in *)&s->cli_addr)->sin_addr;
+	return &static_table_key;
+}
+
+
 #endif /* _PROTO_PROTO_TCP_H */
 
 /*
diff --git a/include/proto/session.h b/include/proto/session.h
index c7a693e..3d56f8e 100644
--- a/include/proto/session.h
+++ b/include/proto/session.h
@@ -25,6 +25,7 @@
 #include <common/config.h>
 #include <common/memory.h>
 #include <types/session.h>
+#include <proto/stick_table.h>
 
 extern struct pool_head *pool2_session;
 extern struct list sessions;
@@ -40,6 +41,31 @@
 struct task *process_session(struct task *t);
 void sess_set_term_flags(struct session *s);
 void default_srv_error(struct session *s, struct stream_interface *si);
+int parse_track_counters(char **args, int *arg,
+			 int section_type, struct proxy *curpx,
+			 struct track_ctr_prm *prm,
+			 struct proxy *defpx, char *err, int errlen);
+
+/* Remove the refcount from the session to the tracked counters, and clear the
+ * pointer to ensure this is only performed once. The caller is responsible for
+ * ensuring that the pointer is valid first.
+ */
+static inline void session_store_counters(struct session *s)
+{
+	s->tracked_counters->ref_cnt--;
+	s->tracked_counters = NULL;
+}
+
+/* Enable tracking of session counters on stksess <ts>. The caller is
+ * responsible for ensuring that <t> and <ts> are valid pointers and that no
+ * previous tracked_counters was assigned to the session.
+ */
+static inline void session_track_counters(struct session *s, struct stktable *t, struct stksess *ts)
+{
+	ts->ref_cnt++;
+	s->tracked_table = t;
+	s->tracked_counters = ts;
+}
 
 static void inline trace_term(struct session *s, unsigned int code)
 {
diff --git a/include/proto/stick_table.h b/include/proto/stick_table.h
index db45760..813b3b9 100644
--- a/include/proto/stick_table.h
+++ b/include/proto/stick_table.h
@@ -36,6 +36,7 @@
 
 int stktable_init(struct stktable *t);
 int stktable_parse_type(char **args, int *idx, unsigned long *type, size_t *key_size);
+struct stksess *stktable_get_entry(struct stktable *table, struct stktable_key *key);
 struct stksess *stktable_store(struct stktable *t, struct stksess *ts);
 struct stksess *stktable_touch(struct stktable *t, struct stksess *ts);
 struct stksess *stktable_lookup(struct stktable *t, struct stksess *ts);
diff --git a/include/types/proto_tcp.h b/include/types/proto_tcp.h
index 54d12a7..a7ca56a 100644
--- a/include/types/proto_tcp.h
+++ b/include/types/proto_tcp.h
@@ -1,23 +1,23 @@
 /*
-  include/types/proto_tcp.h
-  This file contains TCP protocol definitions.
-
-  Copyright (C) 2000-2008 Willy Tarreau - w@1wt.eu
-  
-  This library is free software; you can redistribute it and/or
-  modify it under the terms of the GNU Lesser General Public
-  License as published by the Free Software Foundation, version 2.1
-  exclusively.
-
-  This library is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-  Lesser General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public
-  License along with this library; if not, write to the Free Software
-  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
-*/
+ * include/types/proto_tcp.h
+ * This file contains TCP protocol definitions.
+ *
+ * Copyright (C) 2000-2010 Willy Tarreau - w@1wt.eu
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, version 2.1
+ * exclusively.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
 
 #ifndef _TYPES_PROTO_TCP_H
 #define _TYPES_PROTO_TCP_H
@@ -26,17 +26,22 @@
 #include <common/mini-clist.h>
 
 #include <types/acl.h>
+#include <types/session.h>
 
 /* Layer4 accept/reject rules */
 enum {
 	TCP_ACT_ACCEPT = 1,
 	TCP_ACT_REJECT = 2,
+	TCP_ACT_TRK_CTR = 3,
 };
 
 struct tcp_rule {
 	struct list list;
 	struct acl_cond *cond;
 	int action;
+	union {
+		struct track_ctr_prm trk_ctr;
+	} act_prm;
 };
 
 #endif /* _TYPES_PROTO_TCP_H */
diff --git a/include/types/session.h b/include/types/session.h
index fdccc7b..222c30f 100644
--- a/include/types/session.h
+++ b/include/types/session.h
@@ -184,7 +184,8 @@
 		int flags;
 	} store[8];				/* tracked stickiness values to store */
 	int store_count;
-	struct stksess *tracked_src_counters;	/* tracked counters for this source */
+	struct stksess *tracked_counters;       /* counters currently being tracked by this session */
+	struct stktable *tracked_table;         /* table the counters above belong to (undefined if counters are null) */
 
 	struct {
 		int logwait;			/* log fields waiting to be collected : LW_* */
@@ -236,6 +237,15 @@
 	unsigned int uniq_id;			/* unique ID used for the traces */
 };
 
+/* parameters to configure tracked counters */
+struct track_ctr_prm {
+	int type;				/* type of the key */
+	union {
+		struct stktable *t;		/* a pointer to the table */
+		char *n;			/* or its name during parsing. */
+	} table;
+};
+
 
 #endif /* _TYPES_SESSION_H */
 
diff --git a/src/cfgparse.c b/src/cfgparse.c
index f313b71..871310b 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -4688,6 +4688,7 @@
 	while (curproxy != NULL) {
 		struct switching_rule *rule;
 		struct sticking_rule *mrule;
+		struct tcp_rule *trule;
 		struct listener *listener;
 		unsigned int next_id;
 
@@ -4936,6 +4937,43 @@
 			}
 		}
 
+		/* find the target table for 'tcp-request' layer 4 rules */
+		list_for_each_entry(trule, &curproxy->tcp_req.l4_rules, list) {
+			struct proxy *target;
+
+			if (trule->action != TCP_ACT_TRK_CTR)
+				continue;
+
+			if (trule->act_prm.trk_ctr.table.n)
+				target = findproxy(trule->act_prm.trk_ctr.table.n, 0);
+			else
+				target = curproxy;
+
+			if (!target) {
+				Alert("Proxy '%s': unable to find table '%s' referenced by track-counter.\n",
+				      curproxy->id, trule->act_prm.trk_ctr.table.n);
+				cfgerr++;
+			}
+			else if (target->table.size == 0) {
+				Alert("Proxy '%s': table '%s' used but not configured.\n",
+				      curproxy->id, trule->act_prm.trk_ctr.table.n ? trule->act_prm.trk_ctr.table.n : curproxy->id);
+				cfgerr++;
+			}
+			else if (trule->act_prm.trk_ctr.type != target->table.type) {
+				Alert("Proxy '%s': type of track-counters pattern not usable with type of stick-table '%s'.\n",
+				      curproxy->id, trule->act_prm.trk_ctr.table.n ? trule->act_prm.trk_ctr.table.n : curproxy->id);
+				cfgerr++;
+			}
+			else {
+				free(trule->act_prm.trk_ctr.table.n);
+				trule->act_prm.trk_ctr.table.t = &target->table;
+				/* Note: if we decide to enhance the track-counters syntax, we may be able
+				 * to pass a list of counters to track and allocate them right here using
+				 * stktable_alloc_data_type().
+				 */
+			}
+		}
+
 		if (curproxy->uri_auth && !(curproxy->uri_auth->flags & ST_CONVDONE) &&
 		    !LIST_ISEMPTY(&curproxy->uri_auth->req_acl) &&
 		    (curproxy->uri_auth->userlist || curproxy->uri_auth->auth_realm )) {
diff --git a/src/proto_tcp.c b/src/proto_tcp.c
index 24906be..7adb0f9 100644
--- a/src/proto_tcp.c
+++ b/src/proto_tcp.c
@@ -46,6 +46,7 @@
 #include <proto/protocols.h>
 #include <proto/proto_tcp.h>
 #include <proto/proxy.h>
+#include <proto/session.h>
 #include <proto/stick_table.h>
 #include <proto/stream_sock.h>
 #include <proto/task.h>
@@ -704,6 +705,9 @@
 int tcp_exec_req_rules(struct session *s)
 {
 	struct tcp_rule *rule;
+	struct stksess *ts = s->tracked_counters;
+	struct stktable *t = NULL;
+	int result = 1;
 	int ret;
 
 	list_for_each_entry(rule, &s->fe->tcp_req.l4_rules, list) {
@@ -727,13 +731,29 @@
 					s->flags |= SN_ERR_PRXCOND;
 				if (!(s->flags & SN_FINST_MASK))
 					s->flags |= SN_FINST_R;
-				return 0;
+				result = 0;
+				break;
 			}
-			/* otherwise it's an accept */
-			break;
+			else if (rule->action == TCP_ACT_TRK_CTR) {
+				if (!s->tracked_counters) {
+					/* only the first valid track-counters directive applies.
+					 * Also, note that right now we can only track SRC so we
+					 * don't check how to get the key, but later we may need
+					 * to consider rule->act_prm->trk_ctr.type.
+					 */
+					t = rule->act_prm.trk_ctr.table.t;
+					ts = stktable_get_entry(t, tcpv4_src_to_stktable_key(s));
+					if (ts)
+						session_track_counters(s, t, ts);
+				}
+			}
+			else {
+				/* otherwise it's an accept */
+				break;
+			}
 		}
 	}
-	return 1;
+	return result;
 }
 
 /* This function should be called to parse a line starting with the "tcp-request"
@@ -840,13 +860,29 @@
 
 	/* OK so we're in front of plain L4 rules */
 
-	if (strcmp(args[1], "accept") == 0)
+	if (strcmp(args[1], "accept") == 0) {
+		arg++;
 		rule->action = TCP_ACT_ACCEPT;
-	else if (strcmp(args[1], "reject") == 0)
+	}
+	else if (strcmp(args[1], "reject") == 0) {
+		arg++;
 		rule->action = TCP_ACT_REJECT;
+	}
+	else if (strcmp(args[1], "track-counters") == 0) {
+		int ret;
+
+		arg++;
+		ret = parse_track_counters(args, &arg, section_type, curpx,
+					   &rule->act_prm.trk_ctr, defpx, err, errlen);
+
+		if (ret < 0) /* nb: warnings are not handled yet */
+			goto error;
+
+		rule->action = TCP_ACT_TRK_CTR;
+	}
 	else {
 		retlen = snprintf(err, errlen,
-				  "'%s' expects 'inspect-delay', 'content', 'accept' or 'reject', in %s '%s' (was '%s')",
+				  "'%s' expects 'inspect-delay', 'content', 'accept', 'reject', or 'track-counters' in %s '%s' (was '%s')",
 				  args[0], proxy_type_str(curpx), curpx->id, args[1]);
 		goto error;
 	}
@@ -857,7 +893,6 @@
 		goto error;
 	}
 
-	arg++;
 	pol = ACL_COND_NONE;
 
 	if (strcmp(args[arg], "if") == 0 || strcmp(args[arg], "unless") == 0) {
diff --git a/src/session.c b/src/session.c
index 96933d3..4e2bedd 100644
--- a/src/session.c
+++ b/src/session.c
@@ -63,6 +63,8 @@
 	/* minimum session initialization required for monitor mode below */
 	s->flags = 0;
 	s->logs.logwait = p->to_log;
+	s->tracked_counters = NULL;
+	s->tracked_table = NULL;
 
 	/* if this session comes from a known monitoring system, we want to ignore
 	 * it as soon as possible, which means closing it immediately for TCP, but
@@ -117,6 +119,8 @@
 	 * even initializing the stream interfaces.
 	 */
 	if ((l->options & LI_O_TCP_RULES) && !tcp_exec_req_rules(s)) {
+		if (s->tracked_counters)
+			session_store_counters(s);
 		task_free(t);
 		LIST_DEL(&s->list);
 		pool_free2(pool2_session, s);
@@ -176,7 +180,6 @@
 
 	/* init store persistence */
 	s->store_count = 0;
-	s->tracked_src_counters = NULL;
 
 	/* Adjust some socket options */
 	if (unlikely(fcntl(cfd, F_SETFL, O_NONBLOCK) == -1)) {
@@ -257,6 +260,8 @@
 			/* work is finished, we can release everything (eg: monitoring) */
 			pool_free2(pool2_buffer, s->rep);
 			pool_free2(pool2_buffer, s->req);
+			if (s->tracked_counters)
+				session_store_counters(s);
 			task_free(t);
 			LIST_DEL(&s->list);
 			pool_free2(pool2_session, s);
@@ -279,6 +284,8 @@
 	pool_free2(pool2_buffer, s->req);
  out_free_task:
 	p->feconn--;
+	if (s->tracked_counters)
+		session_store_counters(s);
 	task_free(t);
  out_free_session:
 	LIST_DEL(&s->list);
@@ -341,6 +348,9 @@
 		pool_free2(fe->req_cap_pool, txn->req.cap);
 	}
 
+	if (s->tracked_counters)
+		session_store_counters(s);
+
 	list_for_each_entry_safe(bref, back, &s->back_refs, users) {
 		/* we have to unlink all watchers. We must not relink them if
 		 * this session was the last one in the list.
@@ -2040,6 +2050,56 @@
 		s->flags |= fin;
 }
 
+
+/* Parse a "track-counters" line starting with "track-counters" in args[arg-1].
+ * Returns the number of warnings emitted, or -1 in case of fatal errors. The
+ * <prm> struct is fed with the table name if any. If unspecified, the caller
+ * will assume that the current proxy's table is used.
+ */
+int parse_track_counters(char **args, int *arg,
+			 int section_type, struct proxy *curpx,
+			 struct track_ctr_prm *prm,
+			 struct proxy *defpx, char *err, int errlen)
+{
+	int pattern_type = 0;
+
+	/* parse the arguments of "track-counters" before the condition in the
+	 * following form :
+	 *      track-counters src [ table xxx ] [ if|unless ... ]
+	 */
+	while (args[*arg]) {
+		if (strcmp(args[*arg], "src") == 0) {
+			prm->type = STKTABLE_TYPE_IP;
+			pattern_type = 1;
+		}
+		else if (strcmp(args[*arg], "table") == 0) {
+			if (!args[*arg + 1]) {
+				snprintf(err, errlen,
+					 "missing table for track-counter in %s '%s'.",
+					 proxy_type_str(curpx), curpx->id);
+				return -1;
+			}
+			/* we copy the table name for now, it will be resolved later */
+			prm->table.n = strdup(args[*arg + 1]);
+			(*arg)++;
+		}
+		else {
+			/* unhandled keywords are handled by the caller */
+			break;
+		}
+		(*arg)++;
+	}
+
+	if (!pattern_type) {
+		snprintf(err, errlen,
+			 "track-counter key not specified in %s '%s' (found %s, only 'src' is supported).",
+			 proxy_type_str(curpx), curpx->id, quote_arg(args[*arg]));
+		return -1;
+	}
+
+	return 0;
+}
+
 /*
  * Local variables:
  *  c-indent-level: 8
diff --git a/src/stick_table.c b/src/stick_table.c
index 09cb96b..f490384 100644
--- a/src/stick_table.c
+++ b/src/stick_table.c
@@ -225,6 +225,30 @@
 	return ts;
 }
 
+/* Returns a valid or initialized stksess for the specified stktable_key in the
+ * specified table, or NULL if the key was NULL, or if no entry was found nor
+ * could be created. The entry's expiration is updated.
+ */
+struct stksess *stktable_get_entry(struct stktable *table, struct stktable_key *key)
+{
+	struct stksess *ts;
+
+	if (!key)
+		return NULL;
+
+	ts = stktable_lookup_key(table, key);
+	if (ts == NULL) {
+		/* entry does not exist, initialize a new one */
+		ts = stksess_new(table, key);
+		if (!ts)
+			return NULL;
+		stktable_store(table, ts);
+	}
+	else
+		stktable_touch(table, ts);
+	return ts;
+}
+
 /*
  * Trash expired sticky sessions from table <t>. The next expiration date is
  * returned.