From: Willy Tarreau Date: Sat, 5 Jun 2010 17:13:27 +0000 (+0200) Subject: [MINOR] tcp: add per-source connection rate limiting X-Git-Tag: v1.5-dev8~558 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=a975b8f381acc5a7486a8287d5ef6495dbd0b72b;p=thirdparty%2Fhaproxy.git [MINOR] tcp: add per-source connection rate limiting This change makes use of the stick-tables to keep track of any source address activity. Two ACLs make it possible to check the count of an entry or update it and act accordingly. The typical usage will be to reject a TCP request upon match of an excess value. --- diff --git a/doc/configuration.txt b/doc/configuration.txt index 83666360b7..aaeefd0faa 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -6265,9 +6265,36 @@ src certain resources such as statistics. Note that it is the TCP-level source address which is used, and not the address of a client behind a proxy. +src_count +src_count(backend) + Returns the number of occurrences of the source IPv4 address in the current + backend's stick-table or in the designated stick-table. If the address is not + found, zero is returned. + src_port Applies to the client's TCP source port. This has a very limited usage. +src_update_count +src_update_count(backend) + Creates or updates the entry associated to the source IPv4 address in the + current backend's stick-table or in the designated stick-table. This table + must be configured to store the "conn_cum" data type, otherwise the match + will be ignored. The current count is incremented by one, and the expiration + timer refreshed. The updated count is returned, so this match can't return + zero. This is used to reject service abusers based on their source address. + + Example : + # This frontend limits incoming SSH connections to 3 per 10 second for + # each source address, and rejects excess connections until a 10 second + # silence is observed. At most 20 addresses are tracked. + listen ssh + bind :22 + mode tcp + maxconn 100 + stick-table type ip size 20 expire 10s store conn_cum + tcp-request content reject if { src_update_count gt 3 } + server local 127.0.0.1:22 + srv_is_up() srv_is_up(/) Returns true when the designated server is UP, and false when it is either diff --git a/src/proto_tcp.c b/src/proto_tcp.c index 1c93396089..80c10fdc98 100644 --- a/src/proto_tcp.c +++ b/src/proto_tcp.c @@ -46,7 +46,9 @@ #include #include #include +#include #include +#include #ifdef CONFIG_HAP_CTTPROXY #include @@ -1018,6 +1020,87 @@ pattern_fetch_dport(struct proxy *px, struct session *l4, void *l7, int dir, return 1; } +/* set test->i to the number of connections from the session's source address + * in the table pointed to by expr. + */ +static int +acl_fetch_src_count(struct proxy *px, struct session *l4, void *l7, int dir, + struct acl_expr *expr, struct acl_test *test) +{ + struct stksess *ts; + + /* right now we only support IPv4 */ + if (l4->cli_addr.ss_family != AF_INET) + return 0; + + if (expr->arg_len) { + /* another table was designated, we must look for it */ + for (px = proxy; px; px = px->next) + if (strcmp(px->id, expr->arg.str) == 0) + break; + } + if (!px) + return 0; /* table not found */ + + static_table_key.key = (void *)&((struct sockaddr_in *)&l4->frt_addr)->sin_addr; + test->flags = ACL_TEST_F_VOL_TEST; + test->i = 0; + if ((ts = stktable_lookup_key(&px->table, &static_table_key)) != NULL) { + void *ptr = stktable_data_ptr(&px->table, ts, STKTABLE_DT_CONN_CUM); + if (!ptr) + return 0; /* parameter not stored */ + test->i = stktable_data_cast(ptr, conn_cum); + } + + return 1; +} + +/* set test->i to the number of connections from the session's source address + * in the table pointed to by expr, after updating it. + */ +static int +acl_fetch_src_update_count(struct proxy *px, struct session *l4, void *l7, int dir, + struct acl_expr *expr, struct acl_test *test) +{ + struct stksess *ts; + void *ptr; + + /* right now we only support IPv4 */ + if (l4->cli_addr.ss_family != AF_INET) + return 0; + + if (expr->arg_len) { + /* another table was designated, we must look for it */ + for (px = proxy; px; px = px->next) + if (strcmp(px->id, expr->arg.str) == 0) + break; + } + if (!px) + return 0; + + static_table_key.key = (void *)&((struct sockaddr_in *)&l4->frt_addr)->sin_addr; + if ((ts = stktable_lookup_key(&px->table, &static_table_key)) == NULL) { + /* entry does not exist, initialize a new one */ + ts = stksess_new(&px->table, &static_table_key); + if (!ts) + return 0; + stktable_store(&px->table, ts); + } + else if (px->table.expire) { + /* if entries can expire, let's update the entry and the table */ + ts->expire = tick_add(now_ms, MS_TO_TICKS(px->table.expire)); + px->table.exp_task->expire = px->table.exp_next = tick_first(ts->expire, px->table.exp_next); + task_queue(px->table.exp_task); + } + + ptr = stktable_data_ptr(&px->table, ts, STKTABLE_DT_CONN_CUM); + if (!ptr) + return 0; /* parameter not stored in this table */ + + test->i = ++stktable_data_cast(ptr, conn_cum); + test->flags = ACL_TEST_F_VOL_TEST; + return 1; +} static struct cfg_kw_list cfg_kws = {{ },{ { CFG_LISTEN, "tcp-request", tcp_parse_tcp_req }, @@ -1030,6 +1113,8 @@ static struct acl_kw_list acl_kws = {{ },{ { "src", acl_parse_ip, acl_fetch_src, acl_match_ip, ACL_USE_TCP4_PERMANENT|ACL_MAY_LOOKUP }, { "dst", acl_parse_ip, acl_fetch_dst, acl_match_ip, ACL_USE_TCP4_PERMANENT|ACL_MAY_LOOKUP }, { "dst_port", acl_parse_int, acl_fetch_dport, acl_match_int, ACL_USE_TCP_PERMANENT }, + { "src_count", acl_parse_int, acl_fetch_src_count,acl_match_int, ACL_USE_TCP4_PERMANENT }, + { "src_update_count", acl_parse_int, acl_fetch_src_update_count, acl_match_int, ACL_USE_TCP4_PERMANENT }, { NULL, NULL, NULL, NULL }, }};