diff --git a/doc/configuration.txt b/doc/configuration.txt index 9e129de09..50985eb39 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -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 that the limit applies both to incoming and outgoing connections, so one connection which is deciphered then ciphered accounts for 2 SSL connections. +maxsslrate + Sets the maximum per-process number of SSL sessions per second to . + 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 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 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 + 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 key [data. ]* 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 diff --git a/include/types/global.h b/include/types/global.h index 393ddc457..678d4fba5 100644 --- a/include/types/global.h +++ b/include/types/global.h @@ -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 */ diff --git a/src/cfgparse.c b/src/cfgparse.c index cab9d6ebc..4d1ecd02a 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -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]); diff --git a/src/dumpstats.c b/src/dumpstats.c index b6a4dc4db..51b47ca9e 100644 --- a/src/dumpstats.c +++ b/src/dumpstats.c @@ -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 diff --git a/src/listener.c b/src/listener.c index 95a11998f..1ce35de8c 100644 --- a/src/listener.c +++ b/src/listener.c @@ -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--) */