]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MINOR] tcp: add per-source connection rate limiting
authorWilly Tarreau <w@1wt.eu>
Sat, 5 Jun 2010 17:13:27 +0000 (19:13 +0200)
committerWilly Tarreau <w@1wt.eu>
Mon, 14 Jun 2010 13:10:25 +0000 (15:10 +0200)
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.

doc/configuration.txt
src/proto_tcp.c

index 83666360b75c91fe5457bc8ac50f2e33357b3014..aaeefd0faa7f9482fdf8ff10a036defbeb40cfcf 100644 (file)
@@ -6265,9 +6265,36 @@ src <ip_address>
   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 <integer>
+src_count(backend) <integer>
+  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 <integer>
   Applies to the client's TCP source port. This has a very limited usage.
 
+src_update_count <integer>
+src_update_count(backend) <integer>
+  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(<server>)
 srv_is_up(<backend>/<server>)
   Returns true when the designated server is UP, and false when it is either
index 1c93396089f2fc0081b1ee0052b1b48b09ee8cd8..80c10fdc98a276717a1077c236093aa009d69b01 100644 (file)
@@ -46,7 +46,9 @@
 #include <proto/protocols.h>
 #include <proto/proto_tcp.h>
 #include <proto/proxy.h>
+#include <proto/stick_table.h>
 #include <proto/stream_sock.h>
+#include <proto/task.h>
 
 #ifdef CONFIG_HAP_CTTPROXY
 #include <import/ip_tproxy.h>
@@ -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 },
 }};