MEDIUM: listener: apply a limit on the session rate submitted to SSL

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".
This commit is contained in:
Willy Tarreau 2013-10-07 20:01:52 +02:00
parent 93e7c006c1
commit e43d5323c6
5 changed files with 107 additions and 1 deletions

View 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

View 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 */

View 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]);

View 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

View 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--) */