From: Willy Tarreau Date: Mon, 14 Jun 2010 19:04:55 +0000 (+0200) Subject: [MAJOR] session: add track-counters to track counters related to the session X-Git-Tag: v1.5-dev8~528 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=9ba2dcc86c490da3275b01bdc71ff3f1716ce477;p=thirdparty%2Fhaproxy.git [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 37d8ea8bd6..1b46d37ad1 100644 --- a/include/proto/proto_tcp.h +++ b/include/proto/proto_tcp.h @@ -25,6 +25,7 @@ #include #include #include +#include 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 */ /* diff --git a/include/proto/session.h b/include/proto/session.h index c7a693e737..3d56f8e030 100644 --- a/include/proto/session.h +++ b/include/proto/session.h @@ -25,6 +25,7 @@ #include #include #include +#include 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 . The caller is + * responsible for ensuring that and 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 db45760f25..813b3b9716 100644 --- a/include/proto/stick_table.h +++ b/include/proto/stick_table.h @@ -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); diff --git a/include/types/proto_tcp.h b/include/types/proto_tcp.h index 54d12a7d5a..a7ca56a4f8 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 #include +#include /* 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 fdccc7b1cf..222c30f7ad 100644 --- a/include/types/session.h +++ b/include/types/session.h @@ -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 */ diff --git a/src/cfgparse.c b/src/cfgparse.c index f313b71ec2..871310b66b 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -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 )) { diff --git a/src/proto_tcp.c b/src/proto_tcp.c index 24906be63a..7adb0f9990 100644 --- a/src/proto_tcp.c +++ b/src/proto_tcp.c @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -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) { diff --git a/src/session.c b/src/session.c index 96933d3d06..4e2bedd652 100644 --- a/src/session.c +++ b/src/session.c @@ -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 + * 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 09cb96beb1..f490384234 100644 --- a/src/stick_table.c +++ b/src/stick_table.c @@ -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 . The next expiration date is * returned.