Add `global_max_clients` option.

This allows limiting client sessions globally on the broker.
pull/2386/head
Roger A. Light 4 years ago
parent 8acee6647c
commit 02685d49b6

@ -56,6 +56,8 @@ Broker:
`enable_control_api` option.
- Add support for `getPluginInfo` to mosquitto_ctrl.
- Add `mosquitto_client_port()` function for plugins.
- Add `global_max_clients` option to allow limiting client sessions globally
on the broker.
Client library:
- Add MOSQ_OPT_DISABLE_SOCKETPAIR to allow the disabling of the socketpair

@ -403,6 +403,32 @@
<para>Reloaded on reload signal.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>global_max_clients</option> <replaceable>count</replaceable></term>
<listitem>
<para>
The maximum number of client sessions to allow across
the whole broker. In this context a client session means
either a client currently connected via the network, or
a client that has clean_session = False (MQTT v3.x) and
is disconnected, or has disconnected and still hasn't
exceeded its session expiry interval (MQTT v5).
</para>
<para>
See also the max_connections setting, which applies to
listeners. If you set <option>global_max_clients</option>
to 1000 and <option>max_connections</option> 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.
</para>
<para>This option applies globally.</para>
<para>Defaults to -1 (unlimited)</para>
<para>Reloaded on reload signal.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>global_plugin</option> <replaceable>file path</replaceable></term>
<listitem>

@ -59,6 +59,19 @@
# retained message will always be published. This affects all listeners.
#check_retain_source true
# The maximum number of client sessions to allow across the whole broker. In
# this context a client session means either a client currently connected via
# the network, or a client that has clean_session = False (MQTT v3.x) and is
# 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.
#
#global_max_clients -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
@ -256,8 +269,10 @@
#http_dir
# The maximum number of client connections to allow. This is
# a per listener setting.
# Default is -1, which means unlimited connections.
# a per listener setting. Use global_max_clients if you wish to enforce a
# client limit across the whole broker.
# Default is -1, which means unlimited connections, unless otherwise limited by
# global_max_clients.
# Note that other process limits mean that unlimited connections
# are not really possible. Typically the default maximum number of
# connections possible is around 1024.

@ -192,6 +192,7 @@ static void config__init_reload(struct mosquitto__config *config)
config->log_timestamp = true;
mosquitto__free(config->log_timestamp_format);
config->log_timestamp_format = NULL;
config->global_max_clients = -1;
config->max_keepalive = 65535;
config->max_packet_size = 0;
config->max_inflight_messages = 20;
@ -1471,6 +1472,8 @@ static int config__read_file_core(struct mosquitto__config *config, bool reload,
#else
log__printf(NULL, MOSQ_LOG_WARNING, "Warning: $CONTROL support not available (enable_control_api).");
#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, "http_dir")){
#ifdef WITH_WEBSOCKETS
if(reload) continue; /* Listeners not valid for reloading. */

@ -207,6 +207,14 @@ int connect__on_authorised(struct mosquitto *context, void *auth_data_out, uint1
do_disconnect(found_context, MOSQ_ERR_SUCCESS);
}
if(db.config->global_max_clients > 0 && HASH_CNT(hh_id, db.contexts_by_id) >= (unsigned int)db.config->global_max_clients){
if(context->protocol == mosq_p_mqtt5){
send__connack(context, 0, MQTT_RC_SERVER_BUSY, NULL);
}
rc = MOSQ_ERR_INVAL;
goto error;
}
rc = acl__find_acls(context);
if(rc){
free(auth_data_out);

@ -273,6 +273,7 @@ struct mosquitto__config {
int cmd_port_count;
bool daemon;
bool enable_control_api;
int global_max_clients;
struct mosquitto__listener default_listener;
struct mosquitto__listener *listeners;
int listener_count;

@ -0,0 +1,72 @@
#!/usr/bin/env python3
# Test whether global_max_clients 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_clients 10\n")
def do_test():
rc = 1
connect_packets_ok = []
connack_packets_ok = []
connect_props = mqtt5_props.gen_uint32_prop(mqtt5_props.PROP_SESSION_EXPIRY_INTERVAL, 60)
for i in range(0, 10):
connect_packets_ok.append(mosq_test.gen_connect("max-conn-%d"%i, proto_ver=5, properties=connect_props))
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 = mosq_test.gen_connack(rc=mqtt5_rc.MQTT_RC_SERVER_BUSY, proto_ver=5, property_helper=False)
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()
## Session expiry means those clients sessions are still active
# 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
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)

@ -26,6 +26,7 @@ msg_sequence_test:
./01-connect-575314.py
./01-connect-allow-anonymous.py
./01-connect-disconnect-v5.py
./01-connect-global-max-clients.py
./01-connect-max-connections.py
./01-connect-max-keepalive.py
./01-connect-uname-no-password-denied.py

@ -8,6 +8,7 @@ tests = [
(1, './01-connect-575314.py'),
(1, './01-connect-allow-anonymous.py'),
(1, './01-connect-disconnect-v5.py'),
(1, './01-connect-global-max-clients.py'),
(1, './01-connect-max-connections.py'),
(1, './01-connect-max-keepalive.py'),
(1, './01-connect-uname-no-password-denied.py'),

Loading…
Cancel
Save