Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9c1683a

Browse files
lukebakkenmichaelklishin
authored andcommitted
Add mqtt.max_connections per-node connection limit
Add the `mqtt.max_connections` configuration key, which sets a node-wide limit on the number of concurrent MQTT connections. The limit is checked in `rabbit_mqtt_processor:process_connect/5` as the first step in the CONNECT packet handler, before authentication. When exceeded, the broker returns a CONNACK with reason code `quota_exceeded` (MQTT 5) or return code `not_authorized` (MQTT 3.x/4). The active connection count is obtained via `ets:info(persistent_term:get(?PG_SCOPE), size)`. The MQTT plugin creates a node-local PG scope in `rabbit_mqtt_sup`; each connection joins it via `pg:join/3` in `register_client_id/4`, using `{VHost, ClientId}` as the group key. Since the MQTT spec requires unique client IDs per vhost, which RabbitMQ enforces, each group has exactly one member. The PG scope ETS table therefore has one row per active connection node-wide, covering all listeners and transports (plain TCP, TLS, and Web MQTT). `ets:info/2` reads this count in O(1) - important given that MQTT nodes can host a very large number of connections. The check runs before `register_client_id/4`, so the current connection is not yet counted; the limit comparison is `>= Limit`. When `mqtt.max_connections` is absent, `application:get_env/3` returns `infinity` and no check is performed. A `node_connection_limit` test is added to the `limit` group in `auth_SUITE`.
1 parent 11f901d commit 9c1683a

4 files changed

Lines changed: 64 additions & 2 deletions

File tree

deps/rabbitmq_mqtt/priv/schema/rabbitmq_mqtt.schema

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,19 @@ end}.
160160
{datatype, integer}
161161
]}.
162162

163+
{mapping, "mqtt.max_connections", "rabbitmq_mqtt.max_connections",
164+
[{datatype, [{atom, infinity}, integer]}, {validators, ["non_negative_integer"]}]}.
165+
166+
{translation, "rabbitmq_mqtt.max_connections",
167+
fun(Conf) ->
168+
case cuttlefish:conf_get("mqtt.max_connections", Conf, undefined) of
169+
undefined -> cuttlefish:unset();
170+
infinity -> infinity;
171+
Val when is_integer(Val) -> Val;
172+
_ -> cuttlefish:invalid("should be a non-negative integer")
173+
end
174+
end}.
175+
163176
{mapping, "mqtt.ssl_cert_login", "rabbitmq_mqtt.ssl_cert_login", [
164177
{datatype, {enum, [true, false]}}]}.
165178

deps/rabbitmq_mqtt/src/rabbit_mqtt_processor.erl

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ process_connect(
188188
end,
189189
Result0 =
190190
maybe
191+
ok ?= check_node_connection_limit(),
191192
ok ?= check_extended_auth(ConnectProps),
192193
{ok, ClientId1} ?= extract_client_id_from_certificate(ClientId0, Socket),
193194
{ok, ClientId} ?= ensure_client_id(ClientId1, CleanStart, ProtoVer),
@@ -1081,6 +1082,33 @@ check_vhost_exists(VHost, Username, PeerIp) ->
10811082
{error, ?RC_BAD_USER_NAME_OR_PASSWORD}
10821083
end.
10831084

1085+
check_node_connection_limit() ->
1086+
case application:get_env(rabbitmq_mqtt, max_connections, infinity) of
1087+
infinity ->
1088+
ok;
1089+
Limit when is_integer(Limit), Limit >= 0 ->
1090+
PgScope = persistent_term:get(?PG_SCOPE),
1091+
%% pg:init/1 creates a named ETS table using the scope atom as the table
1092+
%% name. Each row holds one group: {Group, AllPids, LocalPids}. Since
1093+
%% MQTT enforces unique client IDs per vhost, every connected client
1094+
%% occupies exactly one group, so the table size equals the number of
1095+
%% active connections node-wide across all listeners and transports.
1096+
%% We use ets:info/2 (O(1)) rather than pg:which_groups/1, which
1097+
%% performs a full table scan via ets:match/2 and is O(N) — too
1098+
%% expensive when the node hosts a large number of MQTT connections.
1099+
%% Note: OTP-27.3.4.11 source code used as a reference.
1100+
case ets:info(PgScope, size) of
1101+
ActiveConns when is_integer(ActiveConns), ActiveConns >= Limit ->
1102+
?LOG_ERROR("MQTT connection failed: node connection limit ~tp is reached",
1103+
[Limit]),
1104+
{error, ?RC_QUOTA_EXCEEDED};
1105+
_ ->
1106+
ok
1107+
end;
1108+
_ ->
1109+
ok
1110+
end.
1111+
10841112
check_vhost_connection_limit(VHost) ->
10851113
case rabbit_vhost_limit:is_over_connection_limit(VHost) of
10861114
false ->

deps/rabbitmq_mqtt/test/auth_SUITE.erl

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ sub_groups() ->
121121
{limit, [shuffle],
122122
[vhost_connection_limit,
123123
vhost_queue_limit,
124-
user_connection_limit
124+
user_connection_limit,
125+
node_connection_limit
125126
]}
126127
].
127128

@@ -359,6 +360,10 @@ init_per_testcase(T, Config)
359360
SetupProcess = setup_rabbit_auth_backend_mqtt_mock(Config),
360361
rabbit_ct_helpers:set_config(Config, {mock_setup_process, SetupProcess});
361362

363+
init_per_testcase(T = node_connection_limit, Config) ->
364+
rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
365+
[rabbitmq_mqtt, max_connections, 0]),
366+
testcase_started(Config, T);
362367
init_per_testcase(Testcase, Config) ->
363368
testcase_started(Config, Testcase).
364369

@@ -499,6 +504,11 @@ end_per_testcase(T, Config)
499504
SetupProcess ! stop,
500505
close_all_connections(Config);
501506

507+
end_per_testcase(node_connection_limit = T, Config) ->
508+
rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env,
509+
[rabbitmq_mqtt, max_connections, infinity]),
510+
close_all_connections(Config),
511+
rabbit_ct_helpers:testcase_finished(Config, T);
502512
end_per_testcase(Testcase, Config) ->
503513
close_all_connections(Config),
504514
rabbit_ct_helpers:testcase_finished(Config, Testcase).
@@ -1321,6 +1331,12 @@ user_connection_limit(Config) ->
13211331
ok = emqtt:disconnect(C1),
13221332
ok = rabbit_ct_broker_helpers:clear_user_limits(Config, DefaultUser, max_connections).
13231333

1334+
node_connection_limit(Config) ->
1335+
{ok, C} = connect_anonymous(Config, <<"client1">>),
1336+
ExpectedError = expected_connection_limit_error(Config),
1337+
unlink(C),
1338+
?assertMatch({error, {ExpectedError, _}}, emqtt:connect(C)).
1339+
13241340
expected_connection_limit_error(Config) ->
13251341
case ?config(mqtt_version, Config) of
13261342
v4 ->

deps/rabbitmq_mqtt/test/config_schema_SUITE_data/rabbitmq_mqtt.snippets

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,11 @@
187187
[{rabbitmq_mqtt, [
188188
{message_interceptors, []}
189189
]}],
190-
[]}
190+
[]},
191+
192+
{max_connections,
193+
"mqtt.max_connections = 10",
194+
[{rabbitmq_mqtt,[{max_connections, 10}]}],
195+
[rabbitmq_mqtt]}
191196

192197
].

0 commit comments

Comments
 (0)