From f552ec48b19116bc52437462e924cba570bb2038 Mon Sep 17 00:00:00 2001 From: "Roger A. Light" Date: Fri, 17 Sep 2021 16:06:02 +0100 Subject: [PATCH] Add global_max_connections option. --- ChangeLog.txt | 2 + man/mosquitto.conf.5.xml | 20 ++++- mosquitto.conf | 14 +++- src/conf.c | 3 + src/mosquitto_broker_internal.h | 1 + src/net.c | 4 +- src/websockets.c | 4 +- .../01-connect-global-max-connections.py | 79 +++++++++++++++++++ test/broker/Makefile | 1 + test/broker/test.py | 1 + 10 files changed, 122 insertions(+), 7 deletions(-) create mode 100755 test/broker/01-connect-global-max-connections.py diff --git a/ChangeLog.txt b/ChangeLog.txt index 081bf362..bb50d104 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -58,6 +58,8 @@ Broker: - Add `mosquitto_client_port()` function for plugins. - Add `global_max_clients` option to allow limiting client sessions globally on the broker. +- Add `global_max_connections` option to allow limiting client onnections globally + on the broker. Client library: - Add MOSQ_OPT_DISABLE_SOCKETPAIR to allow the disabling of the socketpair diff --git a/man/mosquitto.conf.5.xml b/man/mosquitto.conf.5.xml index eb7227d0..97d7ef57 100644 --- a/man/mosquitto.conf.5.xml +++ b/man/mosquitto.conf.5.xml @@ -416,7 +416,7 @@ - See also the max_connections setting, which applies to + See also the setting, which applies to listeners. If you set to 1000 and on a listener to 10, then that means only 10 simultaneous @@ -429,6 +429,24 @@ Reloaded on reload signal. + + count + + + The maximum number of currently connected clients to + allow across the whole broker. + + + + See also the and + settings. + + + This option applies globally. + Defaults to -1 (unlimited) + Reloaded on reload signal. + + file path diff --git a/mosquitto.conf b/mosquitto.conf index 191c8085..76037c3b 100644 --- a/mosquitto.conf +++ b/mosquitto.conf @@ -65,13 +65,19 @@ # disconnected, or has disconnected and still hasn't exceeded its session # expiry interval (MQTT v5). # -# See also the max_connections setting, which applies to listeners. If you set -# global_max_clients to 1000 and max_connections on a listener to 10, then that -# means only 10 simultaneous connections will be allowed at once, with an -# overall maximum of 1000 client sessions. +# See also the global_max_connections setting, and the max_connections setting, +# which applies to listeners. If you set global_max_clients to 1000 and +# max_connections on a listener to 10, then that means only 10 simultaneous +# connections will be allowed at once, with an overall maximum of 1000 client +# sessions. # #global_max_clients -1 +# The maximum number of currently connected clients to allow across the whole +# broker. +# See also the global_max_clients and max_connections settings. +#global_max_connections -1 + # QoS 1 and 2 messages will be allowed inflight per client until this limit # is exceeded. Defaults to 0. (No maximum) # See also max_inflight_messages diff --git a/src/conf.c b/src/conf.c index 021478d9..3d68d13e 100644 --- a/src/conf.c +++ b/src/conf.c @@ -193,6 +193,7 @@ static void config__init_reload(struct mosquitto__config *config) mosquitto__free(config->log_timestamp_format); config->log_timestamp_format = NULL; config->global_max_clients = -1; + config->global_max_connections = -1; config->max_keepalive = 65535; config->max_packet_size = 0; config->max_inflight_messages = 20; @@ -1474,6 +1475,8 @@ static int config__read_file_core(struct mosquitto__config *config, bool reload, #endif }else if(!strcmp(token, "global_max_clients")){ if(conf__parse_int(&token, "global_max_clients", &config->global_max_clients, &saveptr)) return MOSQ_ERR_INVAL; + }else if(!strcmp(token, "global_max_connections")){ + if(conf__parse_int(&token, "global_max_connections", &config->global_max_connections, &saveptr)) return MOSQ_ERR_INVAL; }else if(!strcmp(token, "http_dir")){ #ifdef WITH_WEBSOCKETS if(reload) continue; /* Listeners not valid for reloading. */ diff --git a/src/mosquitto_broker_internal.h b/src/mosquitto_broker_internal.h index 4bb2f2e4..0d76c2ce 100644 --- a/src/mosquitto_broker_internal.h +++ b/src/mosquitto_broker_internal.h @@ -274,6 +274,7 @@ struct mosquitto__config { bool daemon; bool enable_control_api; int global_max_clients; + int global_max_connections; struct mosquitto__listener default_listener; struct mosquitto__listener *listeners; int listener_count; diff --git a/src/net.c b/src/net.c index aca87f07..833a4553 100644 --- a/src/net.c +++ b/src/net.c @@ -197,7 +197,9 @@ struct mosquitto *net__socket_accept(struct mosquitto__listener_sock *listensock } new_context->listener->client_count++; - if(new_context->listener->max_connections > 0 && new_context->listener->client_count > new_context->listener->max_connections){ + if((new_context->listener->max_connections > 0 && new_context->listener->client_count > new_context->listener->max_connections) + || (db.config->global_max_connections > 0 && HASH_CNT(hh_sock, db.contexts_by_sock) > (unsigned int)db.config->global_max_connections)){ + if(db.config->connection_messages == true){ log__printf(NULL, MOSQ_LOG_NOTICE, "Client connection from %s denied: max_connections exceeded.", new_context->address); } diff --git a/src/websockets.c b/src/websockets.c index 9b173ef4..c9f2d7b4 100644 --- a/src/websockets.c +++ b/src/websockets.c @@ -164,7 +164,9 @@ static int callback_mqtt( u->mosq = NULL; return -1; } - if(mosq->listener->max_connections > 0 && mosq->listener->client_count > mosq->listener->max_connections){ + if((mosq->listener->max_connections > 0 && mosq->listener->client_count > mosq->listener->max_connections) + || (db.config->global_max_connections > 0 && HASH_CNT(hh_sock, db.contexts_by_sock) > (unsigned int)db.config->global_max_connections)){ + if(db.config->connection_messages == true){ log__printf(NULL, MOSQ_LOG_NOTICE, "Client connection from %s denied: max_connections exceeded.", mosq->address); } diff --git a/test/broker/01-connect-global-max-connections.py b/test/broker/01-connect-global-max-connections.py new file mode 100755 index 00000000..860d33bb --- /dev/null +++ b/test/broker/01-connect-global-max-connections.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 + +# Test whether global_max_connections works + +from mosq_test_helper import * + +def write_config(filename, port): + with open(filename, 'w') as f: + f.write("listener %d\n" % (port)) + f.write("allow_anonymous true\n") + f.write("global_max_connections 10\n") + +def do_test(): + rc = 1 + + connect_packets_ok = [] + connack_packets_ok = [] + for i in range(0, 10): + connect_packets_ok.append(mosq_test.gen_connect("max-conn-%d"%i, proto_ver=5)) + connack_packets_ok.append(mosq_test.gen_connack(rc=0, proto_ver=5)) + + connect_packet_bad = mosq_test.gen_connect("max-conn-bad", proto_ver=5) + connack_packet_bad = b"" + + port = mosq_test.get_port() + conf_file = os.path.basename(__file__).replace('.py', '.conf') + write_config(conf_file, port) + broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=port) + + socks = [] + try: + # Open all allowed connections, a limit of 10 + for i in range(0, 10): + socks.append(mosq_test.do_client_connect(connect_packets_ok[i], connack_packets_ok[i], port=port)) + + # Try to open an 11th connection + try: + sock_bad = mosq_test.do_client_connect(connect_packet_bad, connack_packet_bad, port=port) + except ConnectionResetError: + # Expected behaviour + pass + + # Close all allowed connections + for i in range(0, 10): + socks[i].close() + + ## Now repeat - check it works as before + + # Open all allowed connections, a limit of 10 + for i in range(0, 10): + socks.append(mosq_test.do_client_connect(connect_packets_ok[i], connack_packets_ok[i], port=port)) + + # Try to open an 11th connection + try: + sock_bad = mosq_test.do_client_connect(connect_packet_bad, connack_packet_bad, port=port) + except ConnectionResetError: + # Expected behaviour + pass + + # Close all allowed connections + for i in range(0, 10): + socks[i].close() + + rc = 0 + except mosq_test.TestError: + pass + except Exception as err: + print(err) + finally: + os.remove(conf_file) + broker.terminate() + broker.wait() + (stdo, stde) = broker.communicate() + if rc: + print(stde.decode('utf-8')) + exit(rc) + +do_test() +exit(0) diff --git a/test/broker/Makefile b/test/broker/Makefile index b7e76732..bd80ce4c 100644 --- a/test/broker/Makefile +++ b/test/broker/Makefile @@ -27,6 +27,7 @@ msg_sequence_test: ./01-connect-allow-anonymous.py ./01-connect-disconnect-v5.py ./01-connect-global-max-clients.py + ./01-connect-global-max-connections.py ./01-connect-max-connections.py ./01-connect-max-keepalive.py ./01-connect-uname-no-password-denied.py diff --git a/test/broker/test.py b/test/broker/test.py index 0e986aa5..273fedea 100755 --- a/test/broker/test.py +++ b/test/broker/test.py @@ -9,6 +9,7 @@ tests = [ (1, './01-connect-allow-anonymous.py'), (1, './01-connect-disconnect-v5.py'), (1, './01-connect-global-max-clients.py'), + (1, './01-connect-global-max-connections.py'), (1, './01-connect-max-connections.py'), (1, './01-connect-max-keepalive.py'), (1, './01-connect-uname-no-password-denied.py'),