[MEDIUM] session-counters: add HTTP req/err tracking

This patch adds support for the following session counters :
  - http_req_cnt : HTTP request count
  - http_req_rate: HTTP request rate
  - http_err_cnt : HTTP request error count
  - http_err_rate: HTTP request error rate

The equivalent ACLs have been added to check the tracked counters
for the current session or the counters of the current source.
This commit is contained in:
Willy Tarreau 2010-06-23 11:44:09 +02:00
parent c3bd972cda
commit da7ff64aa9
5 changed files with 309 additions and 7 deletions

View File

@ -98,6 +98,45 @@ static void inline trace_term(struct session *s, unsigned int code)
s->term_trace |= code;
}
/* Increase the number of cumulated HTTP requests in the tracked counters */
static void inline session_inc_http_req_ctr(struct session *s)
{
if (s->tracked_counters) {
void *ptr;
ptr = stktable_data_ptr(s->tracked_table, s->tracked_counters, STKTABLE_DT_HTTP_REQ_CNT);
if (ptr)
stktable_data_cast(ptr, http_req_cnt)++;
ptr = stktable_data_ptr(s->tracked_table, s->tracked_counters, STKTABLE_DT_HTTP_REQ_RATE);
if (ptr)
update_freq_ctr_period(&stktable_data_cast(ptr, http_req_rate),
s->tracked_table->data_arg[STKTABLE_DT_HTTP_REQ_RATE].u, 1);
}
}
/* Increase the number of cumulated failed HTTP requests in the tracked
* counters. Only 4xx requests should be counted here so that we can
* distinguish between errors caused by client behaviour and other ones.
* Note that even 404 are interesting because they're generally caused by
* vulnerability scans.
*/
static void inline session_inc_http_err_ctr(struct session *s)
{
if (s->tracked_counters) {
void *ptr;
ptr = stktable_data_ptr(s->tracked_table, s->tracked_counters, STKTABLE_DT_HTTP_ERR_CNT);
if (ptr)
stktable_data_cast(ptr, http_err_cnt)++;
ptr = stktable_data_ptr(s->tracked_table, s->tracked_counters, STKTABLE_DT_HTTP_ERR_RATE);
if (ptr)
update_freq_ctr_period(&stktable_data_cast(ptr, http_err_rate),
s->tracked_table->data_arg[STKTABLE_DT_HTTP_ERR_RATE].u, 1);
}
}
#endif /* _PROTO_SESSION_H */
/*

View File

@ -49,6 +49,10 @@ enum {
STKTABLE_DT_CONN_CUR, /* concurrent number of connections */
STKTABLE_DT_SESS_CNT, /* cumulated number of sessions (accepted connections) */
STKTABLE_DT_SESS_RATE, /* accepted sessions rate */
STKTABLE_DT_HTTP_REQ_CNT, /* cumulated number of incoming HTTP requests */
STKTABLE_DT_HTTP_REQ_RATE,/* incoming HTTP request rate */
STKTABLE_DT_HTTP_ERR_CNT, /* cumulated number of HTTP requests errors (4xx) */
STKTABLE_DT_HTTP_ERR_RATE,/* HTTP request error rate */
STKTABLE_DT_BYTES_IN_CNT, /* cumulated bytes count from client to servers */
STKTABLE_DT_BYTES_IN_RATE,/* bytes rate from client to servers */
STKTABLE_DT_BYTES_OUT_CNT,/* cumulated bytes count from servers to client */
@ -72,6 +76,10 @@ union stktable_data {
unsigned int conn_cur;
unsigned int sess_cnt;
struct freq_ctr_period sess_rate;
unsigned int http_req_cnt;
struct freq_ctr_period http_req_rate;
unsigned int http_err_cnt;
struct freq_ctr_period http_err_rate;
unsigned long long bytes_in_cnt;
struct freq_ctr_period bytes_in_rate;
unsigned long long bytes_out_cnt;

View File

@ -2439,6 +2439,10 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit)
* we note the error in the session flags but don't set any state.
* Since the error will be noted there, it will not be counted by
* process_session() as a frontend error.
* Last, we may increase some tracked counters' http request errors on
* the cases that are deliberately the client's fault. For instance,
* a timeout or connection reset is not counted as an error. However
* a bad request is.
*/
if (unlikely(msg->msg_state < HTTP_MSG_BODY)) {
@ -2446,6 +2450,8 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit)
* First, let's catch bad requests.
*/
if (unlikely(msg->msg_state == HTTP_MSG_ERROR)) {
session_inc_http_req_ctr(s);
session_inc_http_err_ctr(s);
proxy_inc_fe_req_ctr(s->fe);
goto return_bad_req;
}
@ -2459,6 +2465,8 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit)
/* FIXME: check if URI is set and return Status
* 414 Request URI too long instead.
*/
session_inc_http_req_ctr(s);
session_inc_http_err_ctr(s);
proxy_inc_fe_req_ctr(s->fe);
goto return_bad_req;
}
@ -2472,11 +2480,15 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit)
goto failed_keep_alive;
/* we cannot return any message on error */
if (msg->err_pos >= 0)
if (msg->err_pos >= 0) {
http_capture_bad_message(&s->fe->invalid_req, s, req, msg, s->fe);
session_inc_http_err_ctr(s);
}
msg->msg_state = HTTP_MSG_ERROR;
req->analysers = 0;
session_inc_http_req_ctr(s);
proxy_inc_fe_req_ctr(s->fe);
s->fe->counters.failed_req++;
if (s->listener->counters)
@ -2496,13 +2508,16 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit)
goto failed_keep_alive;
/* read timeout : give up with an error message. */
if (msg->err_pos >= 0)
if (msg->err_pos >= 0) {
http_capture_bad_message(&s->fe->invalid_req, s, req, msg, s->fe);
session_inc_http_err_ctr(s);
}
txn->status = 408;
stream_int_retnclose(req->prod, error_message(s, HTTP_ERR_408));
msg->msg_state = HTTP_MSG_ERROR;
req->analysers = 0;
session_inc_http_req_ctr(s);
proxy_inc_fe_req_ctr(s->fe);
s->fe->counters.failed_req++;
if (s->listener->counters)
@ -2528,6 +2543,8 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit)
msg->msg_state = HTTP_MSG_ERROR;
req->analysers = 0;
session_inc_http_err_ctr(s);
session_inc_http_req_ctr(s);
proxy_inc_fe_req_ctr(s->fe);
s->fe->counters.failed_req++;
if (s->listener->counters)
@ -2588,6 +2605,7 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit)
* left uninitialized (for instance in the absence of headers).
*/
session_inc_http_req_ctr(s);
proxy_inc_fe_req_ctr(s->fe); /* one more valid request for this FE */
if (txn->flags & TX_WAIT_NEXT_RQ) {
@ -2867,6 +2885,7 @@ int http_process_req_common(struct session *s, struct buffer *req, int an_bit, s
/* let's log the request time */
s->logs.tv_request = now;
stream_int_retnclose(req->prod, error_message(s, HTTP_ERR_403));
session_inc_http_err_ctr(s);
goto return_prx_cond;
}
}
@ -2898,6 +2917,7 @@ int http_process_req_common(struct session *s, struct buffer *req, int an_bit, s
txn->status = 403;
s->logs.tv_request = now;
stream_int_retnclose(req->prod, error_message(s, HTTP_ERR_403));
session_inc_http_err_ctr(s);
goto return_prx_cond;
}
@ -2913,6 +2933,7 @@ int http_process_req_common(struct session *s, struct buffer *req, int an_bit, s
/* let's log the request time */
s->logs.tv_request = now;
stream_int_retnclose(req->prod, error_message(s, HTTP_ERR_403));
session_inc_http_err_ctr(s);
goto return_prx_cond;
}
@ -2932,6 +2953,7 @@ int http_process_req_common(struct session *s, struct buffer *req, int an_bit, s
req->analyse_exp = tick_add_ifset(now_ms, s->be->timeout.tarpit);
if (!req->analyse_exp)
req->analyse_exp = tick_add(now_ms, 0);
session_inc_http_err_ctr(s);
return 1;
}
}
@ -3014,6 +3036,11 @@ int http_process_req_common(struct session *s, struct buffer *req, int an_bit, s
chunk_initlen(&msg, trash, sizeof(trash), strlen(trash));
txn->status = 401;
stream_int_retnclose(req->prod, &msg);
/* on 401 we still count one error, because normal browsing
* won't significantly increase the counter but brute force
* attempts will.
*/
session_inc_http_err_ctr(s);
goto return_prx_cond;
}
@ -3578,8 +3605,10 @@ int http_process_request_body(struct session *s, struct buffer *req, int an_bit)
if (!ret)
goto missing_data;
else if (ret < 0)
else if (ret < 0) {
session_inc_http_err_ctr(s);
goto return_bad_req;
}
}
/* Now we're in HTTP_MSG_DATA or HTTP_MSG_TRAILERS state.
@ -3597,8 +3626,10 @@ int http_process_request_body(struct session *s, struct buffer *req, int an_bit)
missing_data:
/* we get here if we need to wait for more data */
if (req->flags & BF_FULL)
if (req->flags & BF_FULL) {
session_inc_http_err_ctr(s);
goto return_bad_req;
}
if ((req->flags & BF_READ_TIMEOUT) || tick_is_expired(req->analyse_exp, now_ms)) {
txn->status = 408;
@ -4178,8 +4209,10 @@ int http_request_forward_body(struct session *s, struct buffer *req, int an_bit)
if (!ret)
goto missing_data;
else if (ret < 0)
else if (ret < 0) {
session_inc_http_err_ctr(s);
goto return_bad_req;
}
/* otherwise we're in HTTP_MSG_DATA or HTTP_MSG_TRAILERS state */
}
else if (msg->msg_state == HTTP_MSG_DATA_CRLF) {
@ -4194,8 +4227,10 @@ int http_request_forward_body(struct session *s, struct buffer *req, int an_bit)
if (ret == 0)
goto missing_data;
else if (ret < 0)
else if (ret < 0) {
session_inc_http_err_ctr(s);
goto return_bad_req;
}
/* we're in MSG_CHUNK_SIZE now */
}
else if (msg->msg_state == HTTP_MSG_TRAILERS) {
@ -4203,8 +4238,10 @@ int http_request_forward_body(struct session *s, struct buffer *req, int an_bit)
if (ret == 0)
goto missing_data;
else if (ret < 0)
else if (ret < 0) {
session_inc_http_err_ctr(s);
goto return_bad_req;
}
/* we're in HTTP_MSG_DONE now */
}
else {
@ -4512,6 +4549,14 @@ int http_wait_for_response(struct session *s, struct buffer *rep, int an_bit)
n = msg->sol[msg->sl.st.c] - '0';
if (n < 1 || n > 5)
n = 0;
/* when the client triggers a 4xx from the server, it's most often due
* to a missing object or permission. These events should be tracked
* because if they happen often, it may indicate a brute force or a
* vulnerability scan.
*/
if (n == 4)
session_inc_http_err_ctr(s);
if (s->srv)
s->srv->counters.p.http.rsp[n]++;

View File

@ -2483,6 +2483,204 @@ acl_fetch_src_sess_rate(struct proxy *px, struct session *l4, void *l7, int dir,
return acl_fetch_sess_rate(&px->table, test, stktable_lookup_key(&px->table, key));
}
/* set test->i to the cumulated number of sessions in the stksess entry <ts> */
static int
acl_fetch_http_req_cnt(struct stktable *table, struct acl_test *test, struct stksess *ts)
{
test->flags = ACL_TEST_F_VOL_TEST;
test->i = 0;
if (ts != NULL) {
void *ptr = stktable_data_ptr(table, ts, STKTABLE_DT_HTTP_REQ_CNT);
if (!ptr)
return 0; /* parameter not stored */
test->i = stktable_data_cast(ptr, http_req_cnt);
}
return 1;
}
/* set test->i to the cumulated number of sessions from the session's tracked counters */
static int
acl_fetch_trk_http_req_cnt(struct proxy *px, struct session *l4, void *l7, int dir,
struct acl_expr *expr, struct acl_test *test)
{
if (!l4->tracked_counters)
return 0;
return acl_fetch_http_req_cnt(l4->tracked_table, test, l4->tracked_counters);
}
/* set test->i to the cumulated number of session from the session's source
* address in the table pointed to by expr.
*/
static int
acl_fetch_src_http_req_cnt(struct proxy *px, struct session *l4, void *l7, int dir,
struct acl_expr *expr, struct acl_test *test)
{
struct stktable_key *key;
key = tcpv4_src_to_stktable_key(l4);
if (!key)
return 0; /* only TCPv4 is supported right now */
if (expr->arg_len)
px = find_stktable(expr->arg.str);
if (!px)
return 0; /* table not found */
return acl_fetch_http_req_cnt(&px->table, test, stktable_lookup_key(&px->table, key));
}
/* set test->i to the session rate in the stksess entry <ts> over the configured period */
static int
acl_fetch_http_req_rate(struct stktable *table, struct acl_test *test, struct stksess *ts)
{
test->flags = ACL_TEST_F_VOL_TEST;
test->i = 0;
if (ts != NULL) {
void *ptr = stktable_data_ptr(table, ts, STKTABLE_DT_HTTP_REQ_RATE);
if (!ptr)
return 0; /* parameter not stored */
test->i = read_freq_ctr_period(&stktable_data_cast(ptr, http_req_rate),
table->data_arg[STKTABLE_DT_HTTP_REQ_RATE].u);
}
return 1;
}
/* set test->i to the session rate from the session's tracked counters over
* the configured period.
*/
static int
acl_fetch_trk_http_req_rate(struct proxy *px, struct session *l4, void *l7, int dir,
struct acl_expr *expr, struct acl_test *test)
{
if (!l4->tracked_counters)
return 0;
return acl_fetch_http_req_rate(l4->tracked_table, test, l4->tracked_counters);
}
/* set test->i to the session rate from the session's source address in the
* table pointed to by expr, over the configured period.
*/
static int
acl_fetch_src_http_req_rate(struct proxy *px, struct session *l4, void *l7, int dir,
struct acl_expr *expr, struct acl_test *test)
{
struct stktable_key *key;
key = tcpv4_src_to_stktable_key(l4);
if (!key)
return 0; /* only TCPv4 is supported right now */
if (expr->arg_len)
px = find_stktable(expr->arg.str);
if (!px)
return 0; /* table not found */
return acl_fetch_http_req_rate(&px->table, test, stktable_lookup_key(&px->table, key));
}
/* set test->i to the cumulated number of sessions in the stksess entry <ts> */
static int
acl_fetch_http_err_cnt(struct stktable *table, struct acl_test *test, struct stksess *ts)
{
test->flags = ACL_TEST_F_VOL_TEST;
test->i = 0;
if (ts != NULL) {
void *ptr = stktable_data_ptr(table, ts, STKTABLE_DT_HTTP_ERR_CNT);
if (!ptr)
return 0; /* parameter not stored */
test->i = stktable_data_cast(ptr, http_err_cnt);
}
return 1;
}
/* set test->i to the cumulated number of sessions from the session's tracked counters */
static int
acl_fetch_trk_http_err_cnt(struct proxy *px, struct session *l4, void *l7, int dir,
struct acl_expr *expr, struct acl_test *test)
{
if (!l4->tracked_counters)
return 0;
return acl_fetch_http_err_cnt(l4->tracked_table, test, l4->tracked_counters);
}
/* set test->i to the cumulated number of session from the session's source
* address in the table pointed to by expr.
*/
static int
acl_fetch_src_http_err_cnt(struct proxy *px, struct session *l4, void *l7, int dir,
struct acl_expr *expr, struct acl_test *test)
{
struct stktable_key *key;
key = tcpv4_src_to_stktable_key(l4);
if (!key)
return 0; /* only TCPv4 is supported right now */
if (expr->arg_len)
px = find_stktable(expr->arg.str);
if (!px)
return 0; /* table not found */
return acl_fetch_http_err_cnt(&px->table, test, stktable_lookup_key(&px->table, key));
}
/* set test->i to the session rate in the stksess entry <ts> over the configured period */
static int
acl_fetch_http_err_rate(struct stktable *table, struct acl_test *test, struct stksess *ts)
{
test->flags = ACL_TEST_F_VOL_TEST;
test->i = 0;
if (ts != NULL) {
void *ptr = stktable_data_ptr(table, ts, STKTABLE_DT_HTTP_ERR_RATE);
if (!ptr)
return 0; /* parameter not stored */
test->i = read_freq_ctr_period(&stktable_data_cast(ptr, http_err_rate),
table->data_arg[STKTABLE_DT_HTTP_ERR_RATE].u);
}
return 1;
}
/* set test->i to the session rate from the session's tracked counters over
* the configured period.
*/
static int
acl_fetch_trk_http_err_rate(struct proxy *px, struct session *l4, void *l7, int dir,
struct acl_expr *expr, struct acl_test *test)
{
if (!l4->tracked_counters)
return 0;
return acl_fetch_http_err_rate(l4->tracked_table, test, l4->tracked_counters);
}
/* set test->i to the session rate from the session's source address in the
* table pointed to by expr, over the configured period.
*/
static int
acl_fetch_src_http_err_rate(struct proxy *px, struct session *l4, void *l7, int dir,
struct acl_expr *expr, struct acl_test *test)
{
struct stktable_key *key;
key = tcpv4_src_to_stktable_key(l4);
if (!key)
return 0; /* only TCPv4 is supported right now */
if (expr->arg_len)
px = find_stktable(expr->arg.str);
if (!px)
return 0; /* table not found */
return acl_fetch_http_err_rate(&px->table, test, stktable_lookup_key(&px->table, key));
}
/* set test->i to the number of kbytes received from clients matching the stksess entry <ts> */
static int
acl_fetch_kbytes_in(struct stktable *table, struct acl_test *test, struct stksess *ts)
@ -2709,6 +2907,14 @@ static struct acl_kw_list acl_kws = {{ },{
{ "src_sess_cnt", acl_parse_int, acl_fetch_src_sess_cnt, acl_match_int, ACL_USE_TCP4_VOLATILE },
{ "trk_sess_rate", acl_parse_int, acl_fetch_trk_sess_rate, acl_match_int, ACL_USE_NOTHING },
{ "src_sess_rate", acl_parse_int, acl_fetch_src_sess_rate, acl_match_int, ACL_USE_TCP4_VOLATILE },
{ "trk_http_req_cnt", acl_parse_int, acl_fetch_trk_http_req_cnt, acl_match_int, ACL_USE_NOTHING },
{ "src_http_req_cnt", acl_parse_int, acl_fetch_src_http_req_cnt, acl_match_int, ACL_USE_TCP4_VOLATILE },
{ "trk_http_req_rate", acl_parse_int, acl_fetch_trk_http_req_rate, acl_match_int, ACL_USE_NOTHING },
{ "src_http_req_rate", acl_parse_int, acl_fetch_src_http_req_rate, acl_match_int, ACL_USE_TCP4_VOLATILE },
{ "trk_http_err_cnt", acl_parse_int, acl_fetch_trk_http_err_cnt, acl_match_int, ACL_USE_NOTHING },
{ "src_http_err_cnt", acl_parse_int, acl_fetch_src_http_err_cnt, acl_match_int, ACL_USE_TCP4_VOLATILE },
{ "trk_http_err_rate", acl_parse_int, acl_fetch_trk_http_err_rate, acl_match_int, ACL_USE_NOTHING },
{ "src_http_err_rate", acl_parse_int, acl_fetch_src_http_err_rate, acl_match_int, ACL_USE_TCP4_VOLATILE },
{ "trk_kbytes_in", acl_parse_int, acl_fetch_trk_kbytes_in, acl_match_int, ACL_USE_TCP4_VOLATILE },
{ "src_kbytes_in", acl_parse_int, acl_fetch_src_kbytes_in, acl_match_int, ACL_USE_TCP4_VOLATILE },
{ "trk_bytes_in_rate", acl_parse_int, acl_fetch_trk_bytes_in_rate, acl_match_int, ACL_USE_NOTHING },

View File

@ -552,6 +552,10 @@ struct stktable_data_type stktable_data_types[STKTABLE_DATA_TYPES] = {
[STKTABLE_DT_CONN_CUR] = { .name = "conn_cur", .data_length = stktable_data_size(conn_cur) },
[STKTABLE_DT_SESS_CNT] = { .name = "sess_cnt", .data_length = stktable_data_size(sess_cnt) },
[STKTABLE_DT_SESS_RATE] = { .name = "sess_rate", .data_length = stktable_data_size(sess_rate), .arg_type = ARG_T_DELAY },
[STKTABLE_DT_HTTP_REQ_CNT] = { .name = "http_req_cnt", .data_length = stktable_data_size(http_req_cnt) },
[STKTABLE_DT_HTTP_REQ_RATE] = { .name = "http_req_rate", .data_length = stktable_data_size(http_req_rate), .arg_type = ARG_T_DELAY },
[STKTABLE_DT_HTTP_ERR_CNT] = { .name = "http_err_cnt", .data_length = stktable_data_size(http_err_cnt) },
[STKTABLE_DT_HTTP_ERR_RATE] = { .name = "http_err_rate", .data_length = stktable_data_size(http_err_rate), .arg_type = ARG_T_DELAY },
[STKTABLE_DT_BYTES_IN_CNT] = { .name = "bytes_in_cnt", .data_length = stktable_data_size(bytes_in_cnt) },
[STKTABLE_DT_BYTES_IN_RATE] = { .name = "bytes_in_rate", .data_length = stktable_data_size(bytes_in_rate), .arg_type = ARG_T_DELAY },
[STKTABLE_DT_BYTES_OUT_CNT] = { .name = "bytes_out_cnt", .data_length = stktable_data_size(bytes_out_cnt) },