[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.
This commit is contained in:
Willy Tarreau 2010-06-14 21:04:55 +02:00
parent 171819b5d7
commit 9ba2dcc86c
9 changed files with 245 additions and 29 deletions

View File

@ -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_inspect_request(struct session *s, struct buffer *req, int an_bit);
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 */
/*

View File

@ -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 @@ void sess_change_server(struct session *sess, struct server *newsrv);
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)
{

View File

@ -36,6 +36,7 @@ void stksess_free(struct stktable *t, struct stksess *ts);
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);

View File

@ -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 */

View File

@ -184,7 +184,8 @@ struct session {
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 @@ struct session {
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 */

View File

@ -4688,6 +4688,7 @@ int check_config_validity()
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 @@ int check_config_validity()
}
}
/* 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 )) {

View File

@ -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_inspect_request(struct session *s, struct buffer *req, int an_bit)
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 @@ int tcp_exec_req_rules(struct session *s)
s->flags |= SN_ERR_PRXCOND;
if (!(s->flags & SN_FINST_MASK))
s->flags |= SN_FINST_R;
return 0;
result = 0;
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;
}
/* 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 @@ static int tcp_parse_tcp_req(char **args, int section_type, struct proxy *curpx,
/* 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 @@ static int tcp_parse_tcp_req(char **args, int section_type, struct proxy *curpx,
goto error;
}
arg++;
pol = ACL_COND_NONE;
if (strcmp(args[arg], "if") == 0 || strcmp(args[arg], "unless") == 0) {

View File

@ -63,6 +63,8 @@ int session_accept(struct listener *l, int cfd, struct sockaddr_storage *addr)
/* 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 @@ int session_accept(struct listener *l, int cfd, struct sockaddr_storage *addr)
* 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 @@ int session_accept(struct listener *l, int cfd, struct sockaddr_storage *addr)
/* 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 @@ int session_accept(struct listener *l, int cfd, struct sockaddr_storage *addr)
/* 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 @@ int session_accept(struct listener *l, int cfd, struct sockaddr_storage *addr)
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 @@ void session_free(struct session *s)
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 @@ void default_srv_error(struct session *s, struct stream_interface *si)
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

View File

@ -225,6 +225,30 @@ struct stksess *stktable_store(struct stktable *t, struct stksess *ts)
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.