[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.