diff --git a/common.h b/common.h index 4e2d4ab..629f580 100644 --- a/common.h +++ b/common.h @@ -109,6 +109,7 @@ struct connection { struct sslhcfg_protocols_item* proto; /* Where to connect to */ /* SOCK_STREAM */ + struct listen_endpoint* endpoint; /* Client-facing listen fd */ enum connection_state state; time_t probe_timeout; diff --git a/doc/max_connections.md b/doc/max_connections.md index 2d3b402..e29d281 100644 --- a/doc/max_connections.md +++ b/doc/max_connections.md @@ -22,22 +22,31 @@ connections: - Use `ulimit` (see bash(3) or your shell's man page) to limit the number of file descriptors. -- In `sslh-select` and `sslh-ev`, you can set -`max_connections` per protocol, and `sslh` will drop -connections after probing if the count is exceeded. -This should help in keeping SSH connections available even -if an attacker is stuffing other protocols. Currently this -does not work for forking protocols (support is planned) +Then, `sslh` provides several mechanisms to limit the number +of concurrent connections, which in turns limits the number +of file descriptors used. -- `sslh-fork` has an explicit design goal to be as simple as +Essentially there are two ways to do this: + +- you can set `max_connections` per protocol, and `sslh` +will drop connections after probing if the count is +exceeded. This should help in keeping SSH connections +available even if an attacker is stuffing other protocols. +Currently this does not work for forking protocols (support +is planned). + +- you can set `max_connections` for each `listen` entry. So +the `sslh` process for each port will limit the number of +concurrent connections to that port. This is similar to the +`udp_max_connections` setting, but for TCP. + +`sslh-select` and `sslh-ev` support both limit types. + +`sslh-fork` has an explicit design goal to be as simple as possible, which makes it impossible to implement limits per protocol (because protocol probing is performed after the -process has forked). Instead, you can set `max_connections` -for each `listen` entry. So the `sslh` process for each port -will limit the number of concurrent connections to that -port. Currently this is not supported in `sslh-select` and -`sslh-ev` (support is planned). - +process has forked). Limits will only work for `listen` +entries. As of sslh 2.2.5, this is an experimental feature and not all use cases have been tested. If 2.2.5 does not exist yet, diff --git a/processes.c b/processes.c index 878889f..68ea4b6 100644 --- a/processes.c +++ b/processes.c @@ -50,6 +50,7 @@ int tidy_connection(struct connection *cnx, struct loop_info* fd_info) fd_info->num_probing--; dec_connections(cnx); + dec_listen_connections(cnx); collection_remove_cnx(fd_info->collection, cnx); return 0; @@ -92,7 +93,7 @@ struct connection* cnx_accept_process(struct loop_info* fd_info, struct listen_e switch (type) { case SOCK_STREAM: - cnx = accept_new_connection(fd, fd_info); + cnx = accept_new_connection(listen_socket, fd_info); if (!cnx) return NULL; break; diff --git a/tcp-listener.c b/tcp-listener.c index 49f6ec8..b6b1e4c 100644 --- a/tcp-listener.c +++ b/tcp-listener.c @@ -118,12 +118,45 @@ void tcp_read_process(struct loop_info* fd_info, } } + + +/* *cnx must have its endpoint field filled; + * Increment the connection count for the listen endpoint. + * Return 1 if connection count is exceeded, 0 otherwise */ +static int inc_listen_connections(struct connection* cnx) +{ + cnx->endpoint->num_connections++; + if (cnx->endpoint->endpoint_cfg->max_connections_is_present) { + int num_cnx = cnx->endpoint->num_connections; + int max_cnx = cnx->endpoint->endpoint_cfg->max_connections; + + print_message(msg_connections, "Endpoint %d +1: %d/%d cnx\n", + cnx->endpoint->socketfd, num_cnx, max_cnx); + if (num_cnx > max_cnx) { + print_message(msg_connections_error, "Endpoint %d: too many connections, dropping\n", cnx->endpoint->socketfd); + return 1; + } + } + return 0; +} + +void dec_listen_connections(struct connection* cnx) +{ + cnx->endpoint->num_connections--; + print_message(msg_connections, "Endpoint %d -1: %d/%d cnx\n", + cnx->endpoint->socketfd, + cnx->endpoint->num_connections, + cnx->endpoint->endpoint_cfg->max_connections); +} + + /* Accepts a connection from the main socket and assigns it to an empty slot. * If no slots are available, allocate another few. If that fails, drop the * connexion */ -struct connection* accept_new_connection(int listen_socket, struct loop_info* fd_info) +struct connection* accept_new_connection(struct listen_endpoint* endpoint, struct loop_info* fd_info) { int in_socket, res; + int listen_socket = endpoint->socketfd; print_message(msg_fd, "accepting from %d\n", listen_socket); @@ -141,12 +174,16 @@ struct connection* accept_new_connection(int listen_socket, struct loop_info* fd close(in_socket); return NULL; } + cnx->endpoint = endpoint; + if (inc_listen_connections(cnx)) { + tidy_connection(cnx, fd_info); + return NULL; + } add_probing_cnx(fd_info, cnx); return cnx; } - /* Connect queue 1 of connection to SSL; returns new file descriptor */ static int connect_queue(struct connection* cnx, struct loop_info* fd_info) diff --git a/tcp-listener.h b/tcp-listener.h index a4ab845..428c2ac 100644 --- a/tcp-listener.h +++ b/tcp-listener.h @@ -6,7 +6,7 @@ #include "tcp-probe.h" void tcp_read_process(struct loop_info* fd_info, int fd); -struct connection* accept_new_connection(int listen_socket, struct loop_info* fd_info); +struct connection* accept_new_connection(struct listen_endpoint* endpoint, struct loop_info* fd_info); void probing_read_process(struct connection* cnx, struct loop_info* fd_info); void cnx_write_process(struct loop_info* fd_info, int fd);