From: Willy Tarreau Date: Wed, 23 Jun 2010 09:44:09 +0000 (+0200) Subject: [MEDIUM] session-counters: add HTTP req/err tracking X-Git-Tag: v1.5-dev8~510 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=da7ff64aa917dec26ee0f2932a18d2cd4b4fd54b;p=thirdparty%2Fhaproxy.git [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. --- diff --git a/include/proto/session.h b/include/proto/session.h index 133e6c9c88..a5dd881b1a 100644 --- a/include/proto/session.h +++ b/include/proto/session.h @@ -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 */ /* diff --git a/include/types/stick_table.h b/include/types/stick_table.h index a1bfc1acf9..45eac020aa 100644 --- a/include/types/stick_table.h +++ b/include/types/stick_table.h @@ -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; diff --git a/src/proto_http.c b/src/proto_http.c index 57ab14b43c..7e28762de5 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -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]++; diff --git a/src/session.c b/src/session.c index aaa0622b88..1266935092 100644 --- a/src/session.c +++ b/src/session.c @@ -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 */ +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 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 */ +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 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 */ 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 }, diff --git a/src/stick_table.c b/src/stick_table.c index 8b216aeded..46d5e1e391 100644 --- a/src/stick_table.c +++ b/src/stick_table.c @@ -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) },