]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: listener: apply a limit on the session rate submitted to SSL
authorWilly Tarreau <w@1wt.eu>
Mon, 7 Oct 2013 18:01:52 +0000 (20:01 +0200)
committerWilly Tarreau <w@1wt.eu>
Tue, 28 Jan 2014 14:50:10 +0000 (15:50 +0100)
Just like the previous commit, we sometimes want to limit the rate of
incoming SSL connections. While it can be done for a frontend, it was
not possible for a whole process, which makes sense when multiple
processes are running on a system to server multiple customers.

The new global "maxsslrate" setting is usable to fix a limit on the
session rate going to the SSL frontends. The limits applies before
the SSL handshake and not after, so that it saves the SSL stack from
expensive key computations that would finally be aborted before being
accounted for.

The same setting may be changed at run time on the CLI using
"set rate-limit ssl-session global".

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

index 9e129de098a3aed2270e15df5d1b6f6e2ce79cc5..50985eb399f0d40cf0178178bc3b0a96dec7f062 100644 (file)
@@ -467,6 +467,7 @@ The following keywords are supported in the "global" section :
    - maxpipes
    - maxsessrate
    - maxsslconn
+   - maxsslrate
    - noepoll
    - nokqueue
    - nopoll
@@ -753,6 +754,18 @@ maxsslconn <number>
   that the limit applies both to incoming and outgoing connections, so one
   connection which is deciphered then ciphered accounts for 2 SSL connections.
 
+maxsslrate <number>
+  Sets the maximum per-process number of SSL sessions per second to <number>.
+  SSL listeners will stop accepting connections when this limit is reached. It
+  can be used to limit the global SSL CPU usage 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. It is also important to
+  note that the sessions are accounted before they enter the SSL stack and not
+  after, which also protects the stack against bad handshakes. Also, lowering
+  tune.maxaccept can improve fairness.
+
 maxzlibmem <number>
   Sets the maximum amount of RAM in megabytes per process usable by the zlib.
   When the maximum amount is reached, future sessions will not compress as long
@@ -12615,6 +12628,13 @@ set rate-limit sessions global <value>
   applies to all frontends and the change has an immediate effect. The value
   is passed in number of sessions per second.
 
+set rate-limit ssl-sessions global <value>
+  Change the process-wide SSL session rate limit, which is set by the global
+  'maxsslrate' 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 sent to the SSL stack. It applies
+  before the handshake in order to protect the stack against handshake abuses.
+
 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 393ddc457f1bef18dfef3161ae0ad090a25a439c..678d4fba500b4b98da8c9891b44d995ce526caac 100644 (file)
@@ -81,10 +81,12 @@ struct global {
 #endif
        struct freq_ctr conn_per_sec;
        struct freq_ctr sess_per_sec;
+       struct freq_ctr ssl_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 ssl_lim, ssl_max;
        int comp_rate_lim;           /* HTTP compression rate limit */
        int maxpipes;           /* max # of pipes */
        int maxsock;            /* max # of sockets */
index cab9d6ebca773c16eeeb8d2bb4bfa555bb98f468..4d1ecd02a495cc440e28b432622e07788be6d617 100644 (file)
@@ -888,6 +888,19 @@ int cfg_parse_global(const char *file, int linenum, char **args, int kwm)
                }
                global.sps_lim = atol(args[1]);
        }
+       else if (!strcmp(args[0], "maxsslrate")) {
+               if (global.ssl_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.ssl_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 b6a4dc4db6ed186626076e871d4de41efa07f303..51b47ca9e9c2df0871c339fff0606a5fd7a46117 100644 (file)
@@ -1462,6 +1462,45 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line)
                                        return 1;
                                }
                        }
+#ifdef USE_OPENSSL
+                       else if (strcmp(args[2], "ssl-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.ssl_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 ssl-sessions' only supports 'global'.\n";
+                                       appctx->st0 = STAT_CLI_PRINT;
+                                       return 1;
+                               }
+                       }
+#endif
                        else if (strcmp(args[2], "http-compression") == 0) {
                                if (strcmp(args[3], "global") == 0) {
                                        int v;
@@ -1482,7 +1521,7 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line)
                                }
                        }
                        else {
-                               appctx->ctx.cli.msg = "'set rate-limit' supports 'connections', 'sessions', and 'http-compression'.\n";
+                               appctx->ctx.cli.msg = "'set rate-limit' supports 'connections', 'sessions', 'ssl-sessions', and 'http-compression'.\n";
                                appctx->st0 = STAT_CLI_PRINT;
                                return 1;
                        }
@@ -2223,6 +2262,11 @@ static int stats_dump_info_to_buffer(struct stream_interface *si)
                     "SessRate: %d\n"
                     "SessRateLimit: %d\n"
                     "MaxSessRate: %d\n"
+#ifdef USE_OPENSSL
+                    "SslRate: %d\n"
+                    "SslRateLimit: %d\n"
+                    "MaxSslRate: %d\n"
+#endif
                     "CompressBpsIn: %u\n"
                     "CompressBpsOut: %u\n"
                     "CompressBpsRateLim: %u\n"
@@ -2251,6 +2295,9 @@ static int stats_dump_info_to_buffer(struct stream_interface *si)
                     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,
+#ifdef USE_OPENSSL
+                    read_freq_ctr(&global.ssl_per_sec), global.ssl_lim, global.ssl_max,
+#endif
                     read_freq_ctr(&global.comp_bps_in), read_freq_ctr(&global.comp_bps_out),
                     global.comp_rate_lim,
 #ifdef USE_ZLIB
index 95a11998f27a5405b070281868fd3bd555b0a494..1ce35de8cde9edc41038917d9af6efe4bed5c1ba 100644 (file)
@@ -294,7 +294,23 @@ void listener_accept(int fd)
                if (max_accept > max)
                        max_accept = max;
        }
+#ifdef USE_OPENSSL
+       if (!(l->options & LI_O_UNLIMITED) && global.ssl_lim && l->bind_conf && l->bind_conf->is_ssl) {
+               int max = freq_ctr_remain(&global.ssl_per_sec, global.ssl_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.ssl_per_sec, global.ssl_lim, 0));
+                       task_schedule(global_listener_queue_task, tick_first(expire, global_listener_queue_task->expire));
+                       return;
+               }
 
+               if (max_accept > max)
+                       max_accept = max;
+       }
+#endif
        if (p && p->fe_sps_lim) {
                int max = freq_ctr_remain(&p->fe_sess_per_sec, p->fe_sps_lim, 0);
 
@@ -435,6 +451,14 @@ void listener_accept(int fd)
                        if (global.sess_per_sec.curr_ctr > global.sps_max)
                                global.sps_max = global.sess_per_sec.curr_ctr;
                }
+#ifdef USE_OPENSSL
+               if (!(l->options & LI_O_UNLIMITED) && l->bind_conf && l->bind_conf->is_ssl) {
+
+                       update_freq_ctr(&global.ssl_per_sec, 1);
+                       if (global.ssl_per_sec.curr_ctr > global.ssl_max)
+                               global.ssl_max = global.ssl_per_sec.curr_ctr;
+               }
+#endif
 
        } /* end of while (max_accept--) */