]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: listener: add support for limiting the session rate in addition to the connec...
authorWilly Tarreau <w@1wt.eu>
Mon, 7 Oct 2013 16:51:07 +0000 (18:51 +0200)
committerWilly Tarreau <w@1wt.eu>
Tue, 28 Jan 2014 14:49:27 +0000 (15:49 +0100)
It's sometimes useful to be able to limit the connection rate on a machine
running many haproxy instances (eg: per customer) but it removes the ability
for that machine to defend itself against a DoS. Thus, better also provide a
limit on the session rate, which does not include the connections rejected by
"tcp-request connection" rules. This permits to have much higher limits on
the connection rate without having to raise the session rate limit to insane
values.

The limit can be changed on the CLI using "set rate-limit sessions global",
or in the global section using "maxsessrate".

doc/configuration.txt
include/types/global.h
src/cfgparse.c
src/dumpstats.c
src/listener.c

index 030a9a6604f1ccfd15567eea7eb4f786b981015b..9e129de098a3aed2270e15df5d1b6f6e2ce79cc5 100644 (file)
@@ -465,6 +465,7 @@ The following keywords are supported in the "global" section :
    - maxcomprate
    - maxcompcpuusage
    - maxpipes
+   - maxsessrate
    - maxsslconn
    - noepoll
    - nokqueue
@@ -733,6 +734,16 @@ maxpipes <number>
   The splice code dynamically allocates and releases pipes, and can fall back
   to standard copy, so setting this value too low may only impact performance.
 
+maxsessrate <number>
+  Sets the maximum per-process number of sessions per second to <number>.
+  Proxies will stop accepting connections when this limit is reached. It can be
+  used to limit the global capacity regardless of each frontend capacity. It is
+  important to note that this can only be used as a service protection measure,
+  as there will not necessarily be a fair share between frontends when the
+  limit is reached, so it's a good idea to also limit each frontend to some
+  value close to its expected share. Also, lowering tune.maxaccept can improve
+  fairness.
+
 maxsslconn <number>
   Sets the maximum per-process number of concurrent SSL connections to
   <number>. By default there is no SSL-specific limit, which means that the
@@ -12598,6 +12609,12 @@ set rate-limit http-compression global <value>
   passed in number of kilobytes per second. The value is available in the "show
   info" on the line "CompressBpsRateLim" in bytes.
 
+set rate-limit sessions global <value>
+  Change the process-wide session rate limit, which is set by the global
+  'maxsessrate' setting. A value of zero disables the limitation. This limit
+  applies to all frontends and the change has an immediate effect. The value
+  is passed in number of sessions per second.
+
 set table <table> key <key> [data.<data_type> <value>]*
   Create or update a stick-table entry in the table. If the key is not present,
   an entry is inserted. See stick-table in section 4.2 to find all possible
index 7d78d207d589754346b354fca67e6914ade37bb1..393ddc457f1bef18dfef3161ae0ad090a25a439c 100644 (file)
@@ -80,9 +80,11 @@ struct global {
        char *connect_default_ciphers;
 #endif
        struct freq_ctr conn_per_sec;
+       struct freq_ctr sess_per_sec;
        struct freq_ctr comp_bps_in;    /* bytes per second, before http compression */
        struct freq_ctr comp_bps_out;   /* bytes per second, after http compression */
        int cps_lim, cps_max;
+       int sps_lim, sps_max;
        int comp_rate_lim;           /* HTTP compression rate limit */
        int maxpipes;           /* max # of pipes */
        int maxsock;            /* max # of sockets */
index e11730e5abbaa80310bae6e61fa0a70e43077634..cab9d6ebca773c16eeeb8d2bb4bfa555bb98f468 100644 (file)
@@ -875,6 +875,19 @@ int cfg_parse_global(const char *file, int linenum, char **args, int kwm)
                }
                global.cps_lim = atol(args[1]);
        }
+       else if (!strcmp(args[0], "maxsessrate")) {
+               if (global.sps_lim != 0) {
+                       Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]);
+                       err_code |= ERR_ALERT;
+                       goto out;
+               }
+               if (*(args[1]) == 0) {
+                       Alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]);
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto out;
+               }
+               global.sps_lim = atol(args[1]);
+       }
        else if (!strcmp(args[0], "maxcomprate")) {
                if (*(args[1]) == 0) {
                        Alert("parsing [%s:%d] : '%s' expects an integer argument in kb/s.\n", file, linenum, args[0]);
index 227abc54616dfdadfe86821978ed1a1e4b055ea6..b6a4dc4db6ed186626076e871d4de41efa07f303 100644 (file)
@@ -1146,6 +1146,7 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line)
                        }
 
                        global.cps_max = 0;
+                       global.sps_max = 0;
                        return 1;
                }
                else if (strcmp(args[1], "table") == 0) {
@@ -1424,6 +1425,43 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line)
                                        return 1;
                                }
                        }
+                       else if (strcmp(args[2], "sessions") == 0) {
+                               if (strcmp(args[3], "global") == 0) {
+                                       int v;
+
+                                       if (s->listener->bind_conf->level < ACCESS_LVL_ADMIN) {
+                                               appctx->ctx.cli.msg = stats_permission_denied_msg;
+                                               appctx->st0 = STAT_CLI_PRINT;
+                                               return 1;
+                                       }
+
+                                       if (!*args[4]) {
+                                               appctx->ctx.cli.msg = "Expects an integer value.\n";
+                                               appctx->st0 = STAT_CLI_PRINT;
+                                               return 1;
+                                       }
+
+                                       v = atoi(args[4]);
+                                       if (v < 0) {
+                                               appctx->ctx.cli.msg = "Value out of range.\n";
+                                               appctx->st0 = STAT_CLI_PRINT;
+                                               return 1;
+                                       }
+
+                                       global.sps_lim = v;
+
+                                       /* Dequeues all of the listeners waiting for a resource */
+                                       if (!LIST_ISEMPTY(&global_listener_queue))
+                                               dequeue_all_listeners(&global_listener_queue);
+
+                                       return 1;
+                               }
+                               else {
+                                       appctx->ctx.cli.msg = "'set rate-limit sessions' only supports 'global'.\n";
+                                       appctx->st0 = STAT_CLI_PRINT;
+                                       return 1;
+                               }
+                       }
                        else if (strcmp(args[2], "http-compression") == 0) {
                                if (strcmp(args[3], "global") == 0) {
                                        int v;
@@ -1444,7 +1482,7 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line)
                                }
                        }
                        else {
-                               appctx->ctx.cli.msg = "'set rate-limit' supports 'connections' and 'http-compression'.\n";
+                               appctx->ctx.cli.msg = "'set rate-limit' supports 'connections', 'sessions', and 'http-compression'.\n";
                                appctx->st0 = STAT_CLI_PRINT;
                                return 1;
                        }
@@ -2182,6 +2220,9 @@ static int stats_dump_info_to_buffer(struct stream_interface *si)
                     "ConnRate: %d\n"
                     "ConnRateLimit: %d\n"
                     "MaxConnRate: %d\n"
+                    "SessRate: %d\n"
+                    "SessRateLimit: %d\n"
+                    "MaxSessRate: %d\n"
                     "CompressBpsIn: %u\n"
                     "CompressBpsOut: %u\n"
                     "CompressBpsRateLim: %u\n"
@@ -2209,6 +2250,7 @@ static int stats_dump_info_to_buffer(struct stream_interface *si)
 #endif
                     global.maxpipes, pipes_used, pipes_free,
                     read_freq_ctr(&global.conn_per_sec), global.cps_lim, global.cps_max,
+                    read_freq_ctr(&global.sess_per_sec), global.sps_lim, global.sps_max,
                     read_freq_ctr(&global.comp_bps_in), read_freq_ctr(&global.comp_bps_out),
                     global.comp_rate_lim,
 #ifdef USE_ZLIB
index 836ca70a41412be826e45a7789b779eec2337549..95a11998f27a5405b070281868fd3bd555b0a494 100644 (file)
@@ -263,13 +263,31 @@ void listener_accept(int fd)
                return;
        }
 
-       if (global.cps_lim && !(l->options & LI_O_UNLIMITED)) {
+       if (!(l->options & LI_O_UNLIMITED) && global.sps_lim) {
+               int max = freq_ctr_remain(&global.sess_per_sec, global.sps_lim, 0);
+               int expire;
+
+               if (unlikely(!max)) {
+                       /* frontend accept rate limit was reached */
+                       limit_listener(l, &global_listener_queue);
+                       expire = tick_add(now_ms, next_event_delay(&global.sess_per_sec, global.sps_lim, 0));
+                       task_schedule(global_listener_queue_task, tick_first(expire, global_listener_queue_task->expire));
+                       return;
+               }
+
+               if (max_accept > max)
+                       max_accept = max;
+       }
+
+       if (!(l->options & LI_O_UNLIMITED) && global.cps_lim) {
                int max = freq_ctr_remain(&global.conn_per_sec, global.cps_lim, 0);
+               int expire;
 
                if (unlikely(!max)) {
                        /* frontend accept rate limit was reached */
                        limit_listener(l, &global_listener_queue);
-                       task_schedule(global_listener_queue_task, tick_add(now_ms, next_event_delay(&global.conn_per_sec, global.cps_lim, 0)));
+                       expire = tick_add(now_ms, next_event_delay(&global.conn_per_sec, global.cps_lim, 0));
+                       task_schedule(global_listener_queue_task, tick_first(expire, global_listener_queue_task->expire));
                        return;
                }
 
@@ -411,6 +429,13 @@ void listener_accept(int fd)
                        return;
                }
 
+               /* increase the per-process number of cumulated connections */
+               if (!(l->options & LI_O_UNLIMITED)) {
+                       update_freq_ctr(&global.sess_per_sec, 1);
+                       if (global.sess_per_sec.curr_ctr > global.sps_max)
+                               global.sps_max = global.sess_per_sec.curr_ctr;
+               }
+
        } /* end of while (max_accept--) */
 
        /* we've exhausted max_accept, so there is no need to poll again */