feat(config): new config for emqx 5.0

new config for emqx 5.0
This commit is contained in:
Shawn 2021-07-17 18:09:08 +08:00 committed by GitHub
commit 6f58c7815d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
97 changed files with 4249 additions and 4899 deletions

View File

@ -1,7 +1,8 @@
EMQX_NAME=emqx EMQX_NAME=emqx
EMQX_CLUSTER__DISCOVERY=static EMQX_CLUSTER__DISCOVERY_STRATEGY=static
EMQX_CLUSTER__STATIC__SEEDS="emqx@node1.emqx.io, emqx@node2.emqx.io" EMQX_CLUSTER__STATIC__SEEDS="[emqx@node1.emqx.io, emqx@node2.emqx.io]"
EMQX_LISTENER__TCP__EXTERNAL__PROXY_PROTOCOL=on EMQX_ZONES__DEFAULT__LISTENERS__MQTT_TCP__PROXY_PROTOCOL=true
EMQX_LISTENER__WS__EXTERNAL__PROXY_PROTOCOL=on EMQX_ZONES__DEFAULT__LISTENERS__MQTT_WS__PROXY_PROTOCOL=true
EMQX_LOG__LEVEL=debug EMQX_LOG__CONSOLE_HANDLER__ENABLE=true
EMQX_LOADED_PLUGINS=emqx_sn EMQX_LOG__CONSOLE_HANDLER__LEVEL=debug
EMQX_LOG__PRIMARY_LEVEL=debug

View File

@ -10,5 +10,4 @@ EMQX_AUTH__PGSQL__PASSWORD=public
EMQX_AUTH__PGSQL__DATABASE=mqtt EMQX_AUTH__PGSQL__DATABASE=mqtt
EMQX_AUTH__REDIS__SERVER=redis_server:6379 EMQX_AUTH__REDIS__SERVER=redis_server:6379
EMQX_AUTH__REDIS__PASSWORD=public EMQX_AUTH__REDIS__PASSWORD=public
CUTTLEFISH_ENV_OVERRIDE_PREFIX=EMQX_
HOCON_ENV_OVERRIDE_PREFIX=EMQX_ HOCON_ENV_OVERRIDE_PREFIX=EMQX_

View File

@ -36,10 +36,9 @@ jobs:
timeout-minutes: 5 timeout-minutes: 5
run: | run: |
set -e -u -x set -e -u -x
echo "CUTTLEFISH_ENV_OVERRIDE_PREFIX=EMQX_" >> .ci/docker-compose-file/conf.cluster.env
echo "HOCON_ENV_OVERRIDE_PREFIX=EMQX_" >> .ci/docker-compose-file/conf.cluster.env echo "HOCON_ENV_OVERRIDE_PREFIX=EMQX_" >> .ci/docker-compose-file/conf.cluster.env
echo "EMQX_ZONE__EXTERNAL__RETRY_INTERVAL=2s" >> .ci/docker-compose-file/conf.cluster.env echo "EMQX_ZONES__DEFAULT__MQTT__RETRY_INTERVAL=2s" >> .ci/docker-compose-file/conf.cluster.env
echo "EMQX_MQTT__MAX_TOPIC_ALIAS=10" >> .ci/docker-compose-file/conf.cluster.env echo "EMQX_ZONES__DEFAULT__MQTT__MAX_TOPIC_ALIAS=10" >> .ci/docker-compose-file/conf.cluster.env
docker-compose \ docker-compose \
-f .ci/docker-compose-file/docker-compose-emqx-cluster.yaml \ -f .ci/docker-compose-file/docker-compose-emqx-cluster.yaml \
-f .ci/docker-compose-file/docker-compose-python.yaml \ -f .ci/docker-compose-file/docker-compose-python.yaml \
@ -118,8 +117,8 @@ jobs:
--set image.pullPolicy=Never \ --set image.pullPolicy=Never \
--set emqxAclConfig="" \ --set emqxAclConfig="" \
--set image.pullPolicy=Never \ --set image.pullPolicy=Never \
--set emqxConfig.EMQX_ZONE__EXTERNAL__RETRY_INTERVAL=2s \ --set emqxConfig.EMQX_ZONES__DEFAULT__MQTT__RETRY_INTERVAL=2s \
--set emqxConfig.EMQX_MQTT__MAX_TOPIC_ALIAS=10 \ --set emqxConfig.EMQX_ZONES__DEFAULT__MQTT__MAX_TOPIC_ALIAS=10 \
deploy/charts/emqx \ deploy/charts/emqx \
--debug --debug

File diff suppressed because it is too large Load Diff

View File

@ -35,12 +35,6 @@
-define(ERTS_MINIMUM_REQUIRED, "10.0"). -define(ERTS_MINIMUM_REQUIRED, "10.0").
%%--------------------------------------------------------------------
%% Configs
%%--------------------------------------------------------------------
-define(NO_PRIORITY_TABLE, none).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Topics' prefix: $SYS | $queue | $share %% Topics' prefix: $SYS | $queue | $share
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------

View File

@ -12,7 +12,7 @@
[ {gproc, {git, "https://github.com/uwiger/gproc", {tag, "0.8.0"}}} [ {gproc, {git, "https://github.com/uwiger/gproc", {tag, "0.8.0"}}}
, {jiffy, {git, "https://github.com/emqx/jiffy", {tag, "1.0.5"}}} , {jiffy, {git, "https://github.com/emqx/jiffy", {tag, "1.0.5"}}}
, {cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.8.2"}}} , {cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.8.2"}}}
, {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.8.0"}}} , {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.8.2"}}}
, {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.10.3"}}} , {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.10.3"}}}
, {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.5.1"}}} , {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.5.1"}}}
, {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.10.3"}}} , {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.10.3"}}}

View File

@ -28,29 +28,23 @@
-spec(authenticate(emqx_types:clientinfo()) -> -spec(authenticate(emqx_types:clientinfo()) ->
ok | {ok, binary()} | {continue, map()} | {continue, binary(), map()} | {error, term()}). ok | {ok, binary()} | {continue, map()} | {continue, binary(), map()} | {error, term()}).
authenticate(Credential = #{zone := Zone}) -> authenticate(Credential) ->
%% TODO: Rename to bypass_authentication run_hooks('client.authenticate', [Credential], ok).
case emqx_zone:get_env(Zone, bypass_auth_plugins, false) of
true ->
ok;
false ->
run_hooks('client.authenticate', [Credential], ok)
end.
%% @doc Check ACL %% @doc Check ACL
-spec(authorize(emqx_types:clientinfo(), emqx_types:pubsub(), emqx_types:topic()) -spec authorize(emqx_types:clientinfo(), emqx_types:pubsub(), emqx_types:topic())
-> allow | deny). -> allow | deny.
authorize(ClientInfo, PubSub, Topic) -> authorize(ClientInfo = #{zone := Zone, listener := Listener}, PubSub, Topic) ->
case emqx_acl_cache:is_enabled() of case emqx_acl_cache:is_enabled(Zone, Listener) of
true -> check_authorization_cache(ClientInfo, PubSub, Topic); true -> check_authorization_cache(ClientInfo, PubSub, Topic);
false -> do_authorize(ClientInfo, PubSub, Topic) false -> do_authorize(ClientInfo, PubSub, Topic)
end. end.
check_authorization_cache(ClientInfo, PubSub, Topic) -> check_authorization_cache(ClientInfo = #{zone := Zone, listener := Listener}, PubSub, Topic) ->
case emqx_acl_cache:get_acl_cache(PubSub, Topic) of case emqx_acl_cache:get_acl_cache(Zone, Listener, PubSub, Topic) of
not_found -> not_found ->
AclResult = do_authorize(ClientInfo, PubSub, Topic), AclResult = do_authorize(ClientInfo, PubSub, Topic),
emqx_acl_cache:put_acl_cache(PubSub, Topic, AclResult), emqx_acl_cache:put_acl_cache(Zone, Listener, PubSub, Topic, AclResult),
AclResult; AclResult;
AclResult -> AclResult AclResult -> AclResult
end. end.

View File

@ -18,15 +18,15 @@
-include("emqx.hrl"). -include("emqx.hrl").
-export([ list_acl_cache/0 -export([ list_acl_cache/2
, get_acl_cache/2 , get_acl_cache/4
, put_acl_cache/3 , put_acl_cache/5
, cleanup_acl_cache/0 , cleanup_acl_cache/2
, empty_acl_cache/0 , empty_acl_cache/0
, dump_acl_cache/0 , dump_acl_cache/0
, get_cache_max_size/0 , get_cache_max_size/2
, get_cache_ttl/0 , get_cache_ttl/2
, is_enabled/0 , is_enabled/2
, drain_cache/0 , drain_cache/0
]). ]).
@ -50,43 +50,45 @@ cache_k(PubSub, Topic)-> {PubSub, Topic}.
cache_v(AclResult)-> {AclResult, time_now()}. cache_v(AclResult)-> {AclResult, time_now()}.
drain_k() -> {?MODULE, drain_timestamp}. drain_k() -> {?MODULE, drain_timestamp}.
-spec(is_enabled() -> boolean()). -spec(is_enabled(atom(), atom()) -> boolean()).
is_enabled() -> is_enabled(Zone, Listener) ->
application:get_env(emqx, enable_acl_cache, true). emqx_config:get_listener_conf(Zone, Listener, [acl, cache, enable]).
-spec(get_cache_max_size() -> integer()). -spec(get_cache_max_size(atom(), atom()) -> integer()).
get_cache_max_size() -> get_cache_max_size(Zone, Listener) ->
application:get_env(emqx, acl_cache_max_size, 32). emqx_config:get_listener_conf(Zone, Listener, [acl, cache, max_size]).
-spec(get_cache_ttl() -> integer()). -spec(get_cache_ttl(atom(), atom()) -> integer()).
get_cache_ttl() -> get_cache_ttl(Zone, Listener) ->
application:get_env(emqx, acl_cache_ttl, 60000). emqx_config:get_listener_conf(Zone, Listener, [acl, cache, ttl]).
-spec(list_acl_cache() -> [acl_cache_entry()]). -spec(list_acl_cache(atom(), atom()) -> [acl_cache_entry()]).
list_acl_cache() -> list_acl_cache(Zone, Listener) ->
cleanup_acl_cache(), cleanup_acl_cache(Zone, Listener),
map_acl_cache(fun(Cache) -> Cache end). map_acl_cache(fun(Cache) -> Cache end).
%% We'll cleanup the cache before replacing an expired acl. %% We'll cleanup the cache before replacing an expired acl.
-spec(get_acl_cache(emqx_types:pubsub(), emqx_topic:topic()) -> (acl_result() | not_found)). -spec get_acl_cache(atom(), atom(), emqx_types:pubsub(), emqx_topic:topic()) ->
get_acl_cache(PubSub, Topic) -> acl_result() | not_found.
get_acl_cache(Zone, Listener, PubSub, Topic) ->
case erlang:get(cache_k(PubSub, Topic)) of case erlang:get(cache_k(PubSub, Topic)) of
undefined -> not_found; undefined -> not_found;
{AclResult, CachedAt} -> {AclResult, CachedAt} ->
if_expired(CachedAt, if_expired(get_cache_ttl(Zone, Listener), CachedAt,
fun(false) -> fun(false) ->
AclResult; AclResult;
(true) -> (true) ->
cleanup_acl_cache(), cleanup_acl_cache(Zone, Listener),
not_found not_found
end) end)
end. end.
%% If the cache get full, and also the latest one %% If the cache get full, and also the latest one
%% is expired, then delete all the cache entries %% is expired, then delete all the cache entries
-spec(put_acl_cache(emqx_types:pubsub(), emqx_topic:topic(), acl_result()) -> ok). -spec put_acl_cache(atom(), atom(), emqx_types:pubsub(), emqx_topic:topic(), acl_result())
put_acl_cache(PubSub, Topic, AclResult) -> -> ok.
MaxSize = get_cache_max_size(), true = (MaxSize =/= 0), put_acl_cache(Zone, Listener, PubSub, Topic, AclResult) ->
MaxSize = get_cache_max_size(Zone, Listener), true = (MaxSize =/= 0),
Size = get_cache_size(), Size = get_cache_size(),
case Size < MaxSize of case Size < MaxSize of
true -> true ->
@ -94,7 +96,7 @@ put_acl_cache(PubSub, Topic, AclResult) ->
false -> false ->
NewestK = get_newest_key(), NewestK = get_newest_key(),
{_AclResult, CachedAt} = erlang:get(NewestK), {_AclResult, CachedAt} = erlang:get(NewestK),
if_expired(CachedAt, if_expired(get_cache_ttl(Zone, Listener), CachedAt,
fun(true) -> fun(true) ->
% all cache expired, cleanup first % all cache expired, cleanup first
empty_acl_cache(), empty_acl_cache(),
@ -121,10 +123,10 @@ evict_acl_cache() ->
decr_cache_size(). decr_cache_size().
%% cleanup all the expired cache entries %% cleanup all the expired cache entries
-spec(cleanup_acl_cache() -> ok). -spec(cleanup_acl_cache(atom(), atom()) -> ok).
cleanup_acl_cache() -> cleanup_acl_cache(Zone, Listener) ->
keys_queue_set( keys_queue_set(
cleanup_acl(keys_queue_get())). cleanup_acl(get_cache_ttl(Zone, Listener), keys_queue_get())).
get_oldest_key() -> get_oldest_key() ->
keys_queue_pick(queue_front()). keys_queue_pick(queue_front()).
@ -174,16 +176,16 @@ update_acl(K, V) ->
erlang:put(K, V), erlang:put(K, V),
keys_queue_update(K). keys_queue_update(K).
cleanup_acl(KeysQ) -> cleanup_acl(TTL, KeysQ) ->
case queue:out(KeysQ) of case queue:out(KeysQ) of
{{value, OldestK}, KeysQ2} -> {{value, OldestK}, KeysQ2} ->
{_AclResult, CachedAt} = erlang:get(OldestK), {_AclResult, CachedAt} = erlang:get(OldestK),
if_expired(CachedAt, if_expired(TTL, CachedAt,
fun(false) -> KeysQ; fun(false) -> KeysQ;
(true) -> (true) ->
erlang:erase(OldestK), erlang:erase(OldestK),
decr_cache_size(), decr_cache_size(),
cleanup_acl(KeysQ2) cleanup_acl(TTL, KeysQ2)
end); end);
{empty, KeysQ} -> KeysQ {empty, KeysQ} -> KeysQ
end. end.
@ -246,8 +248,7 @@ queue_rear() -> fun queue:get_r/1.
time_now() -> erlang:system_time(millisecond). time_now() -> erlang:system_time(millisecond).
if_expired(CachedAt, Fun) -> if_expired(TTL, CachedAt, Fun) ->
TTL = get_cache_ttl(),
Now = time_now(), Now = time_now(),
CurrentEvictTimestamp = persistent_term:get(drain_k(), 0), CurrentEvictTimestamp = persistent_term:get(drain_k(), 0),
case CachedAt =< CurrentEvictTimestamp orelse (CachedAt + TTL) =< Now of case CachedAt =< CurrentEvictTimestamp orelse (CachedAt + TTL) =< Now of

View File

@ -17,6 +17,7 @@
-module(emqx_alarm). -module(emqx_alarm).
-behaviour(gen_server). -behaviour(gen_server).
-behaviour(emqx_config_handler).
-include("emqx.hrl"). -include("emqx.hrl").
-include("logger.hrl"). -include("logger.hrl").
@ -29,7 +30,9 @@
-boot_mnesia({mnesia, [boot]}). -boot_mnesia({mnesia, [boot]}).
-copy_mnesia({mnesia, [copy]}). -copy_mnesia({mnesia, [copy]}).
-export([ start_link/1 -export([handle_update_config/2]).
-export([ start_link/0
, stop/0 , stop/0
]). ]).
@ -75,17 +78,9 @@
}). }).
-record(state, { -record(state, {
actions :: [action()], timer :: reference()
size_limit :: non_neg_integer(),
validity_period :: non_neg_integer(),
timer = undefined :: undefined | reference()
}). }).
-type action() :: log | publish | event.
-define(ACTIVATED_ALARM, emqx_activated_alarm). -define(ACTIVATED_ALARM, emqx_activated_alarm).
-define(DEACTIVATED_ALARM, emqx_deactivated_alarm). -define(DEACTIVATED_ALARM, emqx_deactivated_alarm).
@ -93,7 +88,6 @@
-rlog_shard({?COMMON_SHARD, ?ACTIVATED_ALARM}). -rlog_shard({?COMMON_SHARD, ?ACTIVATED_ALARM}).
-rlog_shard({?COMMON_SHARD, ?DEACTIVATED_ALARM}). -rlog_shard({?COMMON_SHARD, ?DEACTIVATED_ALARM}).
-ifdef(TEST). -ifdef(TEST).
-compile(export_all). -compile(export_all).
-compile(nowarn_export_all). -compile(nowarn_export_all).
@ -124,8 +118,8 @@ mnesia(copy) ->
%% API %% API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
start_link(Opts) -> start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [Opts], []). gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
stop() -> stop() ->
gen_server:stop(?MODULE). gen_server:stop(?MODULE).
@ -157,27 +151,26 @@ get_alarms(activated) ->
get_alarms(deactivated) -> get_alarms(deactivated) ->
gen_server:call(?MODULE, {get_alarms, deactivated}). gen_server:call(?MODULE, {get_alarms, deactivated}).
handle_update_config(#{<<"validity_period">> := Period0} = NewConf, OldConf) ->
?MODULE ! {update_timer, hocon_postprocess:duration(Period0)},
maps:merge(OldConf, NewConf);
handle_update_config(NewConf, OldConf) ->
maps:merge(OldConf, NewConf).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% gen_server callbacks %% gen_server callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([]) -> init([]) ->
Opts = [{actions, [log, publish]}],
init([Opts]);
init([Opts]) ->
deactivate_all_alarms(), deactivate_all_alarms(),
Actions = proplists:get_value(actions, Opts), emqx_config_handler:add_handler([alarm], ?MODULE),
SizeLimit = proplists:get_value(size_limit, Opts), {ok, #state{timer = ensure_timer(undefined, get_validity_period())}}.
ValidityPeriod = timer:seconds(proplists:get_value(validity_period, Opts)),
{ok, ensure_delete_timer(#state{actions = Actions,
size_limit = SizeLimit,
validity_period = ValidityPeriod})}.
%% suppress dialyzer warning due to dirty read/write race condition. %% suppress dialyzer warning due to dirty read/write race condition.
%% TODO: change from dirty_read/write to transactional. %% TODO: change from dirty_read/write to transactional.
%% TODO: handle mnesia write errors. %% TODO: handle mnesia write errors.
-dialyzer([{nowarn_function, [handle_call/3]}]). -dialyzer([{nowarn_function, [handle_call/3]}]).
handle_call({activate_alarm, Name, Details}, _From, State = #state{actions = Actions}) -> handle_call({activate_alarm, Name, Details}, _From, State) ->
case mnesia:dirty_read(?ACTIVATED_ALARM, Name) of case mnesia:dirty_read(?ACTIVATED_ALARM, Name) of
[#activated_alarm{name = Name}] -> [#activated_alarm{name = Name}] ->
{reply, {error, already_existed}, State}; {reply, {error, already_existed}, State};
@ -187,17 +180,16 @@ handle_call({activate_alarm, Name, Details}, _From, State = #state{actions = Act
message = normalize_message(Name, Details), message = normalize_message(Name, Details),
activate_at = erlang:system_time(microsecond)}, activate_at = erlang:system_time(microsecond)},
ekka_mnesia:dirty_write(?ACTIVATED_ALARM, Alarm), ekka_mnesia:dirty_write(?ACTIVATED_ALARM, Alarm),
do_actions(activate, Alarm, Actions), do_actions(activate, Alarm, emqx_config:get([alarm, actions])),
{reply, ok, State} {reply, ok, State}
end; end;
handle_call({deactivate_alarm, Name, Details}, _From, State = #state{ handle_call({deactivate_alarm, Name, Details}, _From, State) ->
actions = Actions, size_limit = SizeLimit}) ->
case mnesia:dirty_read(?ACTIVATED_ALARM, Name) of case mnesia:dirty_read(?ACTIVATED_ALARM, Name) of
[] -> [] ->
{reply, {error, not_found}, State}; {reply, {error, not_found}, State};
[Alarm] -> [Alarm] ->
deactivate_alarm(Details, SizeLimit, Actions, Alarm), deactivate_alarm(Details, Alarm),
{reply, ok, State} {reply, ok, State}
end; end;
@ -232,11 +224,15 @@ handle_cast(Msg, State) ->
?LOG(error, "Unexpected msg: ~p", [Msg]), ?LOG(error, "Unexpected msg: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info({timeout, TRef, delete_expired_deactivated_alarm}, handle_info({timeout, _TRef, delete_expired_deactivated_alarm},
State = #state{timer = TRef, #state{timer = TRef} = State) ->
validity_period = ValidityPeriod}) -> Period = get_validity_period(),
delete_expired_deactivated_alarms(erlang:system_time(microsecond) - ValidityPeriod * 1000), delete_expired_deactivated_alarms(erlang:system_time(microsecond) - Period * 1000),
{noreply, ensure_delete_timer(State)}; {noreply, State#state{timer = ensure_timer(TRef, Period)}};
handle_info({update_timer, Period}, #state{timer = TRef} = State) ->
?LOG(warning, "update the 'validity_period' timer to ~p", [Period]),
{noreply, State#state{timer = ensure_timer(TRef, Period)}};
handle_info(Info, State) -> handle_info(Info, State) ->
?LOG(error, "Unexpected info: ~p", [Info]), ?LOG(error, "Unexpected info: ~p", [Info]),
@ -252,11 +248,13 @@ code_change(_OldVsn, State, _Extra) ->
%% Internal functions %% Internal functions
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
deactivate_alarm(Details, SizeLimit, Actions, #activated_alarm{ get_validity_period() ->
activate_at = ActivateAt, name = Name, details = Details0, timer:seconds(emqx_config:get([alarm, validity_period])).
message = Msg0}) ->
case SizeLimit > 0 andalso deactivate_alarm(Details, #activated_alarm{activate_at = ActivateAt, name = Name,
(mnesia:table_info(?DEACTIVATED_ALARM, size) >= SizeLimit) of details = Details0, message = Msg0}) ->
SizeLimit = emqx_config:get([alarm, size_limit]),
case SizeLimit > 0 andalso (mnesia:table_info(?DEACTIVATED_ALARM, size) >= SizeLimit) of
true -> true ->
case mnesia:dirty_first(?DEACTIVATED_ALARM) of case mnesia:dirty_first(?DEACTIVATED_ALARM) of
'$end_of_table' -> ok; '$end_of_table' -> ok;
@ -272,7 +270,7 @@ deactivate_alarm(Details, SizeLimit, Actions, #activated_alarm{
erlang:system_time(microsecond)), erlang:system_time(microsecond)),
ekka_mnesia:dirty_write(?DEACTIVATED_ALARM, HistoryAlarm), ekka_mnesia:dirty_write(?DEACTIVATED_ALARM, HistoryAlarm),
ekka_mnesia:dirty_delete(?ACTIVATED_ALARM, Name), ekka_mnesia:dirty_delete(?ACTIVATED_ALARM, Name),
do_actions(deactivate, DeActAlarm, Actions). do_actions(deactivate, DeActAlarm, emqx_config:get([alarm, actions])).
make_deactivated_alarm(ActivateAt, Name, Details, Message, DeActivateAt) -> make_deactivated_alarm(ActivateAt, Name, Details, Message, DeActivateAt) ->
#deactivated_alarm{ #deactivated_alarm{
@ -308,9 +306,12 @@ clear_table(TableName) ->
ok ok
end. end.
ensure_delete_timer(State = #state{validity_period = ValidityPeriod}) -> ensure_timer(OldTRef, Period) ->
TRef = emqx_misc:start_timer(ValidityPeriod, delete_expired_deactivated_alarm), _ = case is_reference(OldTRef) of
State#state{timer = TRef}. true -> erlang:cancel_timer(OldTRef);
false -> ok
end,
emqx_misc:start_timer(Period, delete_expired_deactivated_alarm).
delete_expired_deactivated_alarms(Checkpoint) -> delete_expired_deactivated_alarms(Checkpoint) ->
delete_expired_deactivated_alarms(mnesia:dirty_first(?DEACTIVATED_ALARM), Checkpoint). delete_expired_deactivated_alarms(mnesia:dirty_first(?DEACTIVATED_ALARM), Checkpoint).
@ -381,9 +382,9 @@ normalize_message(high_system_memory_usage, #{high_watermark := HighWatermark})
normalize_message(high_process_memory_usage, #{high_watermark := HighWatermark}) -> normalize_message(high_process_memory_usage, #{high_watermark := HighWatermark}) ->
list_to_binary(io_lib:format("Process memory usage is higher than ~p%", [HighWatermark])); list_to_binary(io_lib:format("Process memory usage is higher than ~p%", [HighWatermark]));
normalize_message(high_cpu_usage, #{usage := Usage}) -> normalize_message(high_cpu_usage, #{usage := Usage}) ->
list_to_binary(io_lib:format("~p% cpu usage", [Usage])); list_to_binary(io_lib:format("~s cpu usage", [Usage]));
normalize_message(too_many_processes, #{usage := Usage}) -> normalize_message(too_many_processes, #{usage := Usage}) ->
list_to_binary(io_lib:format("~p% process usage", [Usage])); list_to_binary(io_lib:format("~s process usage", [Usage]));
normalize_message(partition, #{occurred := Node}) -> normalize_message(partition, #{occurred := Node}) ->
list_to_binary(io_lib:format("Partition occurs at node ~s", [Node])); list_to_binary(io_lib:format("Partition occurs at node ~s", [Node]));
normalize_message(<<"resource", _/binary>>, #{type := Type, id := ID}) -> normalize_message(<<"resource", _/binary>>, #{type := Type, id := ID}) ->

View File

@ -57,11 +57,13 @@ init(_) ->
{ok, []}. {ok, []}.
handle_event({set_alarm, {system_memory_high_watermark, []}}, State) -> handle_event({set_alarm, {system_memory_high_watermark, []}}, State) ->
emqx_alarm:activate(high_system_memory_usage, #{high_watermark => emqx_os_mon:get_sysmem_high_watermark()}), emqx_alarm:activate(high_system_memory_usage,
#{high_watermark => emqx_os_mon:get_sysmem_high_watermark()}),
{ok, State}; {ok, State};
handle_event({set_alarm, {process_memory_high_watermark, Pid}}, State) -> handle_event({set_alarm, {process_memory_high_watermark, Pid}}, State) ->
emqx_alarm:activate(high_process_memory_usage, #{pid => Pid, emqx_alarm:activate(high_process_memory_usage,
#{pid => list_to_binary(pid_to_list(Pid)),
high_watermark => emqx_os_mon:get_procmem_high_watermark()}), high_watermark => emqx_os_mon:get_procmem_high_watermark()}),
{ok, State}; {ok, State};

View File

@ -31,6 +31,8 @@
-export([ info/1 -export([ info/1
, info/2 , info/2
, get_mqtt_conf/3
, get_mqtt_conf/4
, set_conn_state/2 , set_conn_state/2
, get_session/1 , get_session/1
, set_session/2 , set_session/2
@ -63,7 +65,7 @@
, maybe_apply/2 , maybe_apply/2
]). ]).
-export_type([channel/0]). -export_type([channel/0, opts/0]).
-record(channel, { -record(channel, {
%% MQTT ConnInfo %% MQTT ConnInfo
@ -98,6 +100,8 @@
-type(channel() :: #channel{}). -type(channel() :: #channel{}).
-type(opts() :: #{zone := atom(), listener := atom(), atom() => term()}).
-type(conn_state() :: idle | connecting | connected | reauthenticating | disconnected). -type(conn_state() :: idle | connecting | connected | reauthenticating | disconnected).
-type(reply() :: {outgoing, emqx_types:packet()} -type(reply() :: {outgoing, emqx_types:packet()}
@ -151,7 +155,9 @@ info(connected_at, #channel{conninfo = ConnInfo}) ->
info(clientinfo, #channel{clientinfo = ClientInfo}) -> info(clientinfo, #channel{clientinfo = ClientInfo}) ->
ClientInfo; ClientInfo;
info(zone, #channel{clientinfo = ClientInfo}) -> info(zone, #channel{clientinfo = ClientInfo}) ->
maps:get(zone, ClientInfo, undefined); maps:get(zone, ClientInfo);
info(listener, #channel{clientinfo = ClientInfo}) ->
maps:get(listener, ClientInfo);
info(clientid, #channel{clientinfo = ClientInfo}) -> info(clientid, #channel{clientinfo = ClientInfo}) ->
maps:get(clientid, ClientInfo, undefined); maps:get(clientid, ClientInfo, undefined);
info(username, #channel{clientinfo = ClientInfo}) -> info(username, #channel{clientinfo = ClientInfo}) ->
@ -187,25 +193,28 @@ stats(#channel{session = Session})->
emqx_session:stats(Session). emqx_session:stats(Session).
-spec(caps(channel()) -> emqx_types:caps()). -spec(caps(channel()) -> emqx_types:caps()).
caps(#channel{clientinfo = #{zone := Zone}}) -> caps(#channel{clientinfo = #{zone := Zone, listener := Listener}}) ->
emqx_mqtt_caps:get_caps(Zone). emqx_mqtt_caps:get_caps(Zone, Listener).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Init the channel %% Init the channel
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(init(emqx_types:conninfo(), proplists:proplist()) -> channel()). -spec(init(emqx_types:conninfo(), opts()) -> channel()).
init(ConnInfo = #{peername := {PeerHost, _Port}, init(ConnInfo = #{peername := {PeerHost, _Port},
sockname := {_Host, SockPort}}, Options) -> sockname := {_Host, SockPort}}, #{zone := Zone, listener := Listener}) ->
Zone = proplists:get_value(zone, Options),
Peercert = maps:get(peercert, ConnInfo, undefined), Peercert = maps:get(peercert, ConnInfo, undefined),
Protocol = maps:get(protocol, ConnInfo, mqtt), Protocol = maps:get(protocol, ConnInfo, mqtt),
MountPoint = emqx_zone:mountpoint(Zone), MountPoint = case get_mqtt_conf(Zone, Listener, mountpoint) of
QuotaPolicy = emqx_zone:quota_policy(Zone), <<>> -> undefined;
ClientInfo = setting_peercert_infos( MP -> MP
end,
QuotaPolicy = emqx_config:get_listener_conf(Zone, Listener, [rate_limit, quota]),
ClientInfo = set_peercert_infos(
Peercert, Peercert,
#{zone => Zone, #{zone => Zone,
listener => Listener,
protocol => Protocol, protocol => Protocol,
peerhost => PeerHost, peerhost => PeerHost,
sockport => SockPort, sockport => SockPort,
@ -214,7 +223,7 @@ init(ConnInfo = #{peername := {PeerHost, _Port},
mountpoint => MountPoint, mountpoint => MountPoint,
is_bridge => false, is_bridge => false,
is_superuser => false is_superuser => false
}, Options), }, Zone, Listener),
{NClientInfo, NConnInfo} = take_ws_cookie(ClientInfo, ConnInfo), {NClientInfo, NConnInfo} = take_ws_cookie(ClientInfo, ConnInfo),
#channel{conninfo = NConnInfo, #channel{conninfo = NConnInfo,
clientinfo = NClientInfo, clientinfo = NClientInfo,
@ -222,7 +231,7 @@ init(ConnInfo = #{peername := {PeerHost, _Port},
outbound => #{} outbound => #{}
}, },
auth_cache = #{}, auth_cache = #{},
quota = emqx_limiter:init(Zone, QuotaPolicy), quota = emqx_limiter:init(Zone, quota_policy(QuotaPolicy)),
timers = #{}, timers = #{},
conn_state = idle, conn_state = idle,
takeover = false, takeover = false,
@ -230,30 +239,32 @@ init(ConnInfo = #{peername := {PeerHost, _Port},
pendings = [] pendings = []
}. }.
setting_peercert_infos(NoSSL, ClientInfo, _Options) quota_policy(RawPolicy) ->
[{Name, {list_to_integer(StrCount),
erlang:trunc(hocon_postprocess:duration(StrWind) / 1000)}}
|| {Name, [StrCount, StrWind]} <- maps:to_list(RawPolicy)].
set_peercert_infos(NoSSL, ClientInfo, _, _)
when NoSSL =:= nossl; when NoSSL =:= nossl;
NoSSL =:= undefined -> NoSSL =:= undefined ->
ClientInfo#{username => undefined}; ClientInfo#{username => undefined};
setting_peercert_infos(Peercert, ClientInfo, Options) -> set_peercert_infos(Peercert, ClientInfo, Zone, Listener) ->
{DN, CN} = {esockd_peercert:subject(Peercert), {DN, CN} = {esockd_peercert:subject(Peercert),
esockd_peercert:common_name(Peercert)}, esockd_peercert:common_name(Peercert)},
Username = peer_cert_as(peer_cert_as_username, Options, Peercert, DN, CN), PeercetAs = fun(Key) ->
ClientId = peer_cert_as(peer_cert_as_clientid, Options, Peercert, DN, CN), case get_mqtt_conf(Zone, Listener, Key) of
ClientInfo#{username => Username, clientid => ClientId, dn => DN, cn => CN}.
-dialyzer([{nowarn_function, [peer_cert_as/5]}]).
% esockd_peercert:peercert is opaque
% https://github.com/emqx/esockd/blob/master/src/esockd_peercert.erl
peer_cert_as(Key, Options, Peercert, DN, CN) ->
case proplists:get_value(Key, Options) of
cn -> CN; cn -> CN;
dn -> DN; dn -> DN;
crt -> Peercert; crt -> Peercert;
pem -> base64:encode(Peercert); pem when is_binary(Peercert) -> base64:encode(Peercert);
md5 -> emqx_passwd:hash(md5, Peercert); md5 when is_binary(Peercert) -> emqx_passwd:hash(md5, Peercert);
_ -> undefined _ -> undefined
end. end
end,
Username = PeercetAs(peer_cert_as_username),
ClientId = PeercetAs(peer_cert_as_clientid),
ClientInfo#{username => Username, clientid => ClientId, dn => DN, cn => CN}.
take_ws_cookie(ClientInfo, ConnInfo) -> take_ws_cookie(ClientInfo, ConnInfo) ->
case maps:take(ws_cookie, ConnInfo) of case maps:take(ws_cookie, ConnInfo) of
@ -415,16 +426,17 @@ handle_in(?PUBCOMP_PACKET(PacketId, _ReasonCode), Channel = #channel{session = S
end; end;
handle_in(Packet = ?SUBSCRIBE_PACKET(PacketId, Properties, TopicFilters), handle_in(Packet = ?SUBSCRIBE_PACKET(PacketId, Properties, TopicFilters),
Channel = #channel{clientinfo = ClientInfo = #{zone := Zone}}) -> Channel = #channel{clientinfo = ClientInfo = #{zone := Zone, listener := Listener}}) ->
case emqx_packet:check(Packet) of case emqx_packet:check(Packet) of
ok -> ok ->
TopicFilters0 = parse_topic_filters(TopicFilters), TopicFilters0 = parse_topic_filters(TopicFilters),
TopicFilters1 = put_subid_in_subopts(Properties, TopicFilters0), TopicFilters1 = put_subid_in_subopts(Properties, TopicFilters0),
TupleTopicFilters0 = check_sub_acls(TopicFilters1, Channel), TupleTopicFilters0 = check_sub_acls(TopicFilters1, Channel),
case emqx_zone:get_env(Zone, acl_deny_action, ignore) =:= disconnect andalso HasAclDeny = lists:any(fun({_TopicFilter, ReasonCode}) ->
lists:any(fun({_TopicFilter, ReasonCode}) ->
ReasonCode =:= ?RC_NOT_AUTHORIZED ReasonCode =:= ?RC_NOT_AUTHORIZED
end, TupleTopicFilters0) of end, TupleTopicFilters0),
DenyAction = emqx_config:get_listener_conf(Zone, Listener, [acl, deny_action]),
case DenyAction =:= disconnect andalso HasAclDeny of
true -> handle_out(disconnect, ?RC_NOT_AUTHORIZED, Channel); true -> handle_out(disconnect, ?RC_NOT_AUTHORIZED, Channel);
false -> false ->
Replace = fun Replace = fun
@ -526,7 +538,7 @@ process_connect(AckProps, Channel = #channel{conninfo = ConnInfo,
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
process_publish(Packet = ?PUBLISH_PACKET(QoS, Topic, PacketId), process_publish(Packet = ?PUBLISH_PACKET(QoS, Topic, PacketId),
Channel = #channel{clientinfo = #{zone := Zone}}) -> Channel = #channel{clientinfo = #{zone := Zone, listener := Listener}}) ->
case pipeline([fun check_quota_exceeded/2, case pipeline([fun check_quota_exceeded/2,
fun process_alias/2, fun process_alias/2,
fun check_pub_alias/2, fun check_pub_alias/2,
@ -539,7 +551,7 @@ process_publish(Packet = ?PUBLISH_PACKET(QoS, Topic, PacketId),
{error, Rc = ?RC_NOT_AUTHORIZED, NChannel} -> {error, Rc = ?RC_NOT_AUTHORIZED, NChannel} ->
?LOG(warning, "Cannot publish message to ~s due to ~s.", ?LOG(warning, "Cannot publish message to ~s due to ~s.",
[Topic, emqx_reason_codes:text(Rc)]), [Topic, emqx_reason_codes:text(Rc)]),
case emqx_zone:get_env(Zone, acl_deny_action, ignore) of case emqx_config:get_listener_conf(Zone, Listener, [acl_deny_action]) of
ignore -> ignore ->
case QoS of case QoS of
?QOS_0 -> {ok, NChannel}; ?QOS_0 -> {ok, NChannel};
@ -944,8 +956,9 @@ handle_call({takeover, 'end'}, Channel = #channel{session = Session,
AllPendings = lists:append(Delivers, Pendings), AllPendings = lists:append(Delivers, Pendings),
disconnect_and_shutdown(takeovered, AllPendings, Channel); disconnect_and_shutdown(takeovered, AllPendings, Channel);
handle_call(list_acl_cache, Channel) -> handle_call(list_acl_cache, #channel{clientinfo = #{zone := Zone, listener := Listener}}
{reply, emqx_acl_cache:list_acl_cache(), Channel}; = Channel) ->
{reply, emqx_acl_cache:list_acl_cache(Zone, Listener), Channel};
handle_call({quota, Policy}, Channel) -> handle_call({quota, Policy}, Channel) ->
Zone = info(zone, Channel), Zone = info(zone, Channel),
@ -982,9 +995,9 @@ handle_info({sock_closed, Reason}, Channel = #channel{conn_state = connecting})
handle_info({sock_closed, Reason}, Channel = handle_info({sock_closed, Reason}, Channel =
#channel{conn_state = ConnState, #channel{conn_state = ConnState,
clientinfo = ClientInfo = #{zone := Zone}}) clientinfo = ClientInfo = #{zone := Zone, listener := Listener}})
when ConnState =:= connected orelse ConnState =:= reauthenticating -> when ConnState =:= connected orelse ConnState =:= reauthenticating ->
emqx_zone:enable_flapping_detect(Zone) emqx_config:get_listener_conf(Zone, Listener, [flapping_detect, enable])
andalso emqx_flapping:detect(ClientInfo), andalso emqx_flapping:detect(ClientInfo),
Channel1 = ensure_disconnected(Reason, mabye_publish_will_msg(Channel)), Channel1 = ensure_disconnected(Reason, mabye_publish_will_msg(Channel)),
case maybe_shutdown(Reason, Channel1) of case maybe_shutdown(Reason, Channel1) of
@ -1145,9 +1158,9 @@ enrich_conninfo(ConnPkt = #mqtt_packet_connect{
username = Username username = Username
}, },
Channel = #channel{conninfo = ConnInfo, Channel = #channel{conninfo = ConnInfo,
clientinfo = #{zone := Zone} clientinfo = #{zone := Zone, listener := Listener}
}) -> }) ->
ExpiryInterval = expiry_interval(Zone, ConnPkt), ExpiryInterval = expiry_interval(Zone, Listener, ConnPkt),
NConnInfo = ConnInfo#{proto_name => ProtoName, NConnInfo = ConnInfo#{proto_name => ProtoName,
proto_ver => ProtoVer, proto_ver => ProtoVer,
clean_start => CleanStart, clean_start => CleanStart,
@ -1156,22 +1169,21 @@ enrich_conninfo(ConnPkt = #mqtt_packet_connect{
username => Username, username => Username,
conn_props => ConnProps, conn_props => ConnProps,
expiry_interval => ExpiryInterval, expiry_interval => ExpiryInterval,
receive_maximum => receive_maximum(Zone, ConnProps) receive_maximum => receive_maximum(Zone, Listener, ConnProps)
}, },
{ok, Channel#channel{conninfo = NConnInfo}}. {ok, Channel#channel{conninfo = NConnInfo}}.
%% If the Session Expiry Interval is absent the value 0 is used. %% If the Session Expiry Interval is absent the value 0 is used.
-compile({inline, [expiry_interval/2]}). expiry_interval(_, _, #mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V5,
expiry_interval(_Zone, #mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V5,
properties = ConnProps}) -> properties = ConnProps}) ->
emqx_mqtt_props:get('Session-Expiry-Interval', ConnProps, 0); emqx_mqtt_props:get('Session-Expiry-Interval', ConnProps, 0);
expiry_interval(Zone, #mqtt_packet_connect{clean_start = false}) -> expiry_interval(Zone, Listener, #mqtt_packet_connect{clean_start = false}) ->
emqx_zone:session_expiry_interval(Zone); get_mqtt_conf(Zone, Listener, session_expiry_interval);
expiry_interval(_Zone, #mqtt_packet_connect{clean_start = true}) -> expiry_interval(_, _, #mqtt_packet_connect{clean_start = true}) ->
0. 0.
receive_maximum(Zone, ConnProps) -> receive_maximum(Zone, Listener, ConnProps) ->
MaxInflightConfig = case emqx_zone:max_inflight(Zone) of MaxInflightConfig = case get_mqtt_conf(Zone, Listener, max_inflight) of
0 -> ?RECEIVE_MAXIMUM_LIMIT; 0 -> ?RECEIVE_MAXIMUM_LIMIT;
N -> N N -> N
end, end,
@ -1194,8 +1206,8 @@ run_conn_hooks(ConnPkt, Channel = #channel{conninfo = ConnInfo}) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Check Connect Packet %% Check Connect Packet
check_connect(ConnPkt, #channel{clientinfo = #{zone := Zone}}) -> check_connect(ConnPkt, #channel{clientinfo = #{zone := Zone, listener := Listener}}) ->
emqx_packet:check(ConnPkt, emqx_mqtt_caps:get_caps(Zone)). emqx_packet:check(ConnPkt, emqx_mqtt_caps:get_caps(Zone, Listener)).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Enrich Client Info %% Enrich Client Info
@ -1220,8 +1232,9 @@ set_bridge_mode(_ConnPkt, _ClientInfo) -> ok.
maybe_username_as_clientid(_ConnPkt, ClientInfo = #{username := undefined}) -> maybe_username_as_clientid(_ConnPkt, ClientInfo = #{username := undefined}) ->
{ok, ClientInfo}; {ok, ClientInfo};
maybe_username_as_clientid(_ConnPkt, ClientInfo = #{zone := Zone, username := Username}) -> maybe_username_as_clientid(_ConnPkt, ClientInfo = #{zone := Zone, listener := Listener,
case emqx_zone:use_username_as_clientid(Zone) of username := Username}) ->
case get_mqtt_conf(Zone, Listener, use_username_as_clientid) of
true -> {ok, ClientInfo#{clientid => Username}}; true -> {ok, ClientInfo#{clientid => Username}};
false -> ok false -> ok
end. end.
@ -1249,8 +1262,8 @@ set_log_meta(_ConnPkt, #channel{clientinfo = #{clientid := ClientId}}) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Check banned %% Check banned
check_banned(_ConnPkt, #channel{clientinfo = ClientInfo = #{zone := Zone}}) -> check_banned(_ConnPkt, #channel{clientinfo = ClientInfo}) ->
case emqx_zone:enable_ban(Zone) andalso emqx_banned:check(ClientInfo) of case emqx_banned:check(ClientInfo) of
true -> {error, ?RC_BANNED}; true -> {error, ?RC_BANNED};
false -> ok false -> ok
end. end.
@ -1419,8 +1432,8 @@ check_pub_caps(#mqtt_packet{header = #mqtt_packet_header{qos = QoS,
retain = Retain}, retain = Retain},
variable = #mqtt_packet_publish{topic_name = Topic} variable = #mqtt_packet_publish{topic_name = Topic}
}, },
#channel{clientinfo = #{zone := Zone}}) -> #channel{clientinfo = #{zone := Zone, listener := Listener}}) ->
emqx_mqtt_caps:check_pub(Zone, #{qos => QoS, retain => Retain, topic => Topic}). emqx_mqtt_caps:check_pub(Zone, Listener, #{qos => QoS, retain => Retain, topic => Topic}).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Check Sub ACL %% Check Sub ACL
@ -1448,8 +1461,9 @@ check_sub_acl(TopicFilter, #channel{clientinfo = ClientInfo}) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Check Sub Caps %% Check Sub Caps
check_sub_caps(TopicFilter, SubOpts, #channel{clientinfo = #{zone := Zone}}) -> check_sub_caps(TopicFilter, SubOpts, #channel{clientinfo = #{zone := Zone,
emqx_mqtt_caps:check_sub(Zone, TopicFilter, SubOpts). listener := Listener}}) ->
emqx_mqtt_caps:check_sub(Zone, Listener, TopicFilter, SubOpts).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Enrich SubId %% Enrich SubId
@ -1463,21 +1477,23 @@ put_subid_in_subopts(_Properties, TopicFilters) -> TopicFilters.
enrich_subopts(SubOpts, _Channel = ?IS_MQTT_V5) -> enrich_subopts(SubOpts, _Channel = ?IS_MQTT_V5) ->
SubOpts; SubOpts;
enrich_subopts(SubOpts, #channel{clientinfo = #{zone := Zone, is_bridge := IsBridge}}) -> enrich_subopts(SubOpts, #channel{clientinfo = #{zone := Zone, is_bridge := IsBridge,
NL = flag(emqx_zone:ignore_loop_deliver(Zone)), listener := Listener}}) ->
NL = flag(get_mqtt_conf(Zone, Listener, ignore_loop_deliver)),
SubOpts#{rap => flag(IsBridge), nl => NL}. SubOpts#{rap => flag(IsBridge), nl => NL}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Enrich ConnAck Caps %% Enrich ConnAck Caps
enrich_connack_caps(AckProps, ?IS_MQTT_V5 = #channel{clientinfo = #{zone := Zone}}) -> enrich_connack_caps(AckProps, ?IS_MQTT_V5 = #channel{clientinfo = #{
zone := Zone, listener := Listener}}) ->
#{max_packet_size := MaxPktSize, #{max_packet_size := MaxPktSize,
max_qos_allowed := MaxQoS, max_qos_allowed := MaxQoS,
retain_available := Retain, retain_available := Retain,
max_topic_alias := MaxAlias, max_topic_alias := MaxAlias,
shared_subscription := Shared, shared_subscription := Shared,
wildcard_subscription := Wildcard wildcard_subscription := Wildcard
} = emqx_mqtt_caps:get_caps(Zone), } = emqx_mqtt_caps:get_caps(Zone, Listener),
NAckProps = AckProps#{'Retain-Available' => flag(Retain), NAckProps = AckProps#{'Retain-Available' => flag(Retain),
'Maximum-Packet-Size' => MaxPktSize, 'Maximum-Packet-Size' => MaxPktSize,
'Topic-Alias-Maximum' => MaxAlias, 'Topic-Alias-Maximum' => MaxAlias,
@ -1499,9 +1515,9 @@ enrich_connack_caps(AckProps, _Channel) -> AckProps.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Enrich server keepalive %% Enrich server keepalive
enrich_server_keepalive(AckProps, #channel{clientinfo = #{zone := Zone}}) -> enrich_server_keepalive(AckProps, #channel{clientinfo = #{zone := Zone, listener := Listener}}) ->
case emqx_zone:server_keepalive(Zone) of case get_mqtt_conf(Zone, Listener, server_keepalive) of
undefined -> AckProps; disabled -> AckProps;
Keepalive -> AckProps#{'Server-Keep-Alive' => Keepalive} Keepalive -> AckProps#{'Server-Keep-Alive' => Keepalive}
end. end.
@ -1509,10 +1525,14 @@ enrich_server_keepalive(AckProps, #channel{clientinfo = #{zone := Zone}}) ->
%% Enrich response information %% Enrich response information
enrich_response_information(AckProps, #channel{conninfo = #{conn_props := ConnProps}, enrich_response_information(AckProps, #channel{conninfo = #{conn_props := ConnProps},
clientinfo = #{zone := Zone}}) -> clientinfo = #{zone := Zone, listener := Listener}}) ->
case emqx_mqtt_props:get('Request-Response-Information', ConnProps, 0) of case emqx_mqtt_props:get('Request-Response-Information', ConnProps, 0) of
0 -> AckProps; 0 -> AckProps;
1 -> AckProps#{'Response-Information' => emqx_zone:response_information(Zone)} 1 -> AckProps#{'Response-Information' =>
case get_mqtt_conf(Zone, Listener, response_information, "") of
"" -> undefined;
RspInfo -> RspInfo
end}
end. end.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -1542,9 +1562,9 @@ ensure_connected(Channel = #channel{conninfo = ConnInfo,
init_alias_maximum(#mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V5, init_alias_maximum(#mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V5,
properties = Properties}, properties = Properties},
#{zone := Zone} = _ClientInfo) -> #{zone := Zone, listener := Listener} = _ClientInfo) ->
#{outbound => emqx_mqtt_props:get('Topic-Alias-Maximum', Properties, 0), #{outbound => emqx_mqtt_props:get('Topic-Alias-Maximum', Properties, 0),
inbound => emqx_mqtt_caps:get_caps(Zone, max_topic_alias, ?MAX_TOPIC_AlIAS) inbound => maps:get(max_topic_alias, emqx_mqtt_caps:get_caps(Zone, Listener))
}; };
init_alias_maximum(_ConnPkt, _ClientInfo) -> undefined. init_alias_maximum(_ConnPkt, _ClientInfo) -> undefined.
@ -1560,8 +1580,10 @@ ensure_keepalive(_AckProps, Channel = #channel{conninfo = ConnInfo}) ->
ensure_keepalive_timer(maps:get(keepalive, ConnInfo), Channel). ensure_keepalive_timer(maps:get(keepalive, ConnInfo), Channel).
ensure_keepalive_timer(0, Channel) -> Channel; ensure_keepalive_timer(0, Channel) -> Channel;
ensure_keepalive_timer(Interval, Channel = #channel{clientinfo = #{zone := Zone}}) -> ensure_keepalive_timer(disabled, Channel) -> Channel;
Backoff = emqx_zone:keepalive_backoff(Zone), ensure_keepalive_timer(Interval, Channel = #channel{clientinfo = #{zone := Zone,
listener := Listener}}) ->
Backoff = get_mqtt_conf(Zone, Listener, keepalive_backoff),
Keepalive = emqx_keepalive:init(round(timer:seconds(Interval) * Backoff)), Keepalive = emqx_keepalive:init(round(timer:seconds(Interval) * Backoff)),
ensure_timer(alive_timer, Channel#channel{keepalive = Keepalive}). ensure_timer(alive_timer, Channel#channel{keepalive = Keepalive}).
@ -1602,10 +1624,8 @@ maybe_shutdown(Reason, Channel = #channel{conninfo = ConnInfo}) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Is ACL enabled? %% Is ACL enabled?
is_acl_enabled(#{zone := Zone, listener := Listener, is_superuser := IsSuperuser}) ->
-compile({inline, [is_acl_enabled/1]}). (not IsSuperuser) andalso emqx_config:get_listener_conf(Zone, Listener, [acl, enable]).
is_acl_enabled(#{zone := Zone, is_superuser := IsSuperuser}) ->
(not IsSuperuser) andalso emqx_zone:enable_acl(Zone).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Parse Topic Filters %% Parse Topic Filters
@ -1716,6 +1736,12 @@ sp(false) -> 0.
flag(true) -> 1; flag(true) -> 1;
flag(false) -> 0. flag(false) -> 0.
get_mqtt_conf(Zone, Listener, Key) ->
emqx_config:get_listener_conf(Zone, Listener, [mqtt, Key]).
get_mqtt_conf(Zone, Listener, Key, Default) ->
emqx_config:get_listener_conf(Zone, Listener, [mqtt, Key], Default).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% For CT tests %% For CT tests
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -1723,4 +1749,3 @@ flag(false) -> 0.
set_field(Name, Value, Channel) -> set_field(Name, Value, Channel) ->
Pos = emqx_misc:index_of(Name, record_info(fields, channel)), Pos = emqx_misc:index_of(Name, record_info(fields, channel)),
setelement(Pos+1, Channel, Value). setelement(Pos+1, Channel, Value).

View File

@ -20,10 +20,17 @@
-export([ get/0 -export([ get/0
, get/1 , get/1
, get/2 , get/2
, find/1
, put/1 , put/1
, put/2 , put/2
]). ]).
-export([ get_listener_conf/3
, get_listener_conf/4
, put_listener_conf/4
, find_listener_conf/3
]).
-export([ update_config/2 -export([ update_config/2
]). ]).
@ -35,43 +42,67 @@
, put_raw/2 , put_raw/2
]). ]).
-export([ deep_get/2
, deep_get/3
, deep_put/3
, safe_atom_key_map/1
, unsafe_atom_key_map/1
]).
-define(CONF, ?MODULE). -define(CONF, ?MODULE).
-define(RAW_CONF, {?MODULE, raw}). -define(RAW_CONF, {?MODULE, raw}).
-export_type([update_request/0, raw_config/0, config_key/0, config_key_path/0]). -export_type([update_request/0, raw_config/0, config/0]).
-type update_request() :: term(). -type update_request() :: term().
-type raw_config() :: hocon:config() | undefined. -type raw_config() :: #{binary() => term()} | undefined.
-type config_key() :: atom() | binary(). -type config() :: #{atom() => term()} | undefined.
-type config_key_path() :: [config_key()].
-spec get() -> map(). -spec get() -> map().
get() -> get() ->
persistent_term:get(?CONF, #{}). persistent_term:get(?CONF, #{}).
-spec get(config_key_path()) -> term(). -spec get(emqx_map_lib:config_key_path()) -> term().
get(KeyPath) -> get(KeyPath) ->
deep_get(KeyPath, get()). emqx_map_lib:deep_get(KeyPath, get()).
-spec get(config_key_path(), term()) -> term(). -spec get(emqx_map_lib:config_key_path(), term()) -> term().
get(KeyPath, Default) -> get(KeyPath, Default) ->
deep_get(KeyPath, get(), Default). emqx_map_lib:deep_get(KeyPath, get(), Default).
-spec find(emqx_map_lib:config_key_path()) ->
{ok, term()} | {not_found, emqx_map_lib:config_key_path(), term()}.
find(KeyPath) ->
emqx_map_lib:deep_find(KeyPath, get()).
-spec get_listener_conf(atom(), atom(), emqx_map_lib:config_key_path()) -> term().
get_listener_conf(Zone, Listener, KeyPath) ->
case find_listener_conf(Zone, Listener, KeyPath) of
{not_found, SubKeyPath, Data} -> error({not_found, SubKeyPath, Data});
{ok, Data} -> Data
end.
-spec get_listener_conf(atom(), atom(), emqx_map_lib:config_key_path(), term()) -> term().
get_listener_conf(Zone, Listener, KeyPath, Default) ->
case find_listener_conf(Zone, Listener, KeyPath) of
{not_found, _, _} -> Default;
{ok, Data} -> Data
end.
-spec put_listener_conf(atom(), atom(), emqx_map_lib:config_key_path(), term()) -> ok.
put_listener_conf(Zone, Listener, KeyPath, Conf) ->
?MODULE:put([zones, Zone, listeners, Listener | KeyPath], Conf).
-spec find_listener_conf(atom(), atom(), emqx_map_lib:config_key_path()) ->
{ok, term()} | {not_found, emqx_map_lib:config_key_path(), term()}.
find_listener_conf(Zone, Listener, KeyPath) ->
%% the configs in listener is prior to the ones in the zone
case find([zones, Zone, listeners, Listener | KeyPath]) of
{not_found, _, _} -> find([zones, Zone | KeyPath]);
{ok, Data} -> {ok, Data}
end.
-spec put(map()) -> ok. -spec put(map()) -> ok.
put(Config) -> put(Config) ->
persistent_term:put(?CONF, Config). persistent_term:put(?CONF, Config).
-spec put(config_key_path(), term()) -> ok. -spec put(emqx_map_lib:config_key_path(), term()) -> ok.
put(KeyPath, Config) -> put(KeyPath, Config) ->
put(deep_put(KeyPath, get(), Config)). put(emqx_map_lib:deep_put(KeyPath, get(), Config)).
-spec update_config(config_key_path(), update_request()) -> -spec update_config(emqx_map_lib:config_key_path(), update_request()) ->
ok | {error, term()}. ok | {error, term()}.
update_config(ConfKeyPath, UpdateReq) -> update_config(ConfKeyPath, UpdateReq) ->
emqx_config_handler:update_config(ConfKeyPath, UpdateReq, get_raw()). emqx_config_handler:update_config(ConfKeyPath, UpdateReq, get_raw()).
@ -80,67 +111,18 @@ update_config(ConfKeyPath, UpdateReq) ->
get_raw() -> get_raw() ->
persistent_term:get(?RAW_CONF, #{}). persistent_term:get(?RAW_CONF, #{}).
-spec get_raw(config_key_path()) -> term(). -spec get_raw(emqx_map_lib:config_key_path()) -> term().
get_raw(KeyPath) -> get_raw(KeyPath) ->
deep_get(KeyPath, get_raw()). emqx_map_lib:deep_get(KeyPath, get_raw()).
-spec get_raw(config_key_path(), term()) -> term(). -spec get_raw(emqx_map_lib:config_key_path(), term()) -> term().
get_raw(KeyPath, Default) -> get_raw(KeyPath, Default) ->
deep_get(KeyPath, get_raw(), Default). emqx_map_lib:deep_get(KeyPath, get_raw(), Default).
-spec put_raw(map()) -> ok. -spec put_raw(map()) -> ok.
put_raw(Config) -> put_raw(Config) ->
persistent_term:put(?RAW_CONF, Config). persistent_term:put(?RAW_CONF, Config).
-spec put_raw(config_key_path(), term()) -> ok. -spec put_raw(emqx_map_lib:config_key_path(), term()) -> ok.
put_raw(KeyPath, Config) -> put_raw(KeyPath, Config) ->
put_raw(deep_put(KeyPath, get_raw(), Config)). put_raw(emqx_map_lib:deep_put(KeyPath, get_raw(), Config)).
%%-----------------------------------------------------------------
-dialyzer([{nowarn_function, [deep_get/2]}]).
-spec deep_get(config_key_path(), map()) -> term().
deep_get(ConfKeyPath, Map) ->
do_deep_get(ConfKeyPath, Map, fun(KeyPath, Data) ->
error({not_found, KeyPath, Data}) end).
-spec deep_get(config_key_path(), map(), term()) -> term().
deep_get(ConfKeyPath, Map, Default) ->
do_deep_get(ConfKeyPath, Map, fun(_, _) -> Default end).
-spec deep_put(config_key_path(), map(), term()) -> map().
deep_put([], Map, Config) when is_map(Map) ->
Config;
deep_put([Key | KeyPath], Map, Config) ->
SubMap = deep_put(KeyPath, maps:get(Key, Map, #{}), Config),
Map#{Key => SubMap}.
unsafe_atom_key_map(Map) ->
covert_keys_to_atom(Map, fun(K) -> binary_to_atom(K, utf8) end).
safe_atom_key_map(Map) ->
covert_keys_to_atom(Map, fun(K) -> binary_to_existing_atom(K, utf8) end).
%%---------------------------------------------------------------------------
-spec do_deep_get(config_key_path(), map(), fun((config_key(), term()) -> any())) -> term().
do_deep_get([], Map, _) ->
Map;
do_deep_get([Key | KeyPath], Map, OnNotFound) when is_map(Map) ->
case maps:find(Key, Map) of
{ok, SubMap} -> do_deep_get(KeyPath, SubMap, OnNotFound);
error -> OnNotFound(Key, Map)
end;
do_deep_get([Key | _KeyPath], Data, OnNotFound) ->
OnNotFound(Key, Data).
covert_keys_to_atom(BinKeyMap, Conv) when is_map(BinKeyMap) ->
maps:fold(
fun(K, V, Acc) when is_binary(K) ->
Acc#{Conv(K) => covert_keys_to_atom(V, Conv)};
(K, V, Acc) when is_atom(K) ->
%% richmap keys
Acc#{K => covert_keys_to_atom(V, Conv)}
end, #{}, BinKeyMap);
covert_keys_to_atom(ListV, Conv) when is_list(ListV) ->
[covert_keys_to_atom(V, Conv) || V <- ListV];
covert_keys_to_atom(Val, _) -> Val.

View File

@ -45,11 +45,15 @@
-type handler_name() :: module(). -type handler_name() :: module().
-type handlers() :: #{emqx_config:config_key() => handlers(), ?MOD => handler_name()}. -type handlers() :: #{emqx_config:config_key() => handlers(), ?MOD => handler_name()}.
-optional_callbacks([handle_update_config/2]). -optional_callbacks([ handle_update_config/2
, post_update_config/2
]).
-callback handle_update_config(emqx_config:update_request(), emqx_config:raw_config()) -> -callback handle_update_config(emqx_config:update_request(), emqx_config:raw_config()) ->
emqx_config:update_request(). emqx_config:update_request().
-callback post_update_config(emqx_config:config(), emqx_config:config()) -> any().
-type state() :: #{ -type state() :: #{
handlers := handlers(), handlers := handlers(),
atom() => term() atom() => term()
@ -72,22 +76,23 @@ add_handler(ConfKeyPath, HandlerName) ->
-spec init(term()) -> {ok, state()}. -spec init(term()) -> {ok, state()}.
init(_) -> init(_) ->
{ok, RawConf} = hocon:load(emqx_conf_name(), #{format => richmap}), RawConf = load_config_file(),
{_MappedEnvs, Conf} = hocon_schema:map_translate(emqx_schema, RawConf, #{}), {_MappedEnvs, Conf} = hocon_schema:map_translate(emqx_schema, RawConf, #{}),
ok = save_config_to_emqx(to_plainmap(Conf), to_plainmap(RawConf)), ok = save_config_to_emqx(to_plainmap(Conf), to_plainmap(RawConf)),
{ok, #{handlers => #{?MOD => ?MODULE}}}. {ok, #{handlers => #{?MOD => ?MODULE}}}.
handle_call({add_child, ConfKeyPath, HandlerName}, _From, handle_call({add_child, ConfKeyPath, HandlerName}, _From,
State = #{handlers := Handlers}) -> State = #{handlers := Handlers}) ->
{reply, ok, State#{handlers => {reply, ok, State#{handlers =>
emqx_config:deep_put(ConfKeyPath, Handlers, #{?MOD => HandlerName})}}; emqx_map_lib:deep_put(ConfKeyPath, Handlers, #{?MOD => HandlerName})}};
handle_call({update_config, ConfKeyPath, UpdateReq, RawConf}, _From, handle_call({update_config, ConfKeyPath, UpdateReq, RawConf}, _From,
#{handlers := Handlers} = State) -> #{handlers := Handlers} = State) ->
OldConf = emqx_config:get(),
try {RootKeys, Conf} = do_update_config(ConfKeyPath, Handlers, RawConf, UpdateReq), try {RootKeys, Conf} = do_update_config(ConfKeyPath, Handlers, RawConf, UpdateReq),
{reply, save_configs(RootKeys, Conf), State} Result = save_configs(RootKeys, Conf),
do_post_update_config(ConfKeyPath, Handlers, OldConf, emqx_config:get()),
{reply, Result, State}
catch catch
throw: Reason ->
{reply, {error, Reason}, State};
Error : Reason : ST -> Error : Reason : ST ->
?LOG(error, "update config failed: ~p", [{Error, Reason, ST}]), ?LOG(error, "update config failed: ~p", [{Error, Reason, ST}]),
{reply, {error, Reason}, State} {reply, {error, Reason}, State}
@ -109,29 +114,40 @@ terminate(_Reason, _State) ->
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
do_update_config([], Handlers, OldConf, UpdateReq) -> do_update_config([], Handlers, OldRawConf, UpdateReq) ->
call_handle_update_config(Handlers, OldConf, UpdateReq); call_handle_update_config(Handlers, OldRawConf, UpdateReq);
do_update_config([ConfKey | ConfKeyPath], Handlers, OldConf, UpdateReq) -> do_update_config([ConfKey | ConfKeyPath], Handlers, OldRawConf, UpdateReq) ->
SubOldRawConf = get_sub_config(bin(ConfKey), OldRawConf),
SubHandlers = maps:get(ConfKey, Handlers, #{}),
NewUpdateReq = do_update_config(ConfKeyPath, SubHandlers, SubOldRawConf, UpdateReq),
call_handle_update_config(Handlers, OldRawConf, #{bin(ConfKey) => NewUpdateReq}).
do_post_update_config([], Handlers, OldConf, NewConf) ->
call_post_update_config(Handlers, OldConf, NewConf);
do_post_update_config([ConfKey | ConfKeyPath], Handlers, OldConf, NewConf) ->
SubOldConf = get_sub_config(ConfKey, OldConf), SubOldConf = get_sub_config(ConfKey, OldConf),
case maps:find(ConfKey, Handlers) of SubNewConf = get_sub_config(ConfKey, NewConf),
error -> throw({handler_not_found, ConfKey}); SubHandlers = maps:get(ConfKey, Handlers, #{}),
{ok, SubHandlers} -> _ = do_post_update_config(ConfKeyPath, SubHandlers, SubOldConf, SubNewConf),
NewUpdateReq = do_update_config(ConfKeyPath, SubHandlers, SubOldConf, UpdateReq), call_post_update_config(Handlers, OldConf, NewConf).
call_handle_update_config(Handlers, OldConf, #{bin(ConfKey) => NewUpdateReq})
end.
get_sub_config(_, undefined) -> get_sub_config(ConfKey, Conf) when is_map(Conf) ->
undefined; maps:get(ConfKey, Conf, undefined);
get_sub_config(ConfKey, OldConf) when is_map(OldConf) -> get_sub_config(_, _Conf) -> %% the Conf is a primitive
maps:get(bin(ConfKey), OldConf, undefined); undefined.
get_sub_config(_, OldConf) ->
OldConf.
call_handle_update_config(Handlers, OldConf, UpdateReq) -> call_handle_update_config(Handlers, OldRawConf, UpdateReq) ->
HandlerName = maps:get(?MOD, Handlers, undefined), HandlerName = maps:get(?MOD, Handlers, undefined),
case erlang:function_exported(HandlerName, handle_update_config, 2) of case erlang:function_exported(HandlerName, handle_update_config, 2) of
true -> HandlerName:handle_update_config(UpdateReq, OldConf); true -> HandlerName:handle_update_config(UpdateReq, OldRawConf);
false -> UpdateReq %% the default behaviour is overwriting the old config false -> merge_to_old_config(UpdateReq, OldRawConf)
end.
call_post_update_config(Handlers, OldConf, NewConf) ->
HandlerName = maps:get(?MOD, Handlers, undefined),
case erlang:function_exported(HandlerName, post_update_config, 2) of
true -> _ = HandlerName:post_update_config(NewConf, OldConf);
false -> ok
end. end.
%% callbacks for the top-level handler %% callbacks for the top-level handler
@ -139,11 +155,15 @@ handle_update_config(UpdateReq, OldConf) ->
FullRawConf = merge_to_old_config(UpdateReq, OldConf), FullRawConf = merge_to_old_config(UpdateReq, OldConf),
{maps:keys(UpdateReq), FullRawConf}. {maps:keys(UpdateReq), FullRawConf}.
%% default callback of config handlers %% The default callback of config handlers
merge_to_old_config(UpdateReq, undefined) -> %% the behaviour is overwriting the old config if:
merge_to_old_config(UpdateReq, #{}); %% 1. the old config is undefined
merge_to_old_config(UpdateReq, RawConf) -> %% 2. either the old or the new config is not of map type
maps:merge(RawConf, UpdateReq). %% the behaviour is merging the new the config to the old config if they are maps.
merge_to_old_config(UpdateReq, RawConf) when is_map(UpdateReq), is_map(RawConf) ->
maps:merge(RawConf, UpdateReq);
merge_to_old_config(UpdateReq, _RawConf) ->
UpdateReq.
%%============================================================================ %%============================================================================
save_configs(RootKeys, RawConf) -> save_configs(RootKeys, RawConf) ->
@ -160,7 +180,8 @@ save_configs(RootKeys, RawConf) ->
% end, MappedEnvs). % end, MappedEnvs).
save_config_to_emqx(Conf, RawConf) -> save_config_to_emqx(Conf, RawConf) ->
emqx_config:put(emqx_config:unsafe_atom_key_map(Conf)), ?LOG(debug, "set config: ~p", [Conf]),
emqx_config:put(emqx_map_lib:unsafe_atom_key_map(Conf)),
emqx_config:put_raw(RawConf). emqx_config:put_raw(RawConf).
save_config_to_disk(RootKeys, Conf) -> save_config_to_disk(RootKeys, Conf) ->
@ -191,14 +212,16 @@ read_old_config(FileName) ->
_ -> #{} _ -> #{}
end. end.
emqx_conf_name() -> load_config_file() ->
filename:join([etc_dir(), "emqx.conf"]). lists:foldl(fun(ConfFile, Acc) ->
{ok, RawConf} = hocon:load(ConfFile, #{format => richmap}),
emqx_map_lib:deep_merge(Acc, RawConf)
end, #{}, emqx:get_env(config_files, [])).
emqx_override_conf_name() -> emqx_override_conf_name() ->
filename:join([emqx:get_env(data_dir), "emqx_override.conf"]). File = filename:join([emqx:get_env(data_dir), "emqx_override.conf"]),
ok = filelib:ensure_dir(File),
etc_dir() -> File.
emqx:get_env(etc_dir).
to_richmap(Map) -> to_richmap(Map) ->
{ok, RichMap} = hocon:binary(jsx:encode(Map), #{format => richmap}), {ok, RichMap} = hocon:binary(jsx:encode(Map), #{format => richmap}),

View File

@ -83,8 +83,6 @@
sockname :: emqx_types:peername(), sockname :: emqx_types:peername(),
%% Sock State %% Sock State
sockstate :: emqx_types:sockstate(), sockstate :: emqx_types:sockstate(),
%% The {active, N} option
active_n :: pos_integer(),
%% Limiter %% Limiter
limiter :: maybe(emqx_limiter:limiter()), limiter :: maybe(emqx_limiter:limiter()),
%% Limit Timer %% Limit Timer
@ -102,13 +100,17 @@
%% Idle Timeout %% Idle Timeout
idle_timeout :: integer(), idle_timeout :: integer(),
%% Idle Timer %% Idle Timer
idle_timer :: maybe(reference()) idle_timer :: maybe(reference()),
%% Zone name
zone :: atom(),
%% Listener Name
listener :: atom()
}). }).
-type(state() :: #state{}). -type(state() :: #state{}).
-define(ACTIVE_N, 100). -define(ACTIVE_N, 100).
-define(INFO_KEYS, [socktype, peername, sockname, sockstate, active_n]). -define(INFO_KEYS, [socktype, peername, sockname, sockstate]).
-define(CONN_STATS, [recv_pkt, recv_msg, send_pkt, send_msg]). -define(CONN_STATS, [recv_pkt, recv_msg, send_pkt, send_msg]).
-define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt, send_pend]). -define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt, send_pend]).
@ -134,7 +136,7 @@
, system_code_change/4 , system_code_change/4
]}). ]}).
-spec(start_link(esockd:transport(), esockd:socket(), proplists:proplist()) -spec(start_link(esockd:transport(), esockd:socket(), emqx_channel:opts())
-> {ok, pid()}). -> {ok, pid()}).
start_link(Transport, Socket, Options) -> start_link(Transport, Socket, Options) ->
Args = [self(), Transport, Socket, Options], Args = [self(), Transport, Socket, Options],
@ -165,8 +167,6 @@ info(sockname, #state{sockname = Sockname}) ->
Sockname; Sockname;
info(sockstate, #state{sockstate = SockSt}) -> info(sockstate, #state{sockstate = SockSt}) ->
SockSt; SockSt;
info(active_n, #state{active_n = ActiveN}) ->
ActiveN;
info(stats_timer, #state{stats_timer = StatsTimer}) -> info(stats_timer, #state{stats_timer = StatsTimer}) ->
StatsTimer; StatsTimer;
info(limit_timer, #state{limit_timer = LimitTimer}) -> info(limit_timer, #state{limit_timer = LimitTimer}) ->
@ -243,7 +243,7 @@ init(Parent, Transport, RawSocket, Options) ->
exit_on_sock_error(Reason) exit_on_sock_error(Reason)
end. end.
init_state(Transport, Socket, Options) -> init_state(Transport, Socket, #{zone := Zone, listener := Listener} = Opts) ->
{ok, Peername} = Transport:ensure_ok_or_exit(peername, [Socket]), {ok, Peername} = Transport:ensure_ok_or_exit(peername, [Socket]),
{ok, Sockname} = Transport:ensure_ok_or_exit(sockname, [Socket]), {ok, Sockname} = Transport:ensure_ok_or_exit(sockname, [Socket]),
Peercert = Transport:ensure_ok_or_exit(peercert, [Socket]), Peercert = Transport:ensure_ok_or_exit(peercert, [Socket]),
@ -253,26 +253,29 @@ init_state(Transport, Socket, Options) ->
peercert => Peercert, peercert => Peercert,
conn_mod => ?MODULE conn_mod => ?MODULE
}, },
Zone = proplists:get_value(zone, Options), Limiter = emqx_limiter:init(Zone, undefined, undefined, []),
ActiveN = proplists:get_value(active_n, Options, ?ACTIVE_N), FrameOpts = #{
PubLimit = emqx_zone:publish_limit(Zone), strict_mode => emqx_config:get_listener_conf(Zone, Listener, [mqtt, strict_mode]),
BytesIn = proplists:get_value(rate_limit, Options), max_size => emqx_config:get_listener_conf(Zone, Listener, [mqtt, max_packet_size])
RateLimit = emqx_zone:ratelimit(Zone), },
Limiter = emqx_limiter:init(Zone, PubLimit, BytesIn, RateLimit),
FrameOpts = emqx_zone:mqtt_frame_options(Zone),
ParseState = emqx_frame:initial_parse_state(FrameOpts), ParseState = emqx_frame:initial_parse_state(FrameOpts),
Serialize = emqx_frame:serialize_opts(), Serialize = emqx_frame:serialize_opts(),
Channel = emqx_channel:init(ConnInfo, Options), Channel = emqx_channel:init(ConnInfo, Opts),
GcState = emqx_zone:init_gc_state(Zone), GcState = case emqx_config:get_listener_conf(Zone, Listener, [force_gc]) of
StatsTimer = emqx_zone:stats_timer(Zone), #{enable := false} -> undefined;
IdleTimeout = emqx_zone:idle_timeout(Zone), GcPolicy -> emqx_gc:init(GcPolicy)
end,
StatsTimer = case emqx_config:get_listener_conf(Zone, Listener, [stats, enable]) of
true -> undefined;
false -> disabled
end,
IdleTimeout = emqx_channel:get_mqtt_conf(Zone, Listener, idle_timeout),
IdleTimer = start_timer(IdleTimeout, idle_timeout), IdleTimer = start_timer(IdleTimeout, idle_timeout),
#state{transport = Transport, #state{transport = Transport,
socket = Socket, socket = Socket,
peername = Peername, peername = Peername,
sockname = Sockname, sockname = Sockname,
sockstate = idle, sockstate = idle,
active_n = ActiveN,
limiter = Limiter, limiter = Limiter,
parse_state = ParseState, parse_state = ParseState,
serialize = Serialize, serialize = Serialize,
@ -280,7 +283,9 @@ init_state(Transport, Socket, Options) ->
gc_state = GcState, gc_state = GcState,
stats_timer = StatsTimer, stats_timer = StatsTimer,
idle_timeout = IdleTimeout, idle_timeout = IdleTimeout,
idle_timer = IdleTimer idle_timer = IdleTimer,
zone = Zone,
listener = Listener
}. }.
run_loop(Parent, State = #state{transport = Transport, run_loop(Parent, State = #state{transport = Transport,
@ -288,8 +293,9 @@ run_loop(Parent, State = #state{transport = Transport,
peername = Peername, peername = Peername,
channel = Channel}) -> channel = Channel}) ->
emqx_logger:set_metadata_peername(esockd:format(Peername)), emqx_logger:set_metadata_peername(esockd:format(Peername)),
emqx_misc:tune_heap_size(emqx_zone:oom_policy( ShutdownPolicy = emqx_config:get_listener_conf(emqx_channel:info(zone, Channel),
emqx_channel:info(zone, Channel))), emqx_channel:info(listener, Channel), [force_shutdown]),
emqx_misc:tune_heap_size(ShutdownPolicy),
case activate_socket(State) of case activate_socket(State) of
{ok, NState} -> hibernate(Parent, NState); {ok, NState} -> hibernate(Parent, NState);
{error, Reason} -> {error, Reason} ->
@ -458,14 +464,15 @@ handle_msg({Passive, _Sock}, State)
NState1 = check_oom(run_gc(InStats, NState)), NState1 = check_oom(run_gc(InStats, NState)),
handle_info(activate_socket, NState1); handle_info(activate_socket, NState1);
handle_msg(Deliver = {deliver, _Topic, _Msg}, handle_msg(Deliver = {deliver, _Topic, _Msg}, #state{zone = Zone,
#state{active_n = ActiveN} = State) -> listener = Listener} = State) ->
ActiveN = get_active_n(Zone, Listener),
Delivers = [Deliver|emqx_misc:drain_deliver(ActiveN)], Delivers = [Deliver|emqx_misc:drain_deliver(ActiveN)],
with_channel(handle_deliver, [Delivers], State); with_channel(handle_deliver, [Delivers], State);
%% Something sent %% Something sent
handle_msg({inet_reply, _Sock, ok}, State = #state{active_n = ActiveN}) -> handle_msg({inet_reply, _Sock, ok}, State = #state{zone = Zone, listener = Listener}) ->
case emqx_pd:get_counter(outgoing_pubs) > ActiveN of case emqx_pd:get_counter(outgoing_pubs) > get_active_n(Zone, Listener) of
true -> true ->
Pubs = emqx_pd:reset_counter(outgoing_pubs), Pubs = emqx_pd:reset_counter(outgoing_pubs),
Bytes = emqx_pd:reset_counter(outgoing_bytes), Bytes = emqx_pd:reset_counter(outgoing_bytes),
@ -794,15 +801,14 @@ run_gc(Stats, State = #state{gc_state = GcSt}) ->
end. end.
check_oom(State = #state{channel = Channel}) -> check_oom(State = #state{channel = Channel}) ->
Zone = emqx_channel:info(zone, Channel), ShutdownPolicy = emqx_config:get_listener_conf(emqx_channel:info(zone, Channel),
OomPolicy = emqx_zone:oom_policy(Zone), emqx_channel:info(listener, Channel), [force_shutdown]),
?tp(debug, check_oom, #{policy => OomPolicy}), ?tp(debug, check_oom, #{policy => ShutdownPolicy}),
case ?ENABLED(OomPolicy) andalso emqx_misc:check_oom(OomPolicy) of case emqx_misc:check_oom(ShutdownPolicy) of
{shutdown, Reason} -> {shutdown, Reason} ->
%% triggers terminate/2 callback immediately %% triggers terminate/2 callback immediately
erlang:exit({shutdown, Reason}); erlang:exit({shutdown, Reason});
_Other -> _ -> ok
ok
end, end,
State. State.
@ -814,10 +820,10 @@ activate_socket(State = #state{sockstate = closed}) ->
{ok, State}; {ok, State};
activate_socket(State = #state{sockstate = blocked}) -> activate_socket(State = #state{sockstate = blocked}) ->
{ok, State}; {ok, State};
activate_socket(State = #state{transport = Transport, activate_socket(State = #state{transport = Transport, socket = Socket,
socket = Socket, zone = Zone, listener = Listener}) ->
active_n = N}) -> ActiveN = get_active_n(Zone, Listener),
case Transport:setopts(Socket, [{active, N}]) of case Transport:setopts(Socket, [{active, ActiveN}]) of
ok -> {ok, State#state{sockstate = running}}; ok -> {ok, State#state{sockstate = running}};
Error -> Error Error -> Error
end. end.
@ -898,3 +904,9 @@ get_state(Pid) ->
State = sys:get_state(Pid), State = sys:get_state(Pid),
maps:from_list(lists:zip(record_info(fields, state), maps:from_list(lists:zip(record_info(fields, state),
tl(tuple_to_list(State)))). tl(tuple_to_list(State)))).
get_active_n(Zone, Listener) ->
case emqx_config:get([zones, Zone, listeners, Listener, type]) of
quic -> 100;
_ -> emqx_config:get_listener_conf(Zone, Listener, [tcp, active_n])
end.

View File

@ -45,16 +45,16 @@
-define(FLAPPING_DURATION, 60000). -define(FLAPPING_DURATION, 60000).
-define(FLAPPING_BANNED_INTERVAL, 300000). -define(FLAPPING_BANNED_INTERVAL, 300000).
-define(DEFAULT_DETECT_POLICY, -define(DEFAULT_DETECT_POLICY,
#{threshold => ?FLAPPING_THRESHOLD, #{max_count => ?FLAPPING_THRESHOLD,
duration => ?FLAPPING_DURATION, window_time => ?FLAPPING_DURATION,
banned_interval => ?FLAPPING_BANNED_INTERVAL ban_time => ?FLAPPING_BANNED_INTERVAL
}). }).
-record(flapping, { -record(flapping, {
clientid :: emqx_types:clientid(), clientid :: emqx_types:clientid(),
peerhost :: emqx_types:peerhost(), peerhost :: emqx_types:peerhost(),
started_at :: pos_integer(), started_at :: pos_integer(),
detect_cnt :: pos_integer() detect_cnt :: integer()
}). }).
-opaque(flapping() :: #flapping{}). -opaque(flapping() :: #flapping{}).
@ -69,33 +69,28 @@ stop() -> gen_server:stop(?MODULE).
%% @doc Detect flapping when a MQTT client disconnected. %% @doc Detect flapping when a MQTT client disconnected.
-spec(detect(emqx_types:clientinfo()) -> boolean()). -spec(detect(emqx_types:clientinfo()) -> boolean()).
detect(Client) -> detect(Client, get_policy()). detect(#{clientid := ClientId, peerhost := PeerHost, zone := Zone, listener := Listener}) ->
Policy = #{max_count := Threshold} = get_policy(Zone, Listener),
detect(#{clientid := ClientId, peerhost := PeerHost}, Policy = #{threshold := Threshold}) -> %% The initial flapping record sets the detect_cnt to 0.
try ets:update_counter(?FLAPPING_TAB, ClientId, {#flapping.detect_cnt, 1}) of InitVal = #flapping{
clientid = ClientId,
peerhost = PeerHost,
started_at = erlang:system_time(millisecond),
detect_cnt = 0
},
case ets:update_counter(?FLAPPING_TAB, ClientId, {#flapping.detect_cnt, 1}, InitVal) of
Cnt when Cnt < Threshold -> false; Cnt when Cnt < Threshold -> false;
_Cnt -> case ets:take(?FLAPPING_TAB, ClientId) of _Cnt ->
case ets:take(?FLAPPING_TAB, ClientId) of
[Flapping] -> [Flapping] ->
ok = gen_server:cast(?MODULE, {detected, Flapping, Policy}), ok = gen_server:cast(?MODULE, {detected, Flapping, Policy}),
true; true;
[] -> false [] -> false
end end
catch
error:badarg ->
%% Create a flapping record.
Flapping = #flapping{clientid = ClientId,
peerhost = PeerHost,
started_at = erlang:system_time(millisecond),
detect_cnt = 1
},
true = ets:insert(?FLAPPING_TAB, Flapping),
false
end. end.
-compile({inline, [get_policy/0, now_diff/1]}). get_policy(Zone, Listener) ->
emqx_config:get_listener_conf(Zone, Listener, [flapping_detect]).
get_policy() ->
emqx:get_env(flapping_detect_policy, ?DEFAULT_DETECT_POLICY).
now_diff(TS) -> erlang:system_time(millisecond) - TS. now_diff(TS) -> erlang:system_time(millisecond) - TS.
@ -105,11 +100,12 @@ now_diff(TS) -> erlang:system_time(millisecond) - TS.
init([]) -> init([]) ->
ok = emqx_tables:new(?FLAPPING_TAB, [public, set, ok = emqx_tables:new(?FLAPPING_TAB, [public, set,
{keypos, 2}, {keypos, #flapping.clientid},
{read_concurrency, true}, {read_concurrency, true},
{write_concurrency, true} {write_concurrency, true}
]), ]),
{ok, ensure_timer(#{}), hibernate}. start_timers(),
{ok, #{}, hibernate}.
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
?LOG(error, "Unexpected call: ~p", [Req]), ?LOG(error, "Unexpected call: ~p", [Req]),
@ -119,17 +115,17 @@ handle_cast({detected, #flapping{clientid = ClientId,
peerhost = PeerHost, peerhost = PeerHost,
started_at = StartedAt, started_at = StartedAt,
detect_cnt = DetectCnt}, detect_cnt = DetectCnt},
#{duration := Duration, banned_interval := Interval}}, State) -> #{window_time := WindTime, ban_time := Interval}}, State) ->
case now_diff(StartedAt) < Duration of case now_diff(StartedAt) < WindTime of
true -> %% Flapping happened:( true -> %% Flapping happened:(
?LOG(error, "Flapping detected: ~s(~s) disconnected ~w times in ~wms", ?LOG(error, "Flapping detected: ~s(~s) disconnected ~w times in ~wms",
[ClientId, inet:ntoa(PeerHost), DetectCnt, Duration]), [ClientId, inet:ntoa(PeerHost), DetectCnt, WindTime]),
Now = erlang:system_time(second), Now = erlang:system_time(second),
Banned = #banned{who = {clientid, ClientId}, Banned = #banned{who = {clientid, ClientId},
by = <<"flapping detector">>, by = <<"flapping detector">>,
reason = <<"flapping is detected">>, reason = <<"flapping is detected">>,
at = Now, at = Now,
until = Now + Interval}, until = Now + (Interval div 1000)},
emqx_banned:create(Banned); emqx_banned:create(Banned);
false -> false ->
?LOG(warning, "~s(~s) disconnected ~w times in ~wms", ?LOG(warning, "~s(~s) disconnected ~w times in ~wms",
@ -141,11 +137,13 @@ handle_cast(Msg, State) ->
?LOG(error, "Unexpected cast: ~p", [Msg]), ?LOG(error, "Unexpected cast: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info({timeout, TRef, expired_detecting}, State = #{expired_timer := TRef}) -> handle_info({timeout, _TRef, {garbage_collect, Zone, Listener}}, State) ->
Timestamp = erlang:system_time(millisecond) - maps:get(duration, get_policy()), Timestamp = erlang:system_time(millisecond)
- maps:get(window_time, get_policy(Zone, Listener)),
MatchSpec = [{{'_', '_', '_', '$1', '_'},[{'<', '$1', Timestamp}], [true]}], MatchSpec = [{{'_', '_', '_', '$1', '_'},[{'<', '$1', Timestamp}], [true]}],
ets:select_delete(?FLAPPING_TAB, MatchSpec), ets:select_delete(?FLAPPING_TAB, MatchSpec),
{noreply, ensure_timer(State), hibernate}; start_timer(Zone, Listener),
{noreply, State, hibernate};
handle_info(Info, State) -> handle_info(Info, State) ->
?LOG(error, "Unexpected info: ~p", [Info]), ?LOG(error, "Unexpected info: ~p", [Info]),
@ -157,7 +155,13 @@ terminate(_Reason, _State) ->
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
ensure_timer(State) -> start_timer(Zone, Listener) ->
Timeout = maps:get(duration, get_policy()), WindTime = maps:get(window_time, get_policy(Zone, Listener)),
TRef = emqx_misc:start_timer(Timeout, expired_detecting), emqx_misc:start_timer(WindTime, {garbage_collect, Zone, Listener}).
State#{expired_timer => TRef}.
start_timers() ->
lists:foreach(fun({Zone, ZoneConf}) ->
lists:foreach(fun({Listener, _}) ->
start_timer(Zone, Listener)
end, maps:to_list(maps:get(listeners, ZoneConf, #{})))
end, maps:to_list(emqx_config:get([zones], #{}))).

View File

@ -81,11 +81,7 @@ initial_parse_state() ->
-spec(initial_parse_state(options()) -> {none, options()}). -spec(initial_parse_state(options()) -> {none, options()}).
initial_parse_state(Options) when is_map(Options) -> initial_parse_state(Options) when is_map(Options) ->
?none(merge_opts(Options)). ?none(maps:merge(?DEFAULT_OPTIONS, Options)).
%% @pivate
merge_opts(Options) ->
maps:merge(?DEFAULT_OPTIONS, Options).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Parse MQTT Frame %% Parse MQTT Frame
@ -643,7 +639,7 @@ serialize_properties(Props) when is_map(Props) ->
Bin = << <<(serialize_property(Prop, Val))/binary>> || {Prop, Val} <- maps:to_list(Props) >>, Bin = << <<(serialize_property(Prop, Val))/binary>> || {Prop, Val} <- maps:to_list(Props) >>,
[serialize_variable_byte_integer(byte_size(Bin)), Bin]. [serialize_variable_byte_integer(byte_size(Bin)), Bin].
serialize_property(_, undefined) -> serialize_property(_, Disabled) when Disabled =:= disabled; Disabled =:= undefined ->
<<>>; <<>>;
serialize_property('Payload-Format-Indicator', Val) -> serialize_property('Payload-Format-Indicator', Val) ->
<<16#01, Val>>; <<16#01, Val>>;

View File

@ -27,14 +27,15 @@ start_link() ->
init([]) -> init([]) ->
{ok, {{one_for_one, 10, 100}, {ok, {{one_for_one, 10, 100},
[child_spec(emqx_global_gc, worker), %% always start emqx_config_handler first to load the emqx.conf to emqx_config
child_spec(emqx_pool_sup, supervisor), [ child_spec(emqx_config_handler, worker)
child_spec(emqx_hooks, worker), , child_spec(emqx_global_gc, worker)
child_spec(emqx_stats, worker), , child_spec(emqx_pool_sup, supervisor)
child_spec(emqx_metrics, worker), , child_spec(emqx_hooks, worker)
child_spec(emqx_ctl, worker), , child_spec(emqx_stats, worker)
child_spec(emqx_zone, worker), , child_spec(emqx_metrics, worker)
child_spec(emqx_config_handler, worker) , child_spec(emqx_ctl, worker)
, child_spec(emqx_zone, worker)
]}}. ]}}.
child_spec(M, Type) -> child_spec(M, Type) ->

View File

@ -21,7 +21,6 @@
%% APIs %% APIs
-export([ start/0 -export([ start/0
, ensure_all_started/0
, restart/0 , restart/0
, stop/0 , stop/0
]). ]).
@ -29,90 +28,29 @@
-export([ start_listener/1 -export([ start_listener/1
, start_listener/3 , start_listener/3
, stop_listener/1 , stop_listener/1
, stop_listener/3
, restart_listener/1 , restart_listener/1
, restart_listener/3 , restart_listener/3
]). ]).
-export([ find_id_by_listen_on/1
, find_by_listen_on/1
, find_by_id/1
, identifier/1
, format_listen_on/1
]).
-type(listener() :: #{ name := binary()
, proto := esockd:proto()
, listen_on := esockd:listen_on()
, opts := [esockd:option()]
}).
%% @doc Find listener identifier by listen-on.
%% Return empty string (binary) if listener is not found in config.
-spec(find_id_by_listen_on(esockd:listen_on()) -> binary() | false).
find_id_by_listen_on(ListenOn) ->
case find_by_listen_on(ListenOn) of
false -> false;
L -> identifier(L)
end.
%% @doc Find listener by listen-on.
%% Return 'false' if not found.
-spec(find_by_listen_on(esockd:listen_on()) -> listener() | false).
find_by_listen_on(ListenOn) ->
find_by_listen_on(ListenOn, emqx:get_env(listeners, [])).
%% @doc Find listener by identifier.
%% Return 'false' if not found.
-spec(find_by_id(string() | binary()) -> listener() | false).
find_by_id(Id) ->
find_by_id(iolist_to_binary(Id), emqx:get_env(listeners, [])).
%% @doc Return the ID of the given listener.
-spec identifier(listener()) -> binary().
identifier(#{proto := Proto, name := Name}) ->
identifier(Proto, Name).
%% @doc Start all listeners. %% @doc Start all listeners.
-spec(start() -> ok). -spec(start() -> ok).
start() -> start() ->
lists:foreach(fun start_listener/1, emqx:get_env(listeners, [])). foreach_listeners(fun start_listener/3).
%% @doc Ensure all configured listeners are started. -spec start_listener(atom()) -> ok | {error, term()}.
%% Raise exception if any of them failed to start. start_listener(ListenerId) ->
-spec(ensure_all_started() -> ok). apply_on_listener(ListenerId, fun start_listener/3).
ensure_all_started() ->
ensure_all_started(emqx:get_env(listeners, []), []).
ensure_all_started([], []) -> ok; -spec start_listener(atom(), atom(), map()) -> ok | {error, term()}.
ensure_all_started([], Failed) -> error(Failed); start_listener(ZoneName, ListenerName, #{type := Type, bind := Bind} = Conf) ->
ensure_all_started([L | Rest], Results) -> case do_start_listener(ZoneName, ListenerName, Conf) of
#{proto := Proto, listen_on := ListenOn, opts := Options} = L,
NewResults =
case start_listener(Proto, ListenOn, Options) of
{ok, _Pid} ->
Results;
{error, {already_started, _Pid}} ->
Results;
{error, Reason} ->
[{identifier(L), Reason} | Results]
end,
ensure_all_started(Rest, NewResults).
%% @doc Format address:port for logging.
-spec(format_listen_on(esockd:listen_on()) -> [char()]).
format_listen_on(ListenOn) -> format(ListenOn).
-spec(start_listener(listener()) -> ok).
start_listener(#{proto := Proto, name := Name, listen_on := ListenOn, opts := Options}) ->
ID = identifier(Proto, Name),
case start_listener(Proto, ListenOn, Options) of
{ok, skipped} ->
console_print("Start ~s listener on ~s skpped.~n", [ID, format(ListenOn)]);
{ok, _} -> {ok, _} ->
console_print("Start ~s listener on ~s successfully.~n", [ID, format(ListenOn)]); console_print("Start ~s listener ~s on ~s successfully.~n",
[Type, listener_id(ZoneName, ListenerName), format(Bind)]);
{error, Reason} -> {error, Reason} ->
io:format(standard_error, "Failed to start mqtt listener ~s on ~s: ~0p~n", io:format(standard_error, "Failed to start ~s listener ~s on ~s: ~0p~n",
[ID, format(ListenOn), Reason]), [Type, listener_id(ZoneName, ListenerName), format(Bind), Reason]),
error(Reason) error(Reason)
end. end.
@ -124,154 +62,128 @@ console_print(_Fmt, _Args) -> ok.
-endif. -endif.
%% Start MQTT/TCP listener %% Start MQTT/TCP listener
-spec(start_listener(esockd:proto(), esockd:listen_on(), [esockd:option()]) -spec(do_start_listener(atom(), atom(), map())
-> {ok, pid() | skipped} | {error, term()}). -> {ok, pid()} | {error, term()}).
start_listener(tcp, ListenOn, Options) -> do_start_listener(ZoneName, ListenerName, #{type := tcp, bind := ListenOn} = Opts) ->
start_mqtt_listener('mqtt:tcp', ListenOn, Options); esockd:open(listener_id(ZoneName, ListenerName), ListenOn, merge_default(esockd_opts(Opts)),
{emqx_connection, start_link,
%% Start MQTT/TLS listener [#{zone => ZoneName, listener => ListenerName}]});
start_listener(Proto, ListenOn, Options) when Proto == ssl; Proto == tls ->
start_mqtt_listener('mqtt:ssl', ListenOn, Options);
%% Start MQTT/WS listener %% Start MQTT/WS listener
start_listener(Proto, ListenOn, Options) when Proto == http; Proto == ws -> do_start_listener(ZoneName, ListenerName, #{type := ws, bind := ListenOn} = Opts) ->
start_http_listener(fun cowboy:start_clear/3, 'mqtt:ws', ListenOn, Id = listener_id(ZoneName, ListenerName),
ranch_opts(Options), ws_opts(Options)); RanchOpts = ranch_opts(ListenOn, Opts),
WsOpts = ws_opts(ZoneName, ListenerName, Opts),
%% Start MQTT/WSS listener case is_ssl(Opts) of
start_listener(Proto, ListenOn, Options) when Proto == https; Proto == wss -> false ->
start_http_listener(fun cowboy:start_tls/3, 'mqtt:wss', ListenOn, cowboy:start_clear(Id, RanchOpts, WsOpts);
ranch_opts(Options), ws_opts(Options)); true ->
cowboy:start_tls(Id, RanchOpts, WsOpts)
end;
%% Start MQTT/QUIC listener %% Start MQTT/QUIC listener
start_listener(quic, ListenOn, Options) -> do_start_listener(ZoneName, ListenerName, #{type := quic, bind := ListenOn} = Opts) ->
case [ A || {quicer, _, _} = A<-application:which_applications() ] of
[_] ->
%% @fixme unsure why we need reopen lib and reopen config. %% @fixme unsure why we need reopen lib and reopen config.
quicer_nif:open_lib(), quicer_nif:open_lib(),
quicer_nif:reg_open(), quicer_nif:reg_open(),
SSLOpts = proplists:get_value(ssl_options, Options),
DefAcceptors = erlang:system_info(schedulers_online) * 8, DefAcceptors = erlang:system_info(schedulers_online) * 8,
ListenOpts = [ {cert, proplists:get_value(certfile, SSLOpts)} ListenOpts = [ {cert, maps:get(certfile, Opts)}
, {key, proplists:get_value(keyfile, SSLOpts)} , {key, maps:get(keyfile, Opts)}
, {alpn, ["mqtt"]} , {alpn, ["mqtt"]}
, {conn_acceptors, proplists:get_value(acceptors, Options, DefAcceptors)} , {conn_acceptors, maps:get(acceptors, Opts, DefAcceptors)}
, {idle_timeout_ms, proplists:get_value(idle_timeout, Options, 60000)} , {idle_timeout_ms, emqx_config:get_listener_conf(ZoneName, ListenerName,
], [mqtt, idle_timeout])}
ConnectionOpts = [ {conn_callback, emqx_quic_connection}
, {peer_unidi_stream_count, 1}
, {peer_bidi_stream_count, 10}
| Options
], ],
ConnectionOpts = #{conn_callback => emqx_quic_connection
, peer_unidi_stream_count => 1
, peer_bidi_stream_count => 10
, zone => ZoneName
, listener => ListenerName
},
StreamOpts = [], StreamOpts = [],
quicer:start_listener('mqtt:quic', ListenOn, {ListenOpts, ConnectionOpts, StreamOpts}); quicer:start_listener(listener_id(ZoneName, ListenerName),
[] -> port(ListenOn), {ListenOpts, ConnectionOpts, StreamOpts}).
io:format(standard_error, "INFO: quicer application is unavailable/disabled~n",
[]),
{ok, skipped}
end.
replace(Opts, Key, Value) -> [{Key, Value} | proplists:delete(Key, Opts)]. esockd_opts(Opts0) ->
Opts1 = maps:with([acceptors, max_connections, proxy_protocol, proxy_protocol_timeout], Opts0),
Opts2 = case emqx_map_lib:deep_get([rate_limit, max_conn_rate], Opts0) of
infinity -> Opts1;
Rate -> Opts1#{max_conn_rate => Rate}
end,
Opts3 = Opts2#{access_rules => esockd_access_rules(maps:get(access_rules, Opts0, []))},
maps:to_list(case is_ssl(Opts0) of
false ->
Opts3#{tcp_options => tcp_opts(Opts0)};
true ->
Opts3#{ssl_options => ssl_opts(Opts0), tcp_options => tcp_opts(Opts0)}
end).
drop_tls13_for_old_otp(Options) -> ws_opts(ZoneName, ListenerName, Opts) ->
case proplists:get_value(ssl_options, Options) of WsPaths = [{maps:get(mqtt_path, Opts, "/mqtt"), emqx_ws_connection,
undefined -> Options; #{zone => ZoneName, listener => ListenerName}}],
SslOpts ->
SslOpts1 = emqx_tls_lib:drop_tls13_for_old_otp(SslOpts),
replace(Options, ssl_options, SslOpts1)
end.
start_mqtt_listener(Name, ListenOn, Options0) ->
Options = drop_tls13_for_old_otp(Options0),
SockOpts = esockd:parse_opt(Options),
esockd:open(Name, ListenOn, merge_default(SockOpts),
{emqx_connection, start_link, [Options -- SockOpts]}).
start_http_listener(Start, Name, ListenOn, RanchOpts, ProtoOpts) ->
Start(ws_name(Name, ListenOn), with_port(ListenOn, RanchOpts), ProtoOpts).
mqtt_path(Options) ->
proplists:get_value(mqtt_path, Options, "/mqtt").
ws_opts(Options) ->
WsPaths = [{mqtt_path(Options), emqx_ws_connection, Options}],
Dispatch = cowboy_router:compile([{'_', WsPaths}]), Dispatch = cowboy_router:compile([{'_', WsPaths}]),
ProxyProto = proplists:get_value(proxy_protocol, Options, false), ProxyProto = maps:get(proxy_protocol, Opts, false),
#{env => #{dispatch => Dispatch}, proxy_header => ProxyProto}. #{env => #{dispatch => Dispatch}, proxy_header => ProxyProto}.
ranch_opts(Options0) -> ranch_opts(ListenOn, Opts) ->
Options = drop_tls13_for_old_otp(Options0), NumAcceptors = maps:get(acceptors, Opts, 4),
NumAcceptors = proplists:get_value(acceptors, Options, 4), MaxConnections = maps:get(max_connections, Opts, 1024),
MaxConnections = proplists:get_value(max_connections, Options, 1024), SocketOpts = case is_ssl(Opts) of
TcpOptions = proplists:get_value(tcp_options, Options, []), true -> tcp_opts(Opts) ++ proplists:delete(handshake_timeout, ssl_opts(Opts));
RanchOpts = #{num_acceptors => NumAcceptors, false -> tcp_opts(Opts)
end,
#{num_acceptors => NumAcceptors,
max_connections => MaxConnections, max_connections => MaxConnections,
socket_opts => TcpOptions}, handshake_timeout => maps:get(handshake_timeout, Opts, 15000),
case proplists:get_value(ssl_options, Options) of socket_opts => ip_port(ListenOn) ++ SocketOpts}.
undefined -> RanchOpts;
SslOptions -> RanchOpts#{socket_opts => TcpOptions ++ SslOptions}
end.
with_port(Port, Opts = #{socket_opts := SocketOption}) when is_integer(Port) -> ip_port(Port) when is_integer(Port) ->
Opts#{socket_opts => [{port, Port}| SocketOption]}; [{port, Port}];
with_port({Addr, Port}, Opts = #{socket_opts := SocketOption}) -> ip_port({Addr, Port}) ->
Opts#{socket_opts => [{ip, Addr}, {port, Port}| SocketOption]}. [{ip, Addr}, {port, Port}].
port(Port) when is_integer(Port) -> Port;
port({_Addr, Port}) when is_integer(Port) -> Port.
esockd_access_rules(StrRules) ->
Access = fun(S) ->
[A, CIDR] = string:tokens(S, " "),
{list_to_atom(A), case CIDR of "all" -> all; _ -> CIDR end}
end,
[Access(R) || R <- StrRules].
%% @doc Restart all listeners %% @doc Restart all listeners
-spec(restart() -> ok). -spec(restart() -> ok).
restart() -> restart() ->
lists:foreach(fun restart_listener/1, emqx:get_env(listeners, [])). foreach_listeners(fun restart_listener/3).
-spec(restart_listener(listener() | string() | binary()) -> ok | {error, any()}). -spec(restart_listener(atom()) -> ok | {error, term()}).
restart_listener(#{proto := Proto, listen_on := ListenOn, opts := Options}) -> restart_listener(ListenerId) ->
restart_listener(Proto, ListenOn, Options); apply_on_listener(ListenerId, fun restart_listener/3).
restart_listener(Identifier) ->
case emqx_listeners:find_by_id(Identifier) of -spec(restart_listener(atom(), atom(), map()) -> ok | {error, term()}).
false -> {error, {no_such_listener, Identifier}}; restart_listener(ZoneName, ListenerName, Conf) ->
Listener -> restart_listener(Listener) case stop_listener(ZoneName, ListenerName, Conf) of
ok -> start_listener(ZoneName, ListenerName, Conf);
Error -> Error
end. end.
-spec(restart_listener(esockd:proto(), esockd:listen_on(), [esockd:option()]) ->
ok | {error, any()}).
restart_listener(tcp, ListenOn, _Options) ->
esockd:reopen('mqtt:tcp', ListenOn);
restart_listener(Proto, ListenOn, _Options) when Proto == ssl; Proto == tls ->
esockd:reopen('mqtt:ssl', ListenOn);
restart_listener(Proto, ListenOn, Options) when Proto == http; Proto == ws ->
_ = cowboy:stop_listener(ws_name('mqtt:ws', ListenOn)),
ok(start_listener(Proto, ListenOn, Options));
restart_listener(Proto, ListenOn, Options) when Proto == https; Proto == wss ->
_ = cowboy:stop_listener(ws_name('mqtt:wss', ListenOn)),
ok(start_listener(Proto, ListenOn, Options));
restart_listener(Proto, ListenOn, _Opts) ->
esockd:reopen(Proto, ListenOn).
ok({ok, _}) -> ok;
ok(Other) -> Other.
%% @doc Stop all listeners. %% @doc Stop all listeners.
-spec(stop() -> ok). -spec(stop() -> ok).
stop() -> stop() ->
lists:foreach(fun stop_listener/1, emqx:get_env(listeners, [])). foreach_listeners(fun stop_listener/3).
-spec(stop_listener(listener()) -> ok | {error, term()}). -spec(stop_listener(atom()) -> ok | {error, term()}).
stop_listener(#{proto := Proto, listen_on := ListenOn, opts := Opts}) -> stop_listener(ListenerId) ->
stop_listener(Proto, ListenOn, Opts). apply_on_listener(ListenerId, fun stop_listener/3).
-spec(stop_listener(esockd:proto(), esockd:listen_on(), [esockd:option()]) -spec(stop_listener(atom(), atom(), map()) -> ok | {error, term()}).
-> ok | {error, term()}). stop_listener(ZoneName, ListenerName, #{type := tcp, bind := ListenOn}) ->
stop_listener(tcp, ListenOn, _Opts) -> esockd:close(listener_id(ZoneName, ListenerName), ListenOn);
esockd:close('mqtt:tcp', ListenOn); stop_listener(ZoneName, ListenerName, #{type := ws}) ->
stop_listener(Proto, ListenOn, _Opts) when Proto == ssl; Proto == tls -> cowboy:stop_listener(listener_id(ZoneName, ListenerName));
esockd:close('mqtt:ssl', ListenOn); stop_listener(ZoneName, ListenerName, #{type := quic}) ->
stop_listener(Proto, ListenOn, _Opts) when Proto == http; Proto == ws -> quicer:stop_listener(listener_id(ZoneName, ListenerName)).
cowboy:stop_listener(ws_name('mqtt:ws', ListenOn));
stop_listener(Proto, ListenOn, _Opts) when Proto == https; Proto == wss ->
cowboy:stop_listener(ws_name('mqtt:wss', ListenOn));
stop_listener(quic, _ListenOn, _Opts) ->
quicer:stop_listener('mqtt:quic');
stop_listener(Proto, ListenOn, _Opts) ->
esockd:close(Proto, ListenOn).
merge_default(Options) -> merge_default(Options) ->
case lists:keytake(tcp_options, 1, Options) of case lists:keytake(tcp_options, 1, Options) of
@ -288,23 +200,46 @@ format({Addr, Port}) when is_list(Addr) ->
format({Addr, Port}) when is_tuple(Addr) -> format({Addr, Port}) when is_tuple(Addr) ->
io_lib:format("~s:~w", [inet:ntoa(Addr), Port]). io_lib:format("~s:~w", [inet:ntoa(Addr), Port]).
ws_name(Name, {_Addr, Port}) -> listener_id(ZoneName, ListenerName) ->
ws_name(Name, Port); list_to_atom(lists:append([atom_to_list(ZoneName), ":", atom_to_list(ListenerName)])).
ws_name(Name, Port) ->
list_to_atom(lists:concat([Name, ":", Port])).
identifier(Proto, Name) when is_atom(Proto) -> decode_listener_id(Id) ->
identifier(atom_to_list(Proto), Name); case string:split(atom_to_list(Id), ":", leading) of
identifier(Proto, Name) -> [Zone, Listen] -> {list_to_atom(Zone), list_to_atom(Listen)};
iolist_to_binary(["mqtt", ":", Proto, ":", Name]). _ -> error({invalid_listener_id, Id})
end.
find_by_listen_on(_ListenOn, []) -> false;
find_by_listen_on(ListenOn, [#{listen_on := ListenOn} = L | _]) -> L; ssl_opts(Opts) ->
find_by_listen_on(ListenOn, [_ | Rest]) -> find_by_listen_on(ListenOn, Rest). maps:to_list(
emqx_tls_lib:drop_tls13_for_old_otp(
find_by_id(_Id, []) -> false; maps:without([enable],
find_by_id(Id, [L | Rest]) -> maps:get(ssl, Opts, #{})))).
case identifier(L) =:= Id of
true -> L; tcp_opts(Opts) ->
false -> find_by_id(Id, Rest) maps:to_list(
maps:without([active_n],
maps:get(tcp, Opts, #{}))).
is_ssl(Opts) ->
emqx_map_lib:deep_get([ssl, enable], Opts, false).
foreach_listeners(Do) ->
lists:foreach(fun({ZoneName, ZoneConf}) ->
lists:foreach(fun({LName, LConf}) ->
Do(ZoneName, LName, merge_zone_and_listener_confs(ZoneConf, LConf))
end, maps:to_list(maps:get(listeners, ZoneConf, #{})))
end, maps:to_list(emqx_config:get([zones], #{}))).
%% merge the configs in zone and listeners in a manner that
%% all config entries in the listener are prior to the ones in the zone.
merge_zone_and_listener_confs(ZoneConf, ListenerConf) ->
ConfsInZonesOnly = [listeners, overall_max_connections],
BaseConf = maps:without(ConfsInZonesOnly, ZoneConf),
emqx_map_lib:deep_merge(BaseConf, ListenerConf).
apply_on_listener(ListenerId, Do) ->
{ZoneName, ListenerName} = decode_listener_id(ListenerId),
case emqx_config:find([zones, ZoneName, listeners, ListenerName]) of
{not_found, _, _} -> error({not_found, ListenerId});
{ok, Conf} -> Do(ZoneName, ListenerName, Conf)
end. end.

View File

@ -0,0 +1,101 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_map_lib).
-export([ deep_get/2
, deep_get/3
, deep_find/2
, deep_put/3
, deep_merge/2
, safe_atom_key_map/1
, unsafe_atom_key_map/1
]).
-export_type([config_key/0, config_key_path/0]).
-type config_key() :: atom() | binary().
-type config_key_path() :: [config_key()].
%%-----------------------------------------------------------------
-spec deep_get(config_key_path(), map()) -> term().
deep_get(ConfKeyPath, Map) ->
case deep_find(ConfKeyPath, Map) of
{not_found, KeyPath, Data} -> error({not_found, KeyPath, Data});
{ok, Data} -> Data
end.
-spec deep_get(config_key_path(), map(), term()) -> term().
deep_get(ConfKeyPath, Map, Default) ->
case deep_find(ConfKeyPath, Map) of
{not_found, _KeyPath, _Data} -> Default;
{ok, Data} -> Data
end.
-spec deep_find(config_key_path(), map()) ->
{ok, term()} | {not_found, config_key_path(), term()}.
deep_find([], Map) ->
{ok, Map};
deep_find([Key | KeyPath] = Path, Map) when is_map(Map) ->
case maps:find(Key, Map) of
{ok, SubMap} -> deep_find(KeyPath, SubMap);
error -> {not_found, Path, Map}
end;
deep_find(_KeyPath, Data) ->
{not_found, _KeyPath, Data}.
-spec deep_put(config_key_path(), map(), term()) -> map().
deep_put([], Map, Config) when is_map(Map) ->
Config;
deep_put([], _Map, Config) -> %% not map, replace it
Config;
deep_put([Key | KeyPath], Map, Config) ->
SubMap = deep_put(KeyPath, maps:get(Key, Map, #{}), Config),
Map#{Key => SubMap}.
%% #{a => #{b => 3, c => 2}, d => 4}
%% = deep_merge(#{a => #{b => 1, c => 2}, d => 4}, #{a => #{b => 3}}).
-spec deep_merge(map(), map()) -> map().
deep_merge(BaseMap, NewMap) ->
NewKeys = maps:keys(NewMap) -- maps:keys(BaseMap),
MergedBase = maps:fold(fun(K, V, Acc) ->
case maps:find(K, NewMap) of
error ->
Acc#{K => V};
{ok, NewV} when is_map(V), is_map(NewV) ->
Acc#{K => deep_merge(V, NewV)};
{ok, NewV} ->
Acc#{K => NewV}
end
end, #{}, BaseMap),
maps:merge(MergedBase, maps:with(NewKeys, NewMap)).
unsafe_atom_key_map(Map) ->
covert_keys_to_atom(Map, fun(K) -> binary_to_atom(K, utf8) end).
safe_atom_key_map(Map) ->
covert_keys_to_atom(Map, fun(K) -> binary_to_existing_atom(K, utf8) end).
%%---------------------------------------------------------------------------
covert_keys_to_atom(BinKeyMap, Conv) when is_map(BinKeyMap) ->
maps:fold(
fun(K, V, Acc) when is_binary(K) ->
Acc#{Conv(K) => covert_keys_to_atom(V, Conv)};
(K, V, Acc) when is_atom(K) ->
%% richmap keys
Acc#{K => covert_keys_to_atom(V, Conv)}
end, #{}, BinKeyMap);
covert_keys_to_atom(ListV, Conv) when is_list(ListV) ->
[covert_keys_to_atom(V, Conv) || V <- ListV];
covert_keys_to_atom(Val, _) -> Val.

View File

@ -197,7 +197,8 @@ check_oom(Policy) ->
check_oom(self(), Policy). check_oom(self(), Policy).
-spec(check_oom(pid(), emqx_types:oom_policy()) -> ok | {shutdown, term()}). -spec(check_oom(pid(), emqx_types:oom_policy()) -> ok | {shutdown, term()}).
check_oom(Pid, #{message_queue_len := MaxQLen, check_oom(_Pid, #{enable := false}) -> ok;
check_oom(Pid, #{max_message_queue_len := MaxQLen,
max_heap_size := MaxHeapSize}) -> max_heap_size := MaxHeapSize}) ->
case process_info(Pid, [message_queue_len, total_heap_size]) of case process_info(Pid, [message_queue_len, total_heap_size]) of
undefined -> ok; undefined -> ok;
@ -214,13 +215,26 @@ do_check_oom([{Val, Max, Reason}|Rest]) ->
false -> do_check_oom(Rest) false -> do_check_oom(Rest)
end. end.
tune_heap_size(#{max_heap_size := MaxHeapSize}) -> tune_heap_size(#{enable := false}) ->
%% If set to zero, the limit is disabled. ok;
erlang:process_flag(max_heap_size, #{size => MaxHeapSize, %% If the max_heap_size is set to zero, the limit is disabled.
kill => false, tune_heap_size(#{max_heap_size := MaxHeapSize}) when MaxHeapSize > 0 ->
MaxSize = case erlang:system_info(wordsize) of
8 -> % arch_64
(1 bsl 59) - 1;
4 -> % arch_32
(1 bsl 27) - 1
end,
OverflowedSize = case erlang:trunc(MaxHeapSize * 1.5) of
SZ when SZ > MaxSize -> MaxSize;
SZ -> SZ
end,
erlang:process_flag(max_heap_size, #{
size => OverflowedSize,
kill => true,
error_logger => true error_logger => true
}); }).
tune_heap_size(undefined) -> ok.
-spec(proc_name(atom(), pos_integer()) -> atom()). -spec(proc_name(atom(), pos_integer()) -> atom()).
proc_name(Mod, Id) -> proc_name(Mod, Id) ->

View File

@ -20,19 +20,13 @@
-include("emqx_mqtt.hrl"). -include("emqx_mqtt.hrl").
-include("types.hrl"). -include("types.hrl").
-export([ check_pub/2 -export([ check_pub/3
, check_sub/3 , check_sub/4
]). ]).
-export([ get_caps/1 -export([ get_caps/2
, get_caps/2
, get_caps/3
]). ]).
-export([default_caps/0]).
-export([default/0]).
-export_type([caps/0]). -export_type([caps/0]).
-type(caps() :: #{max_packet_size => integer(), -type(caps() :: #{max_packet_size => integer(),
@ -46,7 +40,7 @@
shared_subscription => boolean() shared_subscription => boolean()
}). }).
-define(UNLIMITED, 0). -define(MAX_TOPIC_LEVELS, 65535).
-define(PUBCAP_KEYS, [max_topic_levels, -define(PUBCAP_KEYS, [max_topic_levels,
max_qos_allowed, max_qos_allowed,
@ -62,7 +56,7 @@
-define(DEFAULT_CAPS, #{max_packet_size => ?MAX_PACKET_SIZE, -define(DEFAULT_CAPS, #{max_packet_size => ?MAX_PACKET_SIZE,
max_clientid_len => ?MAX_CLIENTID_LEN, max_clientid_len => ?MAX_CLIENTID_LEN,
max_topic_alias => ?MAX_TOPIC_AlIAS, max_topic_alias => ?MAX_TOPIC_AlIAS,
max_topic_levels => ?UNLIMITED, max_topic_levels => ?MAX_TOPIC_LEVELS,
max_qos_allowed => ?QOS_2, max_qos_allowed => ?QOS_2,
retain_available => true, retain_available => true,
wildcard_subscription => true, wildcard_subscription => true,
@ -70,18 +64,18 @@
shared_subscription => true shared_subscription => true
}). }).
-spec(check_pub(emqx_types:zone(), -spec(check_pub(emqx_types:zone(), atom(),
#{qos := emqx_types:qos(), #{qos := emqx_types:qos(),
retain := boolean(), retain := boolean(),
topic := emqx_topic:topic()}) topic := emqx_topic:topic()})
-> ok_or_error(emqx_types:reason_code())). -> ok_or_error(emqx_types:reason_code())).
check_pub(Zone, Flags) when is_map(Flags) -> check_pub(Zone, Listener, Flags) when is_map(Flags) ->
do_check_pub(case maps:take(topic, Flags) of do_check_pub(case maps:take(topic, Flags) of
{Topic, Flags1} -> {Topic, Flags1} ->
Flags1#{topic_levels => emqx_topic:levels(Topic)}; Flags1#{topic_levels => emqx_topic:levels(Topic)};
error -> error ->
Flags Flags
end, get_caps(Zone, publish)). end, maps:with(?PUBCAP_KEYS, get_caps(Zone, Listener))).
do_check_pub(#{topic_levels := Levels}, #{max_topic_levels := Limit}) do_check_pub(#{topic_levels := Levels}, #{max_topic_levels := Limit})
when Limit > 0, Levels > Limit -> when Limit > 0, Levels > Limit ->
@ -93,12 +87,12 @@ do_check_pub(#{retain := true}, #{retain_available := false}) ->
{error, ?RC_RETAIN_NOT_SUPPORTED}; {error, ?RC_RETAIN_NOT_SUPPORTED};
do_check_pub(_Flags, _Caps) -> ok. do_check_pub(_Flags, _Caps) -> ok.
-spec(check_sub(emqx_types:zone(), -spec(check_sub(emqx_types:zone(), atom(),
emqx_types:topic(), emqx_types:topic(),
emqx_types:subopts()) emqx_types:subopts())
-> ok_or_error(emqx_types:reason_code())). -> ok_or_error(emqx_types:reason_code())).
check_sub(Zone, Topic, SubOpts) -> check_sub(Zone, Listener, Topic, SubOpts) ->
Caps = get_caps(Zone, subscribe), Caps = maps:with(?SUBCAP_KEYS, get_caps(Zone, Listener)),
Flags = lists:foldl( Flags = lists:foldl(
fun(max_topic_levels, Map) -> fun(max_topic_levels, Map) ->
Map#{topic_levels => emqx_topic:levels(Topic)}; Map#{topic_levels => emqx_topic:levels(Topic)};
@ -119,42 +113,7 @@ do_check_sub(#{is_shared := true}, #{shared_subscription := false}) ->
{error, ?RC_SHARED_SUBSCRIPTIONS_NOT_SUPPORTED}; {error, ?RC_SHARED_SUBSCRIPTIONS_NOT_SUPPORTED};
do_check_sub(_Flags, _Caps) -> ok. do_check_sub(_Flags, _Caps) -> ok.
default_caps() -> get_caps(Zone, Listener) ->
?DEFAULT_CAPS. lists:foldl(fun({K, V}, Acc) ->
Acc#{K => emqx_config:get_listener_conf(Zone, Listener, [mqtt, K], V)}
get_caps(Zone, Cap, Def) -> end, #{}, maps:to_list(?DEFAULT_CAPS)).
emqx_zone:get_env(Zone, Cap, Def).
get_caps(Zone, publish) ->
with_env(Zone, '$mqtt_pub_caps',
fun() ->
filter_caps(?PUBCAP_KEYS, get_caps(Zone))
end);
get_caps(Zone, subscribe) ->
with_env(Zone, '$mqtt_sub_caps',
fun() ->
filter_caps(?SUBCAP_KEYS, get_caps(Zone))
end).
get_caps(Zone) ->
with_env(Zone, '$mqtt_caps',
fun() ->
maps:map(fun(Cap, Def) ->
emqx_zone:get_env(Zone, Cap, Def)
end, ?DEFAULT_CAPS)
end).
filter_caps(Keys, Caps) ->
maps:filter(fun(Key, _Val) -> lists:member(Key, Keys) end, Caps).
-spec(default() -> caps()).
default() -> ?DEFAULT_CAPS.
with_env(Zone, Key, InitFun) ->
case emqx_zone:get_env(Zone, Key) of
undefined -> Caps = InitFun(),
ok = emqx_zone:set_env(Zone, Key, Caps),
Caps;
ZoneCaps -> ZoneCaps
end.

View File

@ -67,6 +67,8 @@
, dropped/1 , dropped/1
]). ]).
-define(NO_PRIORITY_TABLE, disabled).
-export_type([mqueue/0, options/0]). -export_type([mqueue/0, options/0]).
-type(topic() :: emqx_topic:topic()). -type(topic() :: emqx_topic:topic()).

View File

@ -22,15 +22,9 @@
-logger_header("[OS_MON]"). -logger_header("[OS_MON]").
-export([start_link/1]). -export([start_link/0]).
-export([ get_cpu_check_interval/0 -export([ get_mem_check_interval/0
, set_cpu_check_interval/1
, get_cpu_high_watermark/0
, set_cpu_high_watermark/1
, get_cpu_low_watermark/0
, set_cpu_low_watermark/1
, get_mem_check_interval/0
, set_mem_check_interval/1 , set_mem_check_interval/1
, get_sysmem_high_watermark/0 , get_sysmem_high_watermark/0
, set_sysmem_high_watermark/1 , set_sysmem_high_watermark/1
@ -51,31 +45,13 @@
-define(OS_MON, ?MODULE). -define(OS_MON, ?MODULE).
start_link(Opts) -> start_link() ->
gen_server:start_link({local, ?OS_MON}, ?MODULE, [Opts], []). gen_server:start_link({local, ?OS_MON}, ?MODULE, [], []).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% API %% API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
get_cpu_check_interval() ->
call(get_cpu_check_interval).
set_cpu_check_interval(Seconds) ->
call({set_cpu_check_interval, Seconds}).
get_cpu_high_watermark() ->
call(get_cpu_high_watermark).
set_cpu_high_watermark(Float) ->
call({set_cpu_high_watermark, Float}).
get_cpu_low_watermark() ->
call(get_cpu_low_watermark).
set_cpu_low_watermark(Float) ->
call({set_cpu_low_watermark, Float}).
get_mem_check_interval() -> get_mem_check_interval() ->
memsup:get_check_interval() div 1000. memsup:get_check_interval() div 1000.
@ -88,47 +64,25 @@ get_sysmem_high_watermark() ->
memsup:get_sysmem_high_watermark(). memsup:get_sysmem_high_watermark().
set_sysmem_high_watermark(Float) -> set_sysmem_high_watermark(Float) ->
memsup:set_sysmem_high_watermark(Float / 100). memsup:set_sysmem_high_watermark(Float).
get_procmem_high_watermark() -> get_procmem_high_watermark() ->
memsup:get_procmem_high_watermark(). memsup:get_procmem_high_watermark().
set_procmem_high_watermark(Float) -> set_procmem_high_watermark(Float) ->
memsup:set_procmem_high_watermark(Float / 100). memsup:set_procmem_high_watermark(Float).
call(Req) ->
gen_server:call(?OS_MON, Req, infinity).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% gen_server callbacks %% gen_server callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([Opts]) -> init([]) ->
set_mem_check_interval(proplists:get_value(mem_check_interval, Opts)), Opts = emqx_config:get([sysmon, os]),
set_sysmem_high_watermark(proplists:get_value(sysmem_high_watermark, Opts)), set_mem_check_interval(maps:get(mem_check_interval, Opts)),
set_procmem_high_watermark(proplists:get_value(procmem_high_watermark, Opts)), set_sysmem_high_watermark(maps:get(sysmem_high_watermark, Opts)),
{ok, ensure_check_timer(#{cpu_high_watermark => proplists:get_value(cpu_high_watermark, Opts), set_procmem_high_watermark(maps:get(procmem_high_watermark, Opts)),
cpu_low_watermark => proplists:get_value(cpu_low_watermark, Opts), _ = start_check_timer(),
cpu_check_interval => proplists:get_value(cpu_check_interval, Opts), {ok, #{}}.
timer => undefined})}.
handle_call(get_cpu_check_interval, _From, State) ->
{reply, maps:get(cpu_check_interval, State, undefined), State};
handle_call({set_cpu_check_interval, Seconds}, _From, State) ->
{reply, ok, State#{cpu_check_interval := Seconds}};
handle_call(get_cpu_high_watermark, _From, State) ->
{reply, maps:get(cpu_high_watermark, State, undefined), State};
handle_call({set_cpu_high_watermark, Float}, _From, State) ->
{reply, ok, State#{cpu_high_watermark := Float}};
handle_call(get_cpu_low_watermark, _From, State) ->
{reply, maps:get(cpu_low_watermark, State, undefined), State};
handle_call({set_cpu_low_watermark, Float}, _From, State) ->
{reply, ok, State#{cpu_low_watermark := Float}};
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
?LOG(error, "Unexpected call: ~p", [Req]), ?LOG(error, "Unexpected call: ~p", [Req]),
@ -138,32 +92,30 @@ handle_cast(Msg, State) ->
?LOG(error, "Unexpected cast: ~p", [Msg]), ?LOG(error, "Unexpected cast: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info({timeout, Timer, check}, State = #{timer := Timer, handle_info({timeout, _Timer, check}, State) ->
cpu_high_watermark := CPUHighWatermark, CPUHighWatermark = emqx_config:get([sysmon, os, cpu_high_watermark]) * 100,
cpu_low_watermark := CPULowWatermark}) -> CPULowWatermark = emqx_config:get([sysmon, os, cpu_low_watermark]) * 100,
NState = _ = case emqx_vm:cpu_util() of %% TODO: should be improved?
case emqx_vm:cpu_util() of %% TODO: should be improved? 0 -> ok;
0 ->
State#{timer := undefined};
Busy when Busy >= CPUHighWatermark -> Busy when Busy >= CPUHighWatermark ->
emqx_alarm:activate(high_cpu_usage, #{usage => Busy, emqx_alarm:activate(high_cpu_usage, #{usage => io_lib:format("~p%", [Busy]),
high_watermark => CPUHighWatermark, high_watermark => CPUHighWatermark,
low_watermark => CPULowWatermark}), low_watermark => CPULowWatermark}),
ensure_check_timer(State); start_check_timer();
Busy when Busy =< CPULowWatermark -> Busy when Busy =< CPULowWatermark ->
emqx_alarm:deactivate(high_cpu_usage), emqx_alarm:deactivate(high_cpu_usage),
ensure_check_timer(State); start_check_timer();
_Busy -> _Busy ->
ensure_check_timer(State) start_check_timer()
end, end,
{noreply, NState}; {noreply, State};
handle_info(Info, State) -> handle_info(Info, State) ->
?LOG(error, "unexpected info: ~p", [Info]), ?LOG(error, "unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, #{timer := Timer}) -> terminate(_Reason, _State) ->
emqx_misc:cancel_timer(Timer). ok.
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
@ -172,8 +124,9 @@ code_change(_OldVsn, State, _Extra) ->
%% Internal functions %% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
ensure_check_timer(State = #{cpu_check_interval := Interval}) -> start_check_timer() ->
Interval = emqx_config:get([sysmon, os, cpu_check_interval]),
case erlang:system_info(system_architecture) of case erlang:system_info(system_architecture) of
"x86_64-pc-linux-musl" -> State; "x86_64-pc-linux-musl" -> ok;
_ -> State#{timer := emqx_misc:start_timer(timer:seconds(Interval), check)} _ -> emqx_misc:start_timer(timer:seconds(Interval), check)
end. end.

View File

@ -21,6 +21,4 @@
]). ]).
new_conn(Conn, {_L, COpts, _S}) when is_map(COpts) -> new_conn(Conn, {_L, COpts, _S}) when is_map(COpts) ->
new_conn(Conn, maps:to_list(COpts));
new_conn(Conn, COpts) ->
emqx_connection:start_link(emqx_quic_stream, Conn, COpts). emqx_connection:start_link(emqx_quic_stream, Conn, COpts).

File diff suppressed because it is too large Load Diff

View File

@ -92,13 +92,11 @@
-export_type([session/0]). -export_type([session/0]).
-import(emqx_zone, [get_env/3]).
-record(session, { -record(session, {
%% Clients Subscriptions. %% Clients Subscriptions.
subscriptions :: map(), subscriptions :: map(),
%% Max subscriptions allowed %% Max subscriptions allowed
max_subscriptions :: non_neg_integer(), max_subscriptions :: non_neg_integer() | infinity,
%% Upgrade QoS? %% Upgrade QoS?
upgrade_qos :: boolean(), upgrade_qos :: boolean(),
%% Client <- Broker: QoS1/2 messages sent to the client but %% Client <- Broker: QoS1/2 messages sent to the client but
@ -117,7 +115,7 @@
%% have not been completely acknowledged %% have not been completely acknowledged
awaiting_rel :: map(), awaiting_rel :: map(),
%% Maximum number of awaiting QoS2 messages allowed %% Maximum number of awaiting QoS2 messages allowed
max_awaiting_rel :: non_neg_integer(), max_awaiting_rel :: non_neg_integer() | infinity,
%% Awaiting PUBREL Timeout (Unit: millsecond) %% Awaiting PUBREL Timeout (Unit: millsecond)
await_rel_timeout :: timeout(), await_rel_timeout :: timeout(),
%% Created at %% Created at
@ -159,26 +157,27 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(init(emqx_types:clientinfo(), emqx_types:conninfo()) -> session()). -spec(init(emqx_types:clientinfo(), emqx_types:conninfo()) -> session()).
init(#{zone := Zone}, #{receive_maximum := MaxInflight}) -> init(#{zone := Zone, listener := Listener}, #{receive_maximum := MaxInflight}) ->
#session{max_subscriptions = get_env(Zone, max_subscriptions, 0), #session{max_subscriptions = get_conf(Zone, Listener, max_subscriptions),
subscriptions = #{}, subscriptions = #{},
upgrade_qos = get_env(Zone, upgrade_qos, false), upgrade_qos = get_conf(Zone, Listener, upgrade_qos),
inflight = emqx_inflight:new(MaxInflight), inflight = emqx_inflight:new(MaxInflight),
mqueue = init_mqueue(Zone), mqueue = init_mqueue(Zone, Listener),
next_pkt_id = 1, next_pkt_id = 1,
retry_interval = timer:seconds(get_env(Zone, retry_interval, 0)), retry_interval = timer:seconds(get_conf(Zone, Listener, retry_interval)),
awaiting_rel = #{}, awaiting_rel = #{},
max_awaiting_rel = get_env(Zone, max_awaiting_rel, 100), max_awaiting_rel = get_conf(Zone, Listener, max_awaiting_rel),
await_rel_timeout = timer:seconds(get_env(Zone, await_rel_timeout, 300)), await_rel_timeout = timer:seconds(get_conf(Zone, Listener, await_rel_timeout)),
created_at = erlang:system_time(millisecond) created_at = erlang:system_time(millisecond)
}. }.
%% @private init mq %% @private init mq
init_mqueue(Zone) -> init_mqueue(Zone, Listener) ->
emqx_mqueue:init(#{max_len => get_env(Zone, max_mqueue_len, 1000), emqx_mqueue:init(#{
store_qos0 => get_env(Zone, mqueue_store_qos0, true), max_len => get_conf(Zone, Listener, max_mqueue_len),
priorities => get_env(Zone, mqueue_priorities, none), store_qos0 => get_conf(Zone, Listener, mqueue_store_qos0),
default_priority => get_env(Zone, mqueue_default_priority, lowest) priorities => get_conf(Zone, Listener, mqueue_priorities),
default_priority => get_conf(Zone, Listener, mqueue_default_priority)
}). }).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -253,7 +252,7 @@ subscribe(ClientInfo = #{clientid := ClientId}, TopicFilter, SubOpts,
end. end.
-compile({inline, [is_subscriptions_full/1]}). -compile({inline, [is_subscriptions_full/1]}).
is_subscriptions_full(#session{max_subscriptions = 0}) -> is_subscriptions_full(#session{max_subscriptions = infinity}) ->
false; false;
is_subscriptions_full(#session{subscriptions = Subs, is_subscriptions_full(#session{subscriptions = Subs,
max_subscriptions = MaxLimit}) -> max_subscriptions = MaxLimit}) ->
@ -302,7 +301,7 @@ publish(_PacketId, Msg, Session) ->
{ok, emqx_broker:publish(Msg), Session}. {ok, emqx_broker:publish(Msg), Session}.
-compile({inline, [is_awaiting_full/1]}). -compile({inline, [is_awaiting_full/1]}).
is_awaiting_full(#session{max_awaiting_rel = 0}) -> is_awaiting_full(#session{max_awaiting_rel = infinity}) ->
false; false;
is_awaiting_full(#session{awaiting_rel = AwaitingRel, is_awaiting_full(#session{awaiting_rel = AwaitingRel,
max_awaiting_rel = MaxLimit}) -> max_awaiting_rel = MaxLimit}) ->
@ -697,3 +696,5 @@ set_field(Name, Value, Session) ->
Pos = emqx_misc:index_of(Name, record_info(fields, session)), Pos = emqx_misc:index_of(Name, record_info(fields, session)),
setelement(Pos+1, Session, Value). setelement(Pos+1, Session, Value).
get_conf(Zone, Listener, Key) ->
emqx_config:get_listener_conf(Zone, Listener, [mqtt, Key]).

View File

@ -32,8 +32,6 @@
, uptime/0 , uptime/0
, datetime/0 , datetime/0
, sysdescr/0 , sysdescr/0
, sys_interval/0
, sys_heatbeat_interval/0
]). ]).
-export([info/0]). -export([info/0]).
@ -104,15 +102,11 @@ datetime() ->
io_lib:format( io_lib:format(
"~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w", [Y, M, D, H, MM, S])). "~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w", [Y, M, D, H, MM, S])).
%% @doc Get sys interval
-spec(sys_interval() -> pos_integer()).
sys_interval() -> sys_interval() ->
emqx:get_env(broker_sys_interval, 60000). emqx_config:get([broker, sys_msg_interval]).
%% @doc Get sys heatbeat interval
-spec(sys_heatbeat_interval() -> pos_integer()).
sys_heatbeat_interval() -> sys_heatbeat_interval() ->
emqx:get_env(broker_sys_heartbeat, 30000). emqx_config:get([broker, sys_heartbeat_interval]).
%% @doc Get sys info %% @doc Get sys info
-spec(info() -> list(tuple())). -spec(info() -> list(tuple())).

View File

@ -23,7 +23,7 @@
-logger_header("[SYSMON]"). -logger_header("[SYSMON]").
-export([start_link/1]). -export([start_link/0]).
%% compress unused warning %% compress unused warning
-export([procinfo/1]). -export([procinfo/1]).
@ -37,25 +37,19 @@
, code_change/3 , code_change/3
]). ]).
-type(option() :: {long_gc, non_neg_integer()}
| {long_schedule, non_neg_integer()}
| {large_heap, non_neg_integer()}
| {busy_port, boolean()}
| {busy_dist_port, boolean()}).
-define(SYSMON, ?MODULE). -define(SYSMON, ?MODULE).
%% @doc Start the system monitor. %% @doc Start the system monitor.
-spec(start_link(list(option())) -> startlink_ret()). -spec(start_link() -> startlink_ret()).
start_link(Opts) -> start_link() ->
gen_server:start_link({local, ?SYSMON}, ?MODULE, [Opts], []). gen_server:start_link({local, ?SYSMON}, ?MODULE, [], []).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% gen_server callbacks %% gen_server callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([Opts]) -> init([]) ->
_ = erlang:system_monitor(self(), parse_opt(Opts)), _ = erlang:system_monitor(self(), sysm_opts()),
emqx_logger:set_proc_metadata(#{sysmon => true}), emqx_logger:set_proc_metadata(#{sysmon => true}),
%% Monitor cluster partition event %% Monitor cluster partition event
@ -66,30 +60,28 @@ init([Opts]) ->
start_timer(State) -> start_timer(State) ->
State#{timer := emqx_misc:start_timer(timer:seconds(2), reset)}. State#{timer := emqx_misc:start_timer(timer:seconds(2), reset)}.
parse_opt(Opts) -> sysm_opts() ->
parse_opt(Opts, []). sysm_opts(maps:to_list(emqx_config:get([sysmon, vm])), []).
parse_opt([], Acc) -> sysm_opts([], Acc) ->
Acc; Acc;
parse_opt([{long_gc, 0}|Opts], Acc) -> sysm_opts([{_, disabled}|Opts], Acc) ->
parse_opt(Opts, Acc); sysm_opts(Opts, Acc);
parse_opt([{long_gc, Ms}|Opts], Acc) when is_integer(Ms) -> sysm_opts([{long_gc, Ms}|Opts], Acc) when is_integer(Ms) ->
parse_opt(Opts, [{long_gc, Ms}|Acc]); sysm_opts(Opts, [{long_gc, Ms}|Acc]);
parse_opt([{long_schedule, 0}|Opts], Acc) -> sysm_opts([{long_schedule, Ms}|Opts], Acc) when is_integer(Ms) ->
parse_opt(Opts, Acc); sysm_opts(Opts, [{long_schedule, Ms}|Acc]);
parse_opt([{long_schedule, Ms}|Opts], Acc) when is_integer(Ms) -> sysm_opts([{large_heap, Size}|Opts], Acc) when is_integer(Size) ->
parse_opt(Opts, [{long_schedule, Ms}|Acc]); sysm_opts(Opts, [{large_heap, Size}|Acc]);
parse_opt([{large_heap, Size}|Opts], Acc) when is_integer(Size) -> sysm_opts([{busy_port, true}|Opts], Acc) ->
parse_opt(Opts, [{large_heap, Size}|Acc]); sysm_opts(Opts, [busy_port|Acc]);
parse_opt([{busy_port, true}|Opts], Acc) -> sysm_opts([{busy_port, false}|Opts], Acc) ->
parse_opt(Opts, [busy_port|Acc]); sysm_opts(Opts, Acc);
parse_opt([{busy_port, false}|Opts], Acc) -> sysm_opts([{busy_dist_port, true}|Opts], Acc) ->
parse_opt(Opts, Acc); sysm_opts(Opts, [busy_dist_port|Acc]);
parse_opt([{busy_dist_port, true}|Opts], Acc) -> sysm_opts([{busy_dist_port, false}|Opts], Acc) ->
parse_opt(Opts, [busy_dist_port|Acc]); sysm_opts(Opts, Acc);
parse_opt([{busy_dist_port, false}|Opts], Acc) -> sysm_opts([_Opt|Opts], Acc) ->
parse_opt(Opts, Acc); sysm_opts(Opts, Acc).
parse_opt([_Opt|Opts], Acc) ->
parse_opt(Opts, Acc).
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
?LOG(error, "Unexpected call: ~p", [Req]), ?LOG(error, "Unexpected call: ~p", [Req]),

View File

@ -27,10 +27,10 @@ start_link() ->
init([]) -> init([]) ->
Childs = [child_spec(emqx_sys), Childs = [child_spec(emqx_sys),
child_spec(emqx_alarm, [config(alarm)]), child_spec(emqx_alarm),
child_spec(emqx_sys_mon, [config(sysmon)]), child_spec(emqx_sys_mon),
child_spec(emqx_os_mon, [config(os_mon)]), child_spec(emqx_os_mon),
child_spec(emqx_vm_mon, [config(vm_mon)])], child_spec(emqx_vm_mon)],
{ok, {{one_for_one, 10, 100}, Childs}}. {ok, {{one_for_one, 10, 100}, Childs}}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -48,6 +48,3 @@ child_spec(Mod, Args) ->
type => worker, type => worker,
modules => [Mod] modules => [Mod]
}. }.
config(Name) -> emqx:get_env(Name, []).

View File

@ -161,17 +161,16 @@ drop_tls13_for_old_otp(SslOpts) ->
, "TLS_AES_128_CCM_8_SHA256" , "TLS_AES_128_CCM_8_SHA256"
]). ]).
drop_tls13(SslOpts0) -> drop_tls13(SslOpts0) ->
SslOpts1 = case proplists:get_value(versions, SslOpts0) of SslOpts1 = case maps:find(versions, SslOpts0) of
undefined -> SslOpts0; error -> SslOpts0;
Vsns -> replace(SslOpts0, versions, Vsns -- ['tlsv1.3']) {ok, Vsns} -> SslOpts0#{versions => (Vsns -- ['tlsv1.3'])}
end, end,
case proplists:get_value(ciphers, SslOpts1) of case maps:find(ciphers, SslOpts1) of
undefined -> SslOpts1; error -> SslOpts1;
Ciphers -> replace(SslOpts1, ciphers, Ciphers -- ?TLSV13_EXCLUSIVE_CIPHERS) {ok, Ciphers} ->
SslOpts1#{ciphers => Ciphers -- ?TLSV13_EXCLUSIVE_CIPHERS}
end. end.
replace(Opts, Key, Value) -> [{Key, Value} | proplists:delete(Key, Opts)].
-if(?OTP_RELEASE > 22). -if(?OTP_RELEASE > 22).
-ifdef(TEST). -ifdef(TEST).
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
@ -181,13 +180,13 @@ drop_tls13_test() ->
?assert(lists:member('tlsv1.3', Versions)), ?assert(lists:member('tlsv1.3', Versions)),
Ciphers = default_ciphers(), Ciphers = default_ciphers(),
?assert(has_tlsv13_cipher(Ciphers)), ?assert(has_tlsv13_cipher(Ciphers)),
Opts0 = [{versions, Versions}, {ciphers, Ciphers}, other, {bool, true}], Opts0 = #{versions => Versions, ciphers => Ciphers, other => true},
Opts = drop_tls13(Opts0), Opts = drop_tls13(Opts0),
?assertNot(lists:member('tlsv1.3', proplists:get_value(versions, Opts))), ?assertNot(lists:member('tlsv1.3', maps:get(versions, Opts, undefined))),
?assertNot(has_tlsv13_cipher(proplists:get_value(ciphers, Opts))). ?assertNot(has_tlsv13_cipher(maps:get(ciphers, Opts, undefined))).
drop_tls13_no_versions_cipers_test() -> drop_tls13_no_versions_cipers_test() ->
Opts0 = [other, {bool, true}], Opts0 = #{other => 0, bool => true},
Opts = drop_tls13(Opts0), Opts = drop_tls13(Opts0),
?_assertEqual(Opts0, Opts). ?_assertEqual(Opts0, Opts).

View File

@ -209,7 +209,8 @@
-type(infos() :: #{atom() => term()}). -type(infos() :: #{atom() => term()}).
-type(stats() :: [{atom(), term()}]). -type(stats() :: [{atom(), term()}]).
-type(oom_policy() :: #{message_queue_len => non_neg_integer(), -type(oom_policy() :: #{max_message_queue_len => non_neg_integer(),
max_heap_size => non_neg_integer() max_heap_size => non_neg_integer(),
enable => boolean()
}). }).

View File

@ -21,15 +21,7 @@
-include("logger.hrl"). -include("logger.hrl").
%% APIs %% APIs
-export([start_link/1]). -export([start_link/0]).
-export([ get_check_interval/0
, set_check_interval/1
, get_process_high_watermark/0
, set_process_high_watermark/1
, get_process_low_watermark/0
, set_process_low_watermark/1
]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([ init/1
@ -42,61 +34,19 @@
-define(VM_MON, ?MODULE). -define(VM_MON, ?MODULE).
start_link(Opts) ->
gen_server:start_link({local, ?VM_MON}, ?MODULE, [Opts], []).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% API %% API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
start_link() ->
get_check_interval() -> gen_server:start_link({local, ?VM_MON}, ?MODULE, [], []).
call(get_check_interval).
set_check_interval(Seconds) ->
call({set_check_interval, Seconds}).
get_process_high_watermark() ->
call(get_process_high_watermark).
set_process_high_watermark(Float) ->
call({set_process_high_watermark, Float}).
get_process_low_watermark() ->
call(get_process_low_watermark).
set_process_low_watermark(Float) ->
call({set_process_low_watermark, Float}).
call(Req) ->
gen_server:call(?VM_MON, Req, infinity).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% gen_server callbacks %% gen_server callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([Opts]) -> init([]) ->
{ok, ensure_check_timer(#{check_interval => proplists:get_value(check_interval, Opts), start_check_timer(),
process_high_watermark => proplists:get_value(process_high_watermark, Opts), {ok, #{}}.
process_low_watermark => proplists:get_value(process_low_watermark, Opts),
timer => undefined})}.
handle_call(get_check_interval, _From, State) ->
{reply, maps:get(check_interval, State, undefined), State};
handle_call({set_check_interval, Seconds}, _From, State) ->
{reply, ok, State#{check_interval := Seconds}};
handle_call(get_process_high_watermark, _From, State) ->
{reply, maps:get(process_high_watermark, State, undefined), State};
handle_call({set_process_high_watermark, Float}, _From, State) ->
{reply, ok, State#{process_high_watermark := Float}};
handle_call(get_process_low_watermark, _From, State) ->
{reply, maps:get(process_low_watermark, State, undefined), State};
handle_call({set_process_low_watermark, Float}, _From, State) ->
{reply, ok, State#{process_low_watermark := Float}};
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
?LOG(error, "[VM_MON] Unexpected call: ~p", [Req]), ?LOG(error, "[VM_MON] Unexpected call: ~p", [Req]),
@ -106,14 +56,14 @@ handle_cast(Msg, State) ->
?LOG(error, "[VM_MON] Unexpected cast: ~p", [Msg]), ?LOG(error, "[VM_MON] Unexpected cast: ~p", [Msg]),
{noreply, State}. {noreply, State}.
handle_info({timeout, Timer, check}, handle_info({timeout, _Timer, check}, State) ->
State = #{timer := Timer, ProcHighWatermark = emqx_config:get([sysmon, vm, process_high_watermark]),
process_high_watermark := ProcHighWatermark, ProcLowWatermark = emqx_config:get([sysmon, vm, process_low_watermark]),
process_low_watermark := ProcLowWatermark}) ->
ProcessCount = erlang:system_info(process_count), ProcessCount = erlang:system_info(process_count),
case ProcessCount / erlang:system_info(process_limit) * 100 of case ProcessCount / erlang:system_info(process_limit) of
Percent when Percent >= ProcHighWatermark -> Percent when Percent >= ProcHighWatermark ->
emqx_alarm:activate(too_many_processes, #{usage => Percent, emqx_alarm:activate(too_many_processes, #{
usage => io_lib:format("~p%", [Percent*100]),
high_watermark => ProcHighWatermark, high_watermark => ProcHighWatermark,
low_watermark => ProcLowWatermark}); low_watermark => ProcLowWatermark});
Percent when Percent < ProcLowWatermark -> Percent when Percent < ProcLowWatermark ->
@ -121,14 +71,15 @@ handle_info({timeout, Timer, check},
_Precent -> _Precent ->
ok ok
end, end,
{noreply, ensure_check_timer(State)}; start_check_timer(),
{noreply, State};
handle_info(Info, State) -> handle_info(Info, State) ->
?LOG(error, "[VM_MON] Unexpected info: ~p", [Info]), ?LOG(error, "[VM_MON] Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
terminate(_Reason, #{timer := Timer}) -> terminate(_Reason, _State) ->
emqx_misc:cancel_timer(Timer). ok.
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
@ -137,5 +88,6 @@ code_change(_OldVsn, State, _Extra) ->
%% Internal functions %% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
ensure_check_timer(State = #{check_interval := Interval}) -> start_check_timer() ->
State#{timer := emqx_misc:start_timer(timer:seconds(Interval), check)}. Interval = emqx_config:get([sysmon, vm, process_check_interval]),
emqx_misc:start_timer(Interval, check).

View File

@ -62,8 +62,6 @@
sockname :: emqx_types:peername(), sockname :: emqx_types:peername(),
%% Sock state %% Sock state
sockstate :: emqx_types:sockstate(), sockstate :: emqx_types:sockstate(),
%% Simulate the active_n opt
active_n :: pos_integer(),
%% MQTT Piggyback %% MQTT Piggyback
mqtt_piggyback :: single | multiple, mqtt_piggyback :: single | multiple,
%% Limiter %% Limiter
@ -85,7 +83,11 @@
%% Idle Timeout %% Idle Timeout
idle_timeout :: timeout(), idle_timeout :: timeout(),
%% Idle Timer %% Idle Timer
idle_timer :: maybe(reference()) idle_timer :: maybe(reference()),
%% Zone name
zone :: atom(),
%% Listener Name
listener :: atom()
}). }).
-type(state() :: #state{}). -type(state() :: #state{}).
@ -93,7 +95,7 @@
-type(ws_cmd() :: {active, boolean()}|close). -type(ws_cmd() :: {active, boolean()}|close).
-define(ACTIVE_N, 100). -define(ACTIVE_N, 100).
-define(INFO_KEYS, [socktype, peername, sockname, sockstate, active_n]). -define(INFO_KEYS, [socktype, peername, sockname, sockstate]).
-define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt]). -define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt]).
-define(CONN_STATS, [recv_pkt, recv_msg, send_pkt, send_msg]). -define(CONN_STATS, [recv_pkt, recv_msg, send_pkt, send_msg]).
@ -124,8 +126,6 @@ info(sockname, #state{sockname = Sockname}) ->
Sockname; Sockname;
info(sockstate, #state{sockstate = SockSt}) -> info(sockstate, #state{sockstate = SockSt}) ->
SockSt; SockSt;
info(active_n, #state{active_n = ActiveN}) ->
ActiveN;
info(limiter, #state{limiter = Limiter}) -> info(limiter, #state{limiter = Limiter}) ->
maybe_apply(fun emqx_limiter:info/1, Limiter); maybe_apply(fun emqx_limiter:info/1, Limiter);
info(channel, #state{channel = Channel}) -> info(channel, #state{channel = Channel}) ->
@ -174,21 +174,13 @@ call(WsPid, Req, Timeout) when is_pid(WsPid) ->
%% WebSocket callbacks %% WebSocket callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init(Req, Opts) -> init(Req, #{zone := Zone, listener := Listener} = Opts) ->
%% WS Transport Idle Timeout %% WS Transport Idle Timeout
IdleTimeout = proplists:get_value(idle_timeout, Opts, 7200000), WsOpts = #{compress => get_ws_opts(Zone, Listener, compress),
DeflateOptions = maps:from_list(proplists:get_value(deflate_options, Opts, [])), deflate_opts => get_ws_opts(Zone, Listener, deflate_opts),
MaxFrameSize = case proplists:get_value(max_frame_size, Opts, 0) of max_frame_size => get_ws_opts(Zone, Listener, max_frame_size),
0 -> infinity; idle_timeout => get_ws_opts(Zone, Listener, idle_timeout)
I -> I
end,
Compress = proplists:get_bool(compress, Opts),
WsOpts = #{compress => Compress,
deflate_opts => DeflateOptions,
max_frame_size => MaxFrameSize,
idle_timeout => IdleTimeout
}, },
case check_origin_header(Req, Opts) of case check_origin_header(Req, Opts) of
{error, Message} -> {error, Message} ->
?LOG(error, "Invalid Origin Header ~p~n", [Message]), ?LOG(error, "Invalid Origin Header ~p~n", [Message]),
@ -196,18 +188,17 @@ init(Req, Opts) ->
ok -> parse_sec_websocket_protocol(Req, Opts, WsOpts) ok -> parse_sec_websocket_protocol(Req, Opts, WsOpts)
end. end.
parse_sec_websocket_protocol(Req, Opts, WsOpts) -> parse_sec_websocket_protocol(Req, #{zone := Zone, listener := Listener} = Opts, WsOpts) ->
FailIfNoSubprotocol = proplists:get_value(fail_if_no_subprotocol, Opts),
case cowboy_req:parse_header(<<"sec-websocket-protocol">>, Req) of case cowboy_req:parse_header(<<"sec-websocket-protocol">>, Req) of
undefined -> undefined ->
case FailIfNoSubprotocol of case get_ws_opts(Zone, Listener, fail_if_no_subprotocol) of
true -> true ->
{ok, cowboy_req:reply(400, Req), WsOpts}; {ok, cowboy_req:reply(400, Req), WsOpts};
false -> false ->
{cowboy_websocket, Req, [Req, Opts], WsOpts} {cowboy_websocket, Req, [Req, Opts], WsOpts}
end; end;
Subprotocols -> Subprotocols ->
SupportedSubprotocols = proplists:get_value(supported_subprotocols, Opts), SupportedSubprotocols = get_ws_opts(Zone, Listener, supported_subprotocols),
NSupportedSubprotocols = [list_to_binary(Subprotocol) NSupportedSubprotocols = [list_to_binary(Subprotocol)
|| Subprotocol <- SupportedSubprotocols], || Subprotocol <- SupportedSubprotocols],
case pick_subprotocol(Subprotocols, NSupportedSubprotocols) of case pick_subprotocol(Subprotocols, NSupportedSubprotocols) of
@ -231,31 +222,30 @@ pick_subprotocol([Subprotocol | Rest], SupportedSubprotocols) ->
pick_subprotocol(Rest, SupportedSubprotocols) pick_subprotocol(Rest, SupportedSubprotocols)
end. end.
parse_header_fun_origin(Req, Opts) -> parse_header_fun_origin(Req, #{zone := Zone, listener := Listener}) ->
case cowboy_req:header(<<"origin">>, Req) of case cowboy_req:header(<<"origin">>, Req) of
undefined -> undefined ->
case proplists:get_bool(allow_origin_absence, Opts) of case get_ws_opts(Zone, Listener, allow_origin_absence) of
true -> ok; true -> ok;
false -> {error, origin_header_cannot_be_absent} false -> {error, origin_header_cannot_be_absent}
end; end;
Value -> Value ->
Origins = proplists:get_value(check_origins, Opts, []), case lists:member(Value, get_ws_opts(Zone, Listener, check_origins)) of
case lists:member(Value, Origins) of
true -> ok; true -> ok;
false -> {origin_not_allowed, Value} false -> {origin_not_allowed, Value}
end end
end. end.
check_origin_header(Req, Opts) -> check_origin_header(Req, #{zone := Zone, listener := Listener} = Opts) ->
case proplists:get_bool(check_origin_enable, Opts) of case get_ws_opts(Zone, Listener, check_origin_enable) of
true -> parse_header_fun_origin(Req, Opts); true -> parse_header_fun_origin(Req, Opts);
false -> ok false -> ok
end. end.
websocket_init([Req, Opts]) -> websocket_init([Req, #{zone := Zone, listener := Listener} = Opts]) ->
{Peername, Peercert} = {Peername, Peercert} =
case proplists:get_bool(proxy_protocol, Opts) case emqx_config:get_listener_conf(Zone, Listener, [proxy_protocol]) andalso
andalso maps:get(proxy_header, Req) of maps:get(proxy_header, Req) of
#{src_address := SrcAddr, src_port := SrcPort, ssl := SSL} -> #{src_address := SrcAddr, src_port := SrcPort, ssl := SSL} ->
SourceName = {SrcAddr, SrcPort}, SourceName = {SrcAddr, SrcPort},
%% Notice: Only CN is available in Proxy Protocol V2 additional info %% Notice: Only CN is available in Proxy Protocol V2 additional info
@ -266,7 +256,7 @@ websocket_init([Req, Opts]) ->
{SourceName, SourceSSL}; {SourceName, SourceSSL};
#{src_address := SrcAddr, src_port := SrcPort} -> #{src_address := SrcAddr, src_port := SrcPort} ->
SourceName = {SrcAddr, SrcPort}, SourceName = {SrcAddr, SrcPort},
{SourceName , nossl}; {SourceName, nossl};
_ -> _ ->
{get_peer(Req, Opts), cowboy_req:cert(Req)} {get_peer(Req, Opts), cowboy_req:cert(Req)}
end, end,
@ -288,28 +278,35 @@ websocket_init([Req, Opts]) ->
ws_cookie => WsCookie, ws_cookie => WsCookie,
conn_mod => ?MODULE conn_mod => ?MODULE
}, },
Zone = proplists:get_value(zone, Opts), Limiter = emqx_limiter:init(Zone, undefined, undefined, []),
PubLimit = emqx_zone:publish_limit(Zone), MQTTPiggyback = get_ws_opts(Zone, Listener, mqtt_piggyback),
BytesIn = proplists:get_value(rate_limit, Opts), FrameOpts = #{
RateLimit = emqx_zone:ratelimit(Zone), strict_mode => emqx_config:get_listener_conf(Zone, Listener, [mqtt, strict_mode]),
Limiter = emqx_limiter:init(Zone, PubLimit, BytesIn, RateLimit), max_size => emqx_config:get_listener_conf(Zone, Listener, [mqtt, max_packet_size])
ActiveN = proplists:get_value(active_n, Opts, ?ACTIVE_N), },
MQTTPiggyback = proplists:get_value(mqtt_piggyback, Opts, multiple),
FrameOpts = emqx_zone:mqtt_frame_options(Zone),
ParseState = emqx_frame:initial_parse_state(FrameOpts), ParseState = emqx_frame:initial_parse_state(FrameOpts),
Serialize = emqx_frame:serialize_opts(), Serialize = emqx_frame:serialize_opts(),
Channel = emqx_channel:init(ConnInfo, Opts), Channel = emqx_channel:init(ConnInfo, Opts),
GcState = emqx_zone:init_gc_state(Zone), GcState = case emqx_config:get_listener_conf(Zone, Listener, [force_gc]) of
StatsTimer = emqx_zone:stats_timer(Zone), #{enable := false} -> undefined;
GcPolicy -> emqx_gc:init(GcPolicy)
end,
StatsTimer = case emqx_config:get_listener_conf(Zone, Listener, [stats, enable]) of
true -> undefined;
false -> disabled
end,
%% MQTT Idle Timeout %% MQTT Idle Timeout
IdleTimeout = emqx_zone:idle_timeout(Zone), IdleTimeout = emqx_channel:get_mqtt_conf(Zone, Listener, idle_timeout),
IdleTimer = start_timer(IdleTimeout, idle_timeout), IdleTimer = start_timer(IdleTimeout, idle_timeout),
emqx_misc:tune_heap_size(emqx_zone:oom_policy(Zone)), case emqx_config:get_listener_conf(emqx_channel:info(zone, Channel),
emqx_channel:info(listener, Channel), [force_shutdown]) of
#{enable := false} -> ok;
ShutdownPolicy -> emqx_misc:tune_heap_size(ShutdownPolicy)
end,
emqx_logger:set_metadata_peername(esockd:format(Peername)), emqx_logger:set_metadata_peername(esockd:format(Peername)),
{ok, #state{peername = Peername, {ok, #state{peername = Peername,
sockname = Sockname, sockname = Sockname,
sockstate = running, sockstate = running,
active_n = ActiveN,
mqtt_piggyback = MQTTPiggyback, mqtt_piggyback = MQTTPiggyback,
limiter = Limiter, limiter = Limiter,
parse_state = ParseState, parse_state = ParseState,
@ -319,7 +316,9 @@ websocket_init([Req, Opts]) ->
postponed = [], postponed = [],
stats_timer = StatsTimer, stats_timer = StatsTimer,
idle_timeout = IdleTimeout, idle_timeout = IdleTimeout,
idle_timer = IdleTimer idle_timer = IdleTimer,
zone = Zone,
listener = Listener
}, hibernate}. }, hibernate}.
websocket_handle({binary, Data}, State) when is_list(Data) -> websocket_handle({binary, Data}, State) when is_list(Data) ->
@ -372,7 +371,8 @@ websocket_info({check_gc, Stats}, State) ->
return(check_oom(run_gc(Stats, State))); return(check_oom(run_gc(Stats, State)));
websocket_info(Deliver = {deliver, _Topic, _Msg}, websocket_info(Deliver = {deliver, _Topic, _Msg},
State = #state{active_n = ActiveN}) -> State = #state{zone = Zone, listener = Listener}) ->
ActiveN = emqx_config:get_listener_conf(Zone, Listener, [tcp, active_n]),
Delivers = [Deliver|emqx_misc:drain_deliver(ActiveN)], Delivers = [Deliver|emqx_misc:drain_deliver(ActiveN)],
with_channel(handle_deliver, [Delivers], State); with_channel(handle_deliver, [Delivers], State);
@ -521,11 +521,16 @@ run_gc(Stats, State = #state{gc_state = GcSt}) ->
end. end.
check_oom(State = #state{channel = Channel}) -> check_oom(State = #state{channel = Channel}) ->
OomPolicy = emqx_zone:oom_policy(emqx_channel:info(zone, Channel)), ShutdownPolicy = emqx_config:get_listener_conf(emqx_channel:info(zone, Channel),
case ?ENABLED(OomPolicy) andalso emqx_misc:check_oom(OomPolicy) of emqx_channel:info(listener, Channel), [force_shutdown]),
case ShutdownPolicy of
#{enable := false} -> State;
#{enable := true} ->
case emqx_misc:check_oom(ShutdownPolicy) of
Shutdown = {shutdown, _Reason} -> Shutdown = {shutdown, _Reason} ->
postpone(Shutdown, State); postpone(Shutdown, State);
_Other -> State _Other -> State
end
end. end.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -554,11 +559,12 @@ parse_incoming(Data, State = #state{parse_state = ParseState}) ->
%% Handle incoming packet %% Handle incoming packet
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_incoming(Packet, State = #state{active_n = ActiveN}) handle_incoming(Packet, State = #state{zone = Zone, listener = Listener})
when is_record(Packet, mqtt_packet) -> when is_record(Packet, mqtt_packet) ->
?LOG(debug, "RECV ~s", [emqx_packet:format(Packet)]), ?LOG(debug, "RECV ~s", [emqx_packet:format(Packet)]),
ok = inc_incoming_stats(Packet), ok = inc_incoming_stats(Packet),
NState = case emqx_pd:get_counter(incoming_pubs) > ActiveN of NState = case emqx_pd:get_counter(incoming_pubs) >
emqx_config:get_listener_conf(Zone, Listener, [tcp, active_n]) of
true -> postpone({cast, rate_limit}, State); true -> postpone({cast, rate_limit}, State);
false -> State false -> State
end, end,
@ -589,11 +595,13 @@ with_channel(Fun, Args, State = #state{channel = Channel}) ->
%% Handle outgoing packets %% Handle outgoing packets
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_outgoing(Packets, State = #state{active_n = ActiveN, mqtt_piggyback = MQTTPiggyback}) -> handle_outgoing(Packets, State = #state{mqtt_piggyback = MQTTPiggyback,
zone = Zone, listener = Listener}) ->
IoData = lists:map(serialize_and_inc_stats_fun(State), Packets), IoData = lists:map(serialize_and_inc_stats_fun(State), Packets),
Oct = iolist_size(IoData), Oct = iolist_size(IoData),
ok = inc_sent_stats(length(Packets), Oct), ok = inc_sent_stats(length(Packets), Oct),
NState = case emqx_pd:get_counter(outgoing_pubs) > ActiveN of NState = case emqx_pd:get_counter(outgoing_pubs) >
emqx_config:get_listener_conf(Zone, Listener, [tcp, active_n]) of
true -> true ->
Stats = #{cnt => emqx_pd:reset_counter(outgoing_pubs), Stats = #{cnt => emqx_pd:reset_counter(outgoing_pubs),
oct => emqx_pd:reset_counter(outgoing_bytes) oct => emqx_pd:reset_counter(outgoing_bytes)
@ -742,9 +750,10 @@ classify([Event|More], Packets, Cmds, Events) ->
trigger(Event) -> erlang:send(self(), Event). trigger(Event) -> erlang:send(self(), Event).
get_peer(Req, Opts) -> get_peer(Req, #{zone := Zone, listener := Listener}) ->
{PeerAddr, PeerPort} = cowboy_req:peer(Req), {PeerAddr, PeerPort} = cowboy_req:peer(Req),
AddrHeader = cowboy_req:header(proplists:get_value(proxy_address_header, Opts), Req, <<>>), AddrHeader = cowboy_req:header(
get_ws_opts(Zone, Listener, proxy_address_header), Req, <<>>),
ClientAddr = case string:tokens(binary_to_list(AddrHeader), ", ") of ClientAddr = case string:tokens(binary_to_list(AddrHeader), ", ") of
[] -> [] ->
undefined; undefined;
@ -757,7 +766,8 @@ get_peer(Req, Opts) ->
_ -> _ ->
PeerAddr PeerAddr
end, end,
PortHeader = cowboy_req:header(proplists:get_value(proxy_port_header, Opts), Req, <<>>), PortHeader = cowboy_req:header(
get_ws_opts(Zone, Listener, proxy_port_header), Req, <<>>),
ClientPort = case string:tokens(binary_to_list(PortHeader), ", ") of ClientPort = case string:tokens(binary_to_list(PortHeader), ", ") of
[] -> [] ->
undefined; undefined;
@ -778,3 +788,5 @@ set_field(Name, Value, State) ->
Pos = emqx_misc:index_of(Name, record_info(fields, state)), Pos = emqx_misc:index_of(Name, record_info(fields, state)),
setelement(Pos+1, State, Value). setelement(Pos+1, State, Value).
get_ws_opts(Zone, Listener, Key) ->
emqx_config:get_listener_conf(Zone, Listener, [websocket, Key]).

View File

@ -27,6 +27,7 @@ all() -> emqx_ct:all(?MODULE).
init_per_suite(Config) -> init_per_suite(Config) ->
emqx_ct_helpers:start_apps([]), emqx_ct_helpers:start_apps([]),
ct:pal("------------config: ~p", [emqx_config:get()]),
Config. Config.
end_per_suite(_Config) -> end_per_suite(_Config) ->

View File

@ -39,25 +39,14 @@ t_authorize(_) ->
Publish = ?PUBLISH_PACKET(?QOS_0, <<"t">>, 1, <<"payload">>), Publish = ?PUBLISH_PACKET(?QOS_0, <<"t">>, 1, <<"payload">>),
?assertEqual(allow, emqx_access_control:authorize(clientinfo(), Publish, <<"t">>)). ?assertEqual(allow, emqx_access_control:authorize(clientinfo(), Publish, <<"t">>)).
t_bypass_auth_plugins(_) ->
ClientInfo = clientinfo(),
emqx_zone:set_env(bypass_zone, bypass_auth_plugins, true),
emqx:hook('client.authenticate',{?MODULE, auth_fun, []}),
?assertMatch(ok, emqx_access_control:authenticate(ClientInfo#{zone => bypass_zone})),
?assertMatch({error, bad_username_or_password}, emqx_access_control:authenticate(ClientInfo)).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Helper functions %% Helper functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
auth_fun(#{zone := bypass_zone}, _) ->
{stop, ok};
auth_fun(#{zone := _}, _) ->
{stop, {error, bad_username_or_password}}.
clientinfo() -> clientinfo(#{}). clientinfo() -> clientinfo(#{}).
clientinfo(InitProps) -> clientinfo(InitProps) ->
maps:merge(#{zone => zone, maps:merge(#{zone => default,
listener => mqtt_tcp,
protocol => mqtt, protocol => mqtt,
peerhost => {127,0,0,1}, peerhost => {127,0,0,1},
clientid => <<"clientid">>, clientid => <<"clientid">>,
@ -67,3 +56,6 @@ clientinfo(InitProps) ->
peercert => undefined, peercert => undefined,
mountpoint => undefined mountpoint => undefined
}, InitProps). }, InitProps).
toggle_auth(Bool) when is_boolean(Bool) ->
emqx_config:put_listener_conf(default, mqtt_tcp, [auth, enable], Bool).

View File

@ -26,6 +26,7 @@ all() -> emqx_ct:all(?MODULE).
init_per_suite(Config) -> init_per_suite(Config) ->
emqx_ct_helpers:boot_modules(all), emqx_ct_helpers:boot_modules(all),
emqx_ct_helpers:start_apps([]), emqx_ct_helpers:start_apps([]),
toggle_acl(true),
Config. Config.
end_per_suite(_Config) -> end_per_suite(_Config) ->
@ -55,7 +56,6 @@ t_clean_acl_cache(_) ->
?assertEqual(0, length(gen_server:call(ClientPid, list_acl_cache))), ?assertEqual(0, length(gen_server:call(ClientPid, list_acl_cache))),
emqtt:stop(Client). emqtt:stop(Client).
t_drain_acl_cache(_) -> t_drain_acl_cache(_) ->
{ok, Client} = emqtt:start_link([{clientid, <<"emqx_c">>}]), {ok, Client} = emqtt:start_link([{clientid, <<"emqx_c">>}]),
{ok, _} = emqtt:connect(Client), {ok, _} = emqtt:connect(Client),
@ -79,70 +79,5 @@ t_drain_acl_cache(_) ->
?assert(length(gen_server:call(ClientPid, list_acl_cache)) > 0), ?assert(length(gen_server:call(ClientPid, list_acl_cache)) > 0),
emqtt:stop(Client). emqtt:stop(Client).
% optimize?? toggle_acl(Bool) when is_boolean(Bool) ->
t_reload_aclfile_and_cleanall(_Config) -> emqx_config:put_listener_conf(default, mqtt_tcp, [acl, enable], Bool).
RasieMsg = fun() -> Self = self(), #{puback => fun(Msg) -> Self ! {puback, Msg} end,
disconnected => fun(_) -> ok end,
publish => fun(_) -> ok end } end,
{ok, Client} = emqtt:start_link([{clientid, <<"emqx_c">>}, {proto_ver, v5},
{msg_handler, RasieMsg()}]),
{ok, _} = emqtt:connect(Client),
{ok, PktId} = emqtt:publish(Client, <<"t1">>, <<"{\"x\":1}">>, qos1),
%% Success publish to broker
receive
{puback, #{packet_id := PktId, reason_code := Rc}} ->
?assertEqual(16#10, Rc);
_ ->
?assert(false)
end,
%% Check acl cache list
[ClientPid] = emqx_cm:lookup_channels(<<"emqx_c">>),
?assert(length(gen_server:call(ClientPid, list_acl_cache)) > 0),
emqtt:stop(Client).
%% @private
testdir(DataPath) ->
Ls = filename:split(DataPath),
filename:join(lists:sublist(Ls, 1, length(Ls) - 1)).
% t_cache_k(_) ->
% error('TODO').
% t_cache_v(_) ->
% error('TODO').
% t_cleanup_acl_cache(_) ->
% error('TODO').
% t_get_oldest_key(_) ->
% error('TODO').
% t_get_newest_key(_) ->
% error('TODO').
% t_get_cache_max_size(_) ->
% error('TODO').
% t_get_cache_size(_) ->
% error('TODO').
% t_dump_acl_cache(_) ->
% error('TODO').
% t_empty_acl_cache(_) ->
% error('TODO').
% t_put_acl_cache(_) ->
% error('TODO').
% t_get_acl_cache(_) ->
% error('TODO').
% t_is_enabled(_) ->
% error('TODO').

View File

@ -27,27 +27,17 @@ all() -> emqx_ct:all(?MODULE).
init_per_testcase(t_size_limit, Config) -> init_per_testcase(t_size_limit, Config) ->
emqx_ct_helpers:boot_modules(all), emqx_ct_helpers:boot_modules(all),
emqx_ct_helpers:start_apps([], emqx_ct_helpers:start_apps([]),
fun(emqx) -> emqx_config:update_config([alarm], #{
application:set_env(emqx, alarm, [{actions, [log,publish]}, <<"size_limit">> => 2
{size_limit, 2}, }),
{validity_period, 3600}]),
ok;
(_) ->
ok
end),
Config; Config;
init_per_testcase(t_validity_period, Config) -> init_per_testcase(t_validity_period, Config) ->
emqx_ct_helpers:boot_modules(all), emqx_ct_helpers:boot_modules(all),
emqx_ct_helpers:start_apps([], emqx_ct_helpers:start_apps([]),
fun(emqx) -> emqx_config:update_config([alarm], #{
application:set_env(emqx, alarm, [{actions, [log,publish]}, <<"validity_period">> => <<"1s">>
{size_limit, 1000}, }),
{validity_period, 1}]),
ok;
(_) ->
ok
end),
Config; Config;
init_per_testcase(_, Config) -> init_per_testcase(_, Config) ->
emqx_ct_helpers:boot_modules(all), emqx_ct_helpers:boot_modules(all),
@ -89,7 +79,7 @@ t_size_limit(_) ->
ok = emqx_alarm:activate(b), ok = emqx_alarm:activate(b),
ok = emqx_alarm:deactivate(b), ok = emqx_alarm:deactivate(b),
?assertNotEqual({error, not_found}, get_alarm(a, emqx_alarm:get_alarms(deactivated))), ?assertNotEqual({error, not_found}, get_alarm(a, emqx_alarm:get_alarms(deactivated))),
?assertNotEqual({error, not_found}, get_alarm(a, emqx_alarm:get_alarms(deactivated))), ?assertNotEqual({error, not_found}, get_alarm(b, emqx_alarm:get_alarms(deactivated))),
ok = emqx_alarm:activate(c), ok = emqx_alarm:activate(c),
ok = emqx_alarm:deactivate(c), ok = emqx_alarm:deactivate(c),
?assertNotEqual({error, not_found}, get_alarm(c, emqx_alarm:get_alarms(deactivated))), ?assertNotEqual({error, not_found}, get_alarm(c, emqx_alarm:get_alarms(deactivated))),

View File

@ -42,19 +42,19 @@ end_per_suite(_Config) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
t_stats_fun(_) -> t_stats_fun(_) ->
?assertEqual(0, emqx_stats:getstat('subscribers.count')), Subscribers = emqx_stats:getstat('subscribers.count'),
?assertEqual(0, emqx_stats:getstat('subscriptions.count')), Subscriptions = emqx_stats:getstat('subscriptions.count'),
?assertEqual(0, emqx_stats:getstat('suboptions.count')), Subopts = emqx_stats:getstat('suboptions.count'),
ok = emqx_broker:subscribe(<<"topic">>, <<"clientid">>), ok = emqx_broker:subscribe(<<"topic">>, <<"clientid">>),
ok = emqx_broker:subscribe(<<"topic2">>, <<"clientid">>), ok = emqx_broker:subscribe(<<"topic2">>, <<"clientid">>),
emqx_broker:stats_fun(), emqx_broker:stats_fun(),
ct:sleep(10), ct:sleep(10),
?assertEqual(2, emqx_stats:getstat('subscribers.count')), ?assertEqual(Subscribers + 2, emqx_stats:getstat('subscribers.count')),
?assertEqual(2, emqx_stats:getstat('subscribers.max')), ?assertEqual(Subscribers + 2, emqx_stats:getstat('subscribers.max')),
?assertEqual(2, emqx_stats:getstat('subscriptions.count')), ?assertEqual(Subscriptions + 2, emqx_stats:getstat('subscriptions.count')),
?assertEqual(2, emqx_stats:getstat('subscriptions.max')), ?assertEqual(Subscriptions + 2, emqx_stats:getstat('subscriptions.max')),
?assertEqual(2, emqx_stats:getstat('suboptions.count')), ?assertEqual(Subopts + 2, emqx_stats:getstat('suboptions.count')),
?assertEqual(2, emqx_stats:getstat('suboptions.max')). ?assertEqual(Subopts + 2, emqx_stats:getstat('suboptions.max')).
t_subscribed(_) -> t_subscribed(_) ->
emqx_broker:subscribe(<<"topic">>), emqx_broker:subscribe(<<"topic">>),

View File

@ -24,7 +24,152 @@
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
all() -> emqx_ct:all(?MODULE). all() ->
emqx_ct:all(?MODULE).
mqtt_conf() ->
#{await_rel_timeout => 300,
idle_timeout => 15000,
ignore_loop_deliver => false,
keepalive_backoff => 0.75,
max_awaiting_rel => 100,
max_clientid_len => 65535,
max_inflight => 32,
max_mqueue_len => 1000,
max_packet_size => 1048576,
max_qos_allowed => 2,
max_subscriptions => infinity,
max_topic_alias => 65535,
max_topic_levels => 65535,
mountpoint => <<>>,
mqueue_default_priority => lowest,
mqueue_priorities => #{},
mqueue_store_qos0 => true,
peer_cert_as_clientid => disabled,
peer_cert_as_username => disabled,
response_information => [],
retain_available => true,
retry_interval => 30,
server_keepalive => disabled,
session_expiry_interval => 7200,
shared_subscription => true,
strict_mode => false,
upgrade_qos => false,
use_username_as_clientid => false,
wildcard_subscription => true}.
listener_mqtt_tcp_conf() ->
#{acceptors => 16,
access_rules => ["allow all"],
bind => {{0,0,0,0},1883},
max_connections => 1024000,
proxy_protocol => false,
proxy_protocol_timeout => 3000,
rate_limit =>
#{conn_bytes_in =>
["100KB","10s"],
conn_messages_in =>
["100","10s"],
max_conn_rate => 1000,
quota =>
#{conn_messages_routing => infinity,
overall_messages_routing => infinity}},
tcp =>
#{active_n => 100,
backlog => 1024,
buffer => 4096,
high_watermark => 1048576,
send_timeout => 15000,
send_timeout_close =>
true},
type => tcp}.
listener_mqtt_ws_conf() ->
#{acceptors => 16,
access_rules => ["allow all"],
bind => {{0,0,0,0},8083},
max_connections => 1024000,
proxy_protocol => false,
proxy_protocol_timeout => 3000,
rate_limit =>
#{conn_bytes_in =>
["100KB","10s"],
conn_messages_in =>
["100","10s"],
max_conn_rate => 1000,
quota =>
#{conn_messages_routing => infinity,
overall_messages_routing => infinity}},
tcp =>
#{active_n => 100,
backlog => 1024,
buffer => 4096,
high_watermark => 1048576,
send_timeout => 15000,
send_timeout_close =>
true},
type => ws,
websocket =>
#{allow_origin_absence =>
true,
check_origin_enable =>
false,
check_origins => [],
compress => false,
deflate_opts =>
#{client_max_window_bits =>
15,
mem_level => 8,
server_max_window_bits =>
15},
fail_if_no_subprotocol =>
true,
idle_timeout => 86400000,
max_frame_size => infinity,
mqtt_path => "/mqtt",
mqtt_piggyback => multiple,
proxy_address_header =>
"x-forwarded-for",
proxy_port_header =>
"x-forwarded-port",
supported_subprotocols =>
["mqtt","mqtt-v3",
"mqtt-v3.1.1",
"mqtt-v5"]}}.
default_zone_conf() ->
#{zones =>
#{default =>
#{ acl => #{
cache => #{enable => true,max_size => 32, ttl => 60000},
deny_action => ignore,
enable => false
},
auth => #{enable => false},
overall_max_connections => infinity,
stats => #{enable => true},
conn_congestion =>
#{enable_alarm => true, min_alarm_sustain_duration => 60000},
flapping_detect =>
#{ban_time => 300000,enable => false,
max_count => 15,window_time => 60000},
force_gc =>
#{bytes => 16777216,count => 16000,
enable => true},
force_shutdown =>
#{enable => true,
max_heap_size => 4194304,
max_message_queue_len => 1000},
mqtt => mqtt_conf(),
listeners =>
#{mqtt_tcp => listener_mqtt_tcp_conf(),
mqtt_ws => listener_mqtt_ws_conf()}
}
}
}.
set_default_zone_conf() ->
emqx_config:put(default_zone_conf()).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% CT Callbacks %% CT Callbacks
@ -50,6 +195,9 @@ init_per_suite(Config) ->
ok = meck:new(emqx_metrics, [passthrough, no_history, no_link]), ok = meck:new(emqx_metrics, [passthrough, no_history, no_link]),
ok = meck:expect(emqx_metrics, inc, fun(_) -> ok end), ok = meck:expect(emqx_metrics, inc, fun(_) -> ok end),
ok = meck:expect(emqx_metrics, inc, fun(_, _) -> ok end), ok = meck:expect(emqx_metrics, inc, fun(_, _) -> ok end),
%% Ban
meck:new(emqx_banned, [passthrough, no_history, no_link]),
ok = meck:expect(emqx_banned, check, fun(_ConnInfo) -> false end),
Config. Config.
end_per_suite(_Config) -> end_per_suite(_Config) ->
@ -58,15 +206,15 @@ end_per_suite(_Config) ->
emqx_session, emqx_session,
emqx_broker, emqx_broker,
emqx_hooks, emqx_hooks,
emqx_cm emqx_cm,
emqx_banned
]). ]).
init_per_testcase(_TestCase, Config) -> init_per_testcase(_TestCase, Config) ->
meck:new(emqx_zone, [passthrough, no_history, no_link]), set_default_zone_conf(),
Config. Config.
end_per_testcase(_TestCase, Config) -> end_per_testcase(_TestCase, Config) ->
meck:unload([emqx_zone]),
Config. Config.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -83,7 +231,7 @@ t_chan_caps(_) ->
#{max_clientid_len := 65535, #{max_clientid_len := 65535,
max_qos_allowed := 2, max_qos_allowed := 2,
max_topic_alias := 65535, max_topic_alias := 65535,
max_topic_levels := 0, max_topic_levels := 65535,
retain_available := true, retain_available := true,
shared_subscription := true, shared_subscription := true,
subscription_identifiers := true, subscription_identifiers := true,
@ -250,7 +398,7 @@ t_bad_receive_maximum(_) ->
fun(true, _ClientInfo, _ConnInfo) -> fun(true, _ClientInfo, _ConnInfo) ->
{ok, #{session => session(), present => false}} {ok, #{session => session(), present => false}}
end), end),
ok = meck:expect(emqx_zone, response_information, fun(_) -> test end), emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, response_information], test),
C1 = channel(#{conn_state => idle}), C1 = channel(#{conn_state => idle}),
{shutdown, protocol_error, _, _} = {shutdown, protocol_error, _, _} =
emqx_channel:handle_in( emqx_channel:handle_in(
@ -263,8 +411,8 @@ t_override_client_receive_maximum(_) ->
fun(true, _ClientInfo, _ConnInfo) -> fun(true, _ClientInfo, _ConnInfo) ->
{ok, #{session => session(), present => false}} {ok, #{session => session(), present => false}}
end), end),
ok = meck:expect(emqx_zone, response_information, fun(_) -> test end), emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, response_information], test),
ok = meck:expect(emqx_zone, max_inflight, fun(_) -> 0 end), emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, max_inflight], 0),
C1 = channel(#{conn_state => idle}), C1 = channel(#{conn_state => idle}),
ClientCapacity = 2, ClientCapacity = 2,
{ok, [{event, connected}, _ConnAck], C2} = {ok, [{event, connected}, _ConnAck], C2} =
@ -486,7 +634,7 @@ t_handle_deliver_nl(_) ->
Channel = channel(#{clientinfo => ClientInfo, session => Session}), Channel = channel(#{clientinfo => ClientInfo, session => Session}),
Msg = emqx_message:make(<<"clientid">>, ?QOS_1, <<"t1">>, <<"qos1">>), Msg = emqx_message:make(<<"clientid">>, ?QOS_1, <<"t1">>, <<"qos1">>),
NMsg = emqx_message:set_flag(nl, Msg), NMsg = emqx_message:set_flag(nl, Msg),
{ok, Channel} = emqx_channel:handle_deliver([{deliver, <<"t1">>, NMsg}], Channel). {ok, _} = emqx_channel:handle_deliver([{deliver, <<"t1">>, NMsg}], Channel).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Test cases for handle_out %% Test cases for handle_out
@ -515,7 +663,7 @@ t_handle_out_connack_response_information(_) ->
fun(true, _ClientInfo, _ConnInfo) -> fun(true, _ClientInfo, _ConnInfo) ->
{ok, #{session => session(), present => false}} {ok, #{session => session(), present => false}}
end), end),
ok = meck:expect(emqx_zone, response_information, fun(_) -> test end), emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, response_information], test),
IdleChannel = channel(#{conn_state => idle}), IdleChannel = channel(#{conn_state => idle}),
{ok, [{event, connected}, {ok, [{event, connected},
{connack, ?CONNACK_PACKET(?RC_SUCCESS, 0, #{'Response-Information' := test})}], {connack, ?CONNACK_PACKET(?RC_SUCCESS, 0, #{'Response-Information' := test})}],
@ -529,7 +677,7 @@ t_handle_out_connack_not_response_information(_) ->
fun(true, _ClientInfo, _ConnInfo) -> fun(true, _ClientInfo, _ConnInfo) ->
{ok, #{session => session(), present => false}} {ok, #{session => session(), present => false}}
end), end),
ok = meck:expect(emqx_zone, response_information, fun(_) -> test end), emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, response_information], test),
IdleChannel = channel(#{conn_state => idle}), IdleChannel = channel(#{conn_state => idle}),
{ok, [{event, connected}, {connack, ?CONNACK_PACKET(?RC_SUCCESS, 0, AckProps)}], _} = {ok, [{event, connected}, {connack, ?CONNACK_PACKET(?RC_SUCCESS, 0, AckProps)}], _} =
emqx_channel:handle_in( emqx_channel:handle_in(
@ -669,9 +817,6 @@ t_enrich_conninfo(_) ->
t_enrich_client(_) -> t_enrich_client(_) ->
{ok, _ConnPkt, _Chan} = emqx_channel:enrich_client(connpkt(), channel()). {ok, _ConnPkt, _Chan} = emqx_channel:enrich_client(connpkt(), channel()).
t_check_banned(_) ->
ok = emqx_channel:check_banned(connpkt(), channel()).
t_auth_connect(_) -> t_auth_connect(_) ->
{ok, _, _Chan} = emqx_channel:authenticate(?CONNECT_PACKET(connpkt()), channel()). {ok, _, _Chan} = emqx_channel:authenticate(?CONNECT_PACKET(connpkt()), channel()).
@ -718,7 +863,7 @@ t_packing_alias(_) ->
channel())). channel())).
t_check_pub_acl(_) -> t_check_pub_acl(_) ->
ok = meck:expect(emqx_zone, enable_acl, fun(_) -> true end), emqx_config:put_listener_conf(default, mqtt_tcp, [acl, enable], true),
Publish = ?PUBLISH_PACKET(?QOS_0, <<"t">>, 1, <<"payload">>), Publish = ?PUBLISH_PACKET(?QOS_0, <<"t">>, 1, <<"payload">>),
ok = emqx_channel:check_pub_acl(Publish, channel()). ok = emqx_channel:check_pub_acl(Publish, channel()).
@ -728,14 +873,14 @@ t_check_pub_alias(_) ->
ok = emqx_channel:check_pub_alias(#mqtt_packet{variable = Publish}, Channel). ok = emqx_channel:check_pub_alias(#mqtt_packet{variable = Publish}, Channel).
t_check_sub_acls(_) -> t_check_sub_acls(_) ->
ok = meck:expect(emqx_zone, enable_acl, fun(_) -> true end), emqx_config:put_listener_conf(default, mqtt_tcp, [acl, enable], true),
TopicFilter = {<<"t">>, ?DEFAULT_SUBOPTS}, TopicFilter = {<<"t">>, ?DEFAULT_SUBOPTS},
[{TopicFilter, 0}] = emqx_channel:check_sub_acls([TopicFilter], channel()). [{TopicFilter, 0}] = emqx_channel:check_sub_acls([TopicFilter], channel()).
t_enrich_connack_caps(_) -> t_enrich_connack_caps(_) ->
ok = meck:new(emqx_mqtt_caps, [passthrough, no_history]), ok = meck:new(emqx_mqtt_caps, [passthrough, no_history]),
ok = meck:expect(emqx_mqtt_caps, get_caps, ok = meck:expect(emqx_mqtt_caps, get_caps,
fun(_Zone) -> fun(_Zone, _Listener) ->
#{max_packet_size => 1024, #{max_packet_size => 1024,
max_qos_allowed => ?QOS_2, max_qos_allowed => ?QOS_2,
retain_available => true, retain_available => true,
@ -772,7 +917,7 @@ t_ws_cookie_init(_) ->
conn_mod => emqx_ws_connection, conn_mod => emqx_ws_connection,
ws_cookie => WsCookie ws_cookie => WsCookie
}, },
Channel = emqx_channel:init(ConnInfo, [{zone, zone}]), Channel = emqx_channel:init(ConnInfo, #{zone => default, listener => mqtt_tcp}),
?assertMatch(#{ws_cookie := WsCookie}, emqx_channel:info(clientinfo, Channel)). ?assertMatch(#{ws_cookie := WsCookie}, emqx_channel:info(clientinfo, Channel)).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -797,7 +942,7 @@ channel(InitFields) ->
maps:fold(fun(Field, Value, Channel) -> maps:fold(fun(Field, Value, Channel) ->
emqx_channel:set_field(Field, Value, Channel) emqx_channel:set_field(Field, Value, Channel)
end, end,
emqx_channel:init(ConnInfo, [{zone, zone}]), emqx_channel:init(ConnInfo, #{zone => default, listener => mqtt_tcp}),
maps:merge(#{clientinfo => clientinfo(), maps:merge(#{clientinfo => clientinfo(),
session => session(), session => session(),
conn_state => connected conn_state => connected
@ -805,7 +950,8 @@ channel(InitFields) ->
clientinfo() -> clientinfo(#{}). clientinfo() -> clientinfo(#{}).
clientinfo(InitProps) -> clientinfo(InitProps) ->
maps:merge(#{zone => zone, maps:merge(#{zone => default,
listener => mqtt_tcp,
protocol => mqtt, protocol => mqtt,
peerhost => {127,0,0,1}, peerhost => {127,0,0,1},
clientid => <<"clientid">>, clientid => <<"clientid">>,
@ -837,7 +983,8 @@ session(InitFields) when is_map(InitFields) ->
maps:fold(fun(Field, Value, Session) -> maps:fold(fun(Field, Value, Session) ->
emqx_session:set_field(Field, Value, Session) emqx_session:set_field(Field, Value, Session)
end, end,
emqx_session:init(#{zone => channel}, #{receive_maximum => 0}), emqx_session:init(#{zone => default, listener => mqtt_tcp},
#{receive_maximum => 0}),
InitFields). InitFields).
%% conn: 5/s; overall: 10/s %% conn: 5/s; overall: 10/s

View File

@ -78,17 +78,14 @@ groups() ->
init_per_suite(Config) -> init_per_suite(Config) ->
emqx_ct_helpers:boot_modules(all), emqx_ct_helpers:boot_modules(all),
emqx_ct_helpers:start_apps([], fun set_special_confs/1), emqx_ct_helpers:start_apps([]),
emqx_config:put_listener_conf(default, mqtt_ssl, [ssl, verify], verify_peer),
emqx_listeners:restart_listener('default:mqtt_ssl'),
Config. Config.
end_per_suite(_Config) -> end_per_suite(_Config) ->
emqx_ct_helpers:stop_apps([]). emqx_ct_helpers:stop_apps([]).
set_special_confs(emqx) ->
emqx_ct_helpers:change_emqx_opts(ssl_twoway, [{peer_cert_as_username, cn}]);
set_special_confs(_) ->
ok.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Test cases for MQTT v3 %% Test cases for MQTT v3
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -104,8 +101,7 @@ t_basic_v4(_Config) ->
t_basic([{proto_ver, v4}]). t_basic([{proto_ver, v4}]).
t_cm(_) -> t_cm(_) ->
IdleTimeout = emqx_zone:get_env(external, idle_timeout, 30000), emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, idle_timeout], 1000),
emqx_zone:set_env(external, idle_timeout, 1000),
ClientId = <<"myclient">>, ClientId = <<"myclient">>,
{ok, C} = emqtt:start_link([{clientid, ClientId}]), {ok, C} = emqtt:start_link([{clientid, ClientId}]),
{ok, _} = emqtt:connect(C), {ok, _} = emqtt:connect(C),
@ -115,7 +111,7 @@ t_cm(_) ->
ct:sleep(1200), ct:sleep(1200),
Stats = emqx_cm:get_chan_stats(ClientId), Stats = emqx_cm:get_chan_stats(ClientId),
?assertEqual(1, proplists:get_value(subscriptions_cnt, Stats)), ?assertEqual(1, proplists:get_value(subscriptions_cnt, Stats)),
emqx_zone:set_env(external, idle_timeout, IdleTimeout). emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, idle_timeout], 15000).
t_cm_registry(_) -> t_cm_registry(_) ->
Info = supervisor:which_children(emqx_cm_sup), Info = supervisor:which_children(emqx_cm_sup),
@ -273,15 +269,13 @@ t_basic(_Opts) ->
ok = emqtt:disconnect(C). ok = emqtt:disconnect(C).
t_username_as_clientid(_) -> t_username_as_clientid(_) ->
emqx_zone:set_env(external, use_username_as_clientid, true), emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, use_username_as_clientid], true),
Username = <<"usera">>, Username = <<"usera">>,
{ok, C} = emqtt:start_link([{username, Username}]), {ok, C} = emqtt:start_link([{username, Username}]),
{ok, _} = emqtt:connect(C), {ok, _} = emqtt:connect(C),
#{clientinfo := #{clientid := Username}} = emqx_cm:get_chan_info(Username), #{clientinfo := #{clientid := Username}} = emqx_cm:get_chan_info(Username),
emqtt:disconnect(C). emqtt:disconnect(C).
t_certcn_as_clientid_default_config_tls(_) -> t_certcn_as_clientid_default_config_tls(_) ->
tls_certcn_as_clientid(default). tls_certcn_as_clientid(default).
@ -329,7 +323,7 @@ tls_certcn_as_clientid(TLSVsn) ->
tls_certcn_as_clientid(TLSVsn, RequiredTLSVsn) -> tls_certcn_as_clientid(TLSVsn, RequiredTLSVsn) ->
CN = <<"Client">>, CN = <<"Client">>,
emqx_zone:set_env(external, use_username_as_clientid, true), emqx_config:put_listener_conf(default, mqtt_ssl, [mqtt, peer_cert_as_clientid], cn),
SslConf = emqx_ct_helpers:client_ssl_twoway(TLSVsn), SslConf = emqx_ct_helpers:client_ssl_twoway(TLSVsn),
{ok, Client} = emqtt:start_link([{port, 8883}, {ssl, true}, {ssl_opts, SslConf}]), {ok, Client} = emqtt:start_link([{port, 8883}, {ssl, true}, {ssl_opts, SslConf}]),
{ok, _} = emqtt:connect(Client), {ok, _} = emqtt:connect(Client),

View File

@ -89,7 +89,7 @@ t_open_session(_) ->
ok = meck:expect(emqx_connection, call, fun(_, _) -> ok end), ok = meck:expect(emqx_connection, call, fun(_, _) -> ok end),
ok = meck:expect(emqx_connection, call, fun(_, _, _) -> ok end), ok = meck:expect(emqx_connection, call, fun(_, _, _) -> ok end),
ClientInfo = #{zone => external, ClientInfo = #{zone => default, listener => mqtt_tcp,
clientid => <<"clientid">>, clientid => <<"clientid">>,
username => <<"username">>, username => <<"username">>,
peerhost => {127,0,0,1}}, peerhost => {127,0,0,1}},
@ -114,7 +114,7 @@ rand_client_id() ->
t_open_session_race_condition(_) -> t_open_session_race_condition(_) ->
ClientId = rand_client_id(), ClientId = rand_client_id(),
ClientInfo = #{zone => external, ClientInfo = #{zone => default, listener => mqtt_tcp,
clientid => ClientId, clientid => ClientId,
username => <<"username">>, username => <<"username">>,
peerhost => {127,0,0,1}}, peerhost => {127,0,0,1}},

View File

@ -57,6 +57,7 @@ init_per_suite(Config) ->
ok = meck:expect(emqx_alarm, deactivate, fun(_) -> ok end), ok = meck:expect(emqx_alarm, deactivate, fun(_) -> ok end),
ok = meck:expect(emqx_alarm, deactivate, fun(_, _) -> ok end), ok = meck:expect(emqx_alarm, deactivate, fun(_, _) -> ok end),
emqx_channel_SUITE:set_default_zone_conf(),
Config. Config.
end_per_suite(_Config) -> end_per_suite(_Config) ->
@ -120,14 +121,13 @@ t_info(_) ->
end end
end), end),
#{sockinfo := SockInfo} = emqx_connection:info(CPid), #{sockinfo := SockInfo} = emqx_connection:info(CPid),
?assertMatch(#{active_n := 100, ?assertMatch(#{ peername := {{127,0,0,1},3456},
peername := {{127,0,0,1},3456},
sockname := {{127,0,0,1},1883}, sockname := {{127,0,0,1},1883},
sockstate := idle, sockstate := idle,
socktype := tcp}, SockInfo). socktype := tcp}, SockInfo).
t_info_limiter(_) -> t_info_limiter(_) ->
St = st(#{limiter => emqx_limiter:init(external, [])}), St = st(#{limiter => emqx_limiter:init(default, [])}),
?assertEqual(undefined, emqx_connection:info(limiter, St)). ?assertEqual(undefined, emqx_connection:info(limiter, St)).
t_stats(_) -> t_stats(_) ->
@ -219,8 +219,10 @@ t_handle_msg_deliver(_) ->
t_handle_msg_inet_reply(_) -> t_handle_msg_inet_reply(_) ->
ok = meck:expect(emqx_pd, get_counter, fun(_) -> 10 end), ok = meck:expect(emqx_pd, get_counter, fun(_) -> 10 end),
?assertMatch({ok, _St}, handle_msg({inet_reply, for_testing, ok}, st(#{active_n => 0}))), emqx_config:put_listener_conf(default, mqtt_tcp, [tcp, active_n], 0),
?assertEqual(ok, handle_msg({inet_reply, for_testing, ok}, st(#{active_n => 100}))), ?assertMatch({ok, _St}, handle_msg({inet_reply, for_testing, ok}, st())),
emqx_config:put_listener_conf(default, mqtt_tcp, [tcp, active_n], 100),
?assertEqual(ok, handle_msg({inet_reply, for_testing, ok}, st())),
?assertMatch({stop, {shutdown, for_testing}, _St}, ?assertMatch({stop, {shutdown, for_testing}, _St},
handle_msg({inet_reply, for_testing, {error, for_testing}}, st())). handle_msg({inet_reply, for_testing, {error, for_testing}}, st())).
@ -331,12 +333,12 @@ t_ensure_rate_limit(_) ->
?assertEqual(undefined, emqx_connection:info(limiter, State)), ?assertEqual(undefined, emqx_connection:info(limiter, State)),
ok = meck:expect(emqx_limiter, check, ok = meck:expect(emqx_limiter, check,
fun(_, _) -> {ok, emqx_limiter:init(external, [])} end), fun(_, _) -> {ok, emqx_limiter:init(default, [])} end),
State1 = emqx_connection:ensure_rate_limit(#{}, st(#{limiter => #{}})), State1 = emqx_connection:ensure_rate_limit(#{}, st(#{limiter => #{}})),
?assertEqual(undefined, emqx_connection:info(limiter, State1)), ?assertEqual(undefined, emqx_connection:info(limiter, State1)),
ok = meck:expect(emqx_limiter, check, ok = meck:expect(emqx_limiter, check,
fun(_, _) -> {pause, 3000, emqx_limiter:init(external, [])} end), fun(_, _) -> {pause, 3000, emqx_limiter:init(default, [])} end),
State2 = emqx_connection:ensure_rate_limit(#{}, st(#{limiter => #{}})), State2 = emqx_connection:ensure_rate_limit(#{}, st(#{limiter => #{}})),
?assertEqual(undefined, emqx_connection:info(limiter, State2)), ?assertEqual(undefined, emqx_connection:info(limiter, State2)),
?assertEqual(blocked, emqx_connection:info(sockstate, State2)). ?assertEqual(blocked, emqx_connection:info(sockstate, State2)).
@ -386,8 +388,7 @@ t_start_link_exit_on_activate(_) ->
t_get_conn_info(_) -> t_get_conn_info(_) ->
with_conn(fun(CPid) -> with_conn(fun(CPid) ->
#{sockinfo := SockInfo} = emqx_connection:info(CPid), #{sockinfo := SockInfo} = emqx_connection:info(CPid),
?assertEqual(#{active_n => 100, ?assertEqual(#{peername => {{127,0,0,1},3456},
peername => {{127,0,0,1},3456},
sockname => {{127,0,0,1},1883}, sockname => {{127,0,0,1},1883},
sockstate => running, sockstate => running,
socktype => tcp socktype => tcp
@ -397,16 +398,12 @@ t_get_conn_info(_) ->
t_oom_shutdown(init, Config) -> t_oom_shutdown(init, Config) ->
ok = snabbkaffe:start_trace(), ok = snabbkaffe:start_trace(),
ok = meck:new(emqx_misc, [non_strict, passthrough, no_history, no_link]), ok = meck:new(emqx_misc, [non_strict, passthrough, no_history, no_link]),
ok = meck:new(emqx_zone, [non_strict, passthrough, no_history, no_link]),
meck:expect(emqx_zone, oom_policy,
fun(_Zone) -> #{message_queue_len => 10, max_heap_size => 8000000} end),
meck:expect(emqx_misc, check_oom, meck:expect(emqx_misc, check_oom,
fun(_) -> {shutdown, "fake_oom"} end), fun(_) -> {shutdown, "fake_oom"} end),
Config; Config;
t_oom_shutdown('end', _Config) -> t_oom_shutdown('end', _Config) ->
snabbkaffe:stop(), snabbkaffe:stop(),
meck:unload(emqx_misc), meck:unload(emqx_misc),
meck:unload(emqx_zone),
ok. ok.
t_oom_shutdown(_) -> t_oom_shutdown(_) ->
@ -455,13 +452,11 @@ exit_on_activate_error(SockErr, Reason) ->
with_conn(TestFun) -> with_conn(TestFun) ->
with_conn(TestFun, #{trap_exit => false}). with_conn(TestFun, #{trap_exit => false}).
with_conn(TestFun, Options) when is_map(Options) -> with_conn(TestFun, Opts) when is_map(Opts) ->
with_conn(TestFun, maps:to_list(Options)); TrapExit = maps:get(trap_exit, Opts, false),
with_conn(TestFun, Options) ->
TrapExit = proplists:get_value(trap_exit, Options, false),
process_flag(trap_exit, TrapExit), process_flag(trap_exit, TrapExit),
{ok, CPid} = emqx_connection:start_link(emqx_transport, sock, Options), {ok, CPid} = emqx_connection:start_link(emqx_transport, sock,
maps:merge(Opts, #{zone => default, listener => mqtt_tcp})),
TestFun(CPid), TestFun(CPid),
TrapExit orelse emqx_connection:stop(CPid), TrapExit orelse emqx_connection:stop(CPid),
ok. ok.
@ -483,7 +478,8 @@ st() -> st(#{}, #{}).
st(InitFields) when is_map(InitFields) -> st(InitFields) when is_map(InitFields) ->
st(InitFields, #{}). st(InitFields, #{}).
st(InitFields, ChannelFields) when is_map(InitFields) -> st(InitFields, ChannelFields) when is_map(InitFields) ->
St = emqx_connection:init_state(emqx_transport, sock, [#{zone => external}]), St = emqx_connection:init_state(emqx_transport, sock, #{zone => default,
listener => mqtt_tcp}),
maps:fold(fun(N, V, S) -> emqx_connection:set_field(N, V, S) end, maps:fold(fun(N, V, S) -> emqx_connection:set_field(N, V, S) end,
emqx_connection:set_field(channel, channel(ChannelFields), St), emqx_connection:set_field(channel, channel(ChannelFields), St),
InitFields InitFields
@ -503,7 +499,8 @@ channel(InitFields) ->
receive_maximum => 100, receive_maximum => 100,
expiry_interval => 0 expiry_interval => 0
}, },
ClientInfo = #{zone => zone, ClientInfo = #{zone => default,
listener => mqtt_tcp,
protocol => mqtt, protocol => mqtt,
peerhost => {127,0,0,1}, peerhost => {127,0,0,1},
clientid => <<"clientid">>, clientid => <<"clientid">>,
@ -512,13 +509,13 @@ channel(InitFields) ->
peercert => undefined, peercert => undefined,
mountpoint => undefined mountpoint => undefined
}, },
Session = emqx_session:init(#{zone => external}, Session = emqx_session:init(#{zone => default, listener => mqtt_tcp},
#{receive_maximum => 0} #{receive_maximum => 0}
), ),
maps:fold(fun(Field, Value, Channel) -> maps:fold(fun(Field, Value, Channel) ->
emqx_channel:set_field(Field, Value, Channel) emqx_channel:set_field(Field, Value, Channel)
end, end,
emqx_channel:init(ConnInfo, [{zone, zone}]), emqx_channel:init(ConnInfo, #{zone => default, listener => mqtt_tcp}),
maps:merge(#{clientinfo => ClientInfo, maps:merge(#{clientinfo => ClientInfo,
session => Session, session => Session,
conn_state => connected conn_state => connected

View File

@ -25,26 +25,23 @@ all() -> emqx_ct:all(?MODULE).
init_per_suite(Config) -> init_per_suite(Config) ->
emqx_ct_helpers:boot_modules(all), emqx_ct_helpers:boot_modules(all),
emqx_ct_helpers:start_apps([], fun set_special_configs/1), emqx_ct_helpers:start_apps([]),
emqx_config:put_listener_conf(default, mqtt_tcp, [flapping_detect],
#{max_count => 3,
window_time => 100, % 0.1s
ban_time => 2000 %% 2s
}),
Config. Config.
set_special_configs(emqx) ->
emqx_zone:set_env(external, enable_flapping_detect, true),
application:set_env(emqx, flapping_detect_policy,
#{threshold => 3,
duration => 100,
banned_interval => 2
});
set_special_configs(_App) -> ok.
end_per_suite(_Config) -> end_per_suite(_Config) ->
emqx_ct_helpers:stop_apps([]), emqx_ct_helpers:stop_apps([]),
ekka_mnesia:delete_schema(), %% Clean emqx_banned table ekka_mnesia:delete_schema(), %% Clean emqx_banned table
ok. ok.
t_detect_check(_) -> t_detect_check(_) ->
ClientInfo = #{zone => external, ClientInfo = #{zone => default,
clientid => <<"clientid">>, listener => mqtt_tcp,
clientid => <<"client007">>,
peerhost => {127,0,0,1} peerhost => {127,0,0,1}
}, },
false = emqx_flapping:detect(ClientInfo), false = emqx_flapping:detect(ClientInfo),
@ -53,6 +50,8 @@ t_detect_check(_) ->
false = emqx_banned:check(ClientInfo), false = emqx_banned:check(ClientInfo),
true = emqx_flapping:detect(ClientInfo), true = emqx_flapping:detect(ClientInfo),
timer:sleep(50), timer:sleep(50),
ct:pal("the table emqx_banned: ~p, nowsec: ~p", [ets:tab2list(emqx_banned),
erlang:system_time(second)]),
true = emqx_banned:check(ClientInfo), true = emqx_banned:check(ClientInfo),
timer:sleep(3000), timer:sleep(3000),
false = emqx_banned:check(ClientInfo), false = emqx_banned:check(ClientInfo),
@ -64,12 +63,13 @@ t_detect_check(_) ->
ok = emqx_flapping:stop(). ok = emqx_flapping:stop().
t_expired_detecting(_) -> t_expired_detecting(_) ->
ClientInfo = #{zone => external, ClientInfo = #{zone => default,
clientid => <<"clientid">>, listener => mqtt_tcp,
clientid => <<"client008">>,
peerhost => {127,0,0,1}}, peerhost => {127,0,0,1}},
false = emqx_flapping:detect(ClientInfo), false = emqx_flapping:detect(ClientInfo),
?assertEqual(true, lists:any(fun({flapping, <<"clientid">>, _, _, _}) -> true; ?assertEqual(true, lists:any(fun({flapping, <<"client008">>, _, _, _}) -> true;
(_) -> false end, ets:tab2list(emqx_flapping))), (_) -> false end, ets:tab2list(emqx_flapping))),
timer:sleep(200), timer:sleep(200),
?assertEqual(true, lists:all(fun({flapping, <<"clientid">>, _, _, _}) -> false; ?assertEqual(true, lists:all(fun({flapping, <<"client008">>, _, _, _}) -> false;
(_) -> true end, ets:tab2list(emqx_flapping))). (_) -> true end, ets:tab2list(emqx_flapping))).

View File

@ -119,8 +119,9 @@ t_index_of(_) ->
?assertEqual(3, emqx_misc:index_of(a, [b, c, a, e, f])). ?assertEqual(3, emqx_misc:index_of(a, [b, c, a, e, f])).
t_check(_) -> t_check(_) ->
Policy = #{message_queue_len => 10, Policy = #{max_message_queue_len => 10,
max_heap_size => 1024 * 1024 * 8}, max_heap_size => 1024 * 1024 * 8,
enable => true},
[self() ! {msg, I} || I <- lists:seq(1, 5)], [self() ! {msg, I} || I <- lists:seq(1, 5)],
?assertEqual(ok, emqx_misc:check_oom(Policy)), ?assertEqual(ok, emqx_misc:check_oom(Policy)),
[self() ! {msg, I} || I <- lists:seq(1, 6)], [self() ! {msg, I} || I <- lists:seq(1, 6)],

View File

@ -156,6 +156,15 @@ t_async_set_keepalive('end', _Config) ->
ok. ok.
t_async_set_keepalive(_) -> t_async_set_keepalive(_) ->
case os:type() of
{unix, darwin} ->
%% Mac OSX don't support the feature
ok;
_ ->
do_async_set_keepalive()
end.
do_async_set_keepalive() ->
ClientID = <<"client-tcp-keepalive">>, ClientID = <<"client-tcp-keepalive">>,
{ok, Client} = emqtt:start_link([{host, "localhost"}, {ok, Client} = emqtt:start_link([{host, "localhost"},
{proto_ver,v5}, {proto_ver,v5},

View File

@ -25,39 +25,36 @@
all() -> emqx_ct:all(?MODULE). all() -> emqx_ct:all(?MODULE).
t_check_pub(_) -> t_check_pub(_) ->
PubCaps = #{max_qos_allowed => ?QOS_1, OldConf = emqx_config:get(),
retain_available => false emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, max_qos_allowed], ?QOS_1),
}, emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, retain_available], false),
emqx_zone:set_env(zone, '$mqtt_pub_caps', PubCaps),
timer:sleep(50), timer:sleep(50),
ok = emqx_mqtt_caps:check_pub(zone, #{qos => ?QOS_1, ok = emqx_mqtt_caps:check_pub(default, mqtt_tcp, #{qos => ?QOS_1, retain => false}),
retain => false}),
PubFlags1 = #{qos => ?QOS_2, retain => false}, PubFlags1 = #{qos => ?QOS_2, retain => false},
?assertEqual({error, ?RC_QOS_NOT_SUPPORTED}, ?assertEqual({error, ?RC_QOS_NOT_SUPPORTED},
emqx_mqtt_caps:check_pub(zone, PubFlags1)), emqx_mqtt_caps:check_pub(default, mqtt_tcp, PubFlags1)),
PubFlags2 = #{qos => ?QOS_1, retain => true}, PubFlags2 = #{qos => ?QOS_1, retain => true},
?assertEqual({error, ?RC_RETAIN_NOT_SUPPORTED}, ?assertEqual({error, ?RC_RETAIN_NOT_SUPPORTED},
emqx_mqtt_caps:check_pub(zone, PubFlags2)), emqx_mqtt_caps:check_pub(default, mqtt_tcp, PubFlags2)),
emqx_zone:unset_env(zone, '$mqtt_pub_caps'). emqx_config:put(OldConf).
t_check_sub(_) -> t_check_sub(_) ->
OldConf = emqx_config:get(),
SubOpts = #{rh => 0, SubOpts = #{rh => 0,
rap => 0, rap => 0,
nl => 0, nl => 0,
qos => ?QOS_2 qos => ?QOS_2
}, },
SubCaps = #{max_topic_levels => 2, emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, max_topic_levels], 2),
max_qos_allowed => ?QOS_2, emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, max_qos_allowed], ?QOS_1),
shared_subscription => false, emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, shared_subscription], false),
wildcard_subscription => false emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, wildcard_subscription], false),
},
emqx_zone:set_env(zone, '$mqtt_sub_caps', SubCaps),
timer:sleep(50), timer:sleep(50),
ok = emqx_mqtt_caps:check_sub(zone, <<"topic">>, SubOpts), ok = emqx_mqtt_caps:check_sub(default, mqtt_tcp, <<"topic">>, SubOpts),
?assertEqual({error, ?RC_TOPIC_FILTER_INVALID}, ?assertEqual({error, ?RC_TOPIC_FILTER_INVALID},
emqx_mqtt_caps:check_sub(zone, <<"a/b/c/d">>, SubOpts)), emqx_mqtt_caps:check_sub(default, mqtt_tcp, <<"a/b/c/d">>, SubOpts)),
?assertEqual({error, ?RC_WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED}, ?assertEqual({error, ?RC_WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED},
emqx_mqtt_caps:check_sub(zone, <<"+/#">>, SubOpts)), emqx_mqtt_caps:check_sub(default, mqtt_tcp, <<"+/#">>, SubOpts)),
?assertEqual({error, ?RC_SHARED_SUBSCRIPTIONS_NOT_SUPPORTED}, ?assertEqual({error, ?RC_SHARED_SUBSCRIPTIONS_NOT_SUPPORTED},
emqx_mqtt_caps:check_sub(zone, <<"topic">>, SubOpts#{share => true})), emqx_mqtt_caps:check_sub(default, mqtt_tcp, <<"topic">>, SubOpts#{share => true})),
emqx_zone:unset_env(zone, '$mqtt_pub_caps'). emqx_config:put(OldConf).

View File

@ -217,10 +217,14 @@ t_connect_will_message(Config) ->
ok = emqtt:disconnect(Client4). ok = emqtt:disconnect(Client4).
t_batch_subscribe(init, Config) -> t_batch_subscribe(init, Config) ->
emqx_config:put_listener_conf(default, mqtt_tcp, [acl, enable], true),
emqx_config:put_listener_conf(default, mqtt_quic, [acl, enable], true),
ok = meck:new(emqx_access_control, [non_strict, passthrough, no_history, no_link]), ok = meck:new(emqx_access_control, [non_strict, passthrough, no_history, no_link]),
meck:expect(emqx_access_control, authorize, fun(_, _, _) -> deny end), meck:expect(emqx_access_control, authorize, fun(_, _, _) -> deny end),
Config; Config;
t_batch_subscribe('end', _Config) -> t_batch_subscribe('end', _Config) ->
emqx_config:put_listener_conf(default, mqtt_tcp, [acl, enable], false),
emqx_config:put_listener_conf(default, mqtt_quic, [acl, enable], false),
meck:unload(emqx_access_control). meck:unload(emqx_access_control).
t_batch_subscribe(Config) -> t_batch_subscribe(Config) ->
@ -284,52 +288,22 @@ t_connect_will_retain(Config) ->
t_connect_idle_timeout(_Config) -> t_connect_idle_timeout(_Config) ->
IdleTimeout = 2000, IdleTimeout = 2000,
emqx_zone:set_env(external, idle_timeout, IdleTimeout), emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, idle_timeout], IdleTimeout),
emqx_config:put_listener_conf(default, mqtt_quic, [mqtt, idle_timeout], IdleTimeout),
{ok, Sock} = emqtt_sock:connect({127,0,0,1}, 1883, [], 60000), {ok, Sock} = emqtt_sock:connect({127,0,0,1}, 1883, [], 60000),
timer:sleep(IdleTimeout), timer:sleep(IdleTimeout),
?assertMatch({error, closed}, emqtt_sock:recv(Sock,1024)). ?assertMatch({error, closed}, emqtt_sock:recv(Sock,1024)).
t_connect_limit_timeout(init, Config) ->
ok = meck:new(proplists, [non_strict, passthrough, no_history, no_link, unstick]),
meck:expect(proplists, get_value, fun(active_n, _Options, _Default) -> 1;
(Arg1, ARg2, Arg3) -> meck:passthrough([Arg1, ARg2, Arg3])
end),
Config;
t_connect_limit_timeout('end', _Config) ->
catch meck:unload(proplists).
t_connect_limit_timeout(Config) ->
ConnFun = ?config(conn_fun, Config),
Topic = nth(1, ?TOPICS),
emqx_zone:set_env(external, publish_limit, {3, 5}),
{ok, Client} = emqtt:start_link([{proto_ver, v5},{keepalive, 60} | Config]),
{ok, _} = emqtt:ConnFun(Client),
[ClientPid] = emqx_cm:lookup_channels(client_info(clientid, Client)),
?assertEqual(undefined, emqx_connection:info(limit_timer, sys:get_state(ClientPid))),
Payload = <<"t_shared_subscriptions_client_terminates_when_qos_eq_2">>,
{ok, 2} = emqtt:publish(Client, Topic, Payload, 1),
{ok, 3} = emqtt:publish(Client, Topic, Payload, 1),
{ok, 4} = emqtt:publish(Client, Topic, Payload, 1),
timer:sleep(250),
?assert(is_reference(emqx_connection:info(limit_timer, sys:get_state(ClientPid)))),
ok = emqtt:disconnect(Client),
emqx_zone:set_env(external, publish_limit, undefined),
meck:unload(proplists).
t_connect_emit_stats_timeout(init, Config) -> t_connect_emit_stats_timeout(init, Config) ->
NewIdleTimeout = 1000, NewIdleTimeout = 1000,
OldIdleTimeout = emqx_zone:get_env(external, idle_timeout), emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, idle_timeout], NewIdleTimeout),
emqx_zone:set_env(external, idle_timeout, NewIdleTimeout), emqx_config:put_listener_conf(default, mqtt_quic, [mqtt, idle_timeout], NewIdleTimeout),
ok = snabbkaffe:start_trace(), ok = snabbkaffe:start_trace(),
[{idle_timeout, NewIdleTimeout}, {old_idle_timeout, OldIdleTimeout} | Config]; [{idle_timeout, NewIdleTimeout} | Config];
t_connect_emit_stats_timeout('end', Config) -> t_connect_emit_stats_timeout('end', _Config) ->
snabbkaffe:stop(), snabbkaffe:stop(),
{_, OldIdleTimeout} = lists:keyfind(old_idle_timeout, 1, Config), emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, idle_timeout], 15000),
emqx_zone:set_env(external, idle_timeout, OldIdleTimeout), emqx_config:put_listener_conf(default, mqtt_quic, [mqtt, idle_timeout], 15000),
ok. ok.
t_connect_emit_stats_timeout(Config) -> t_connect_emit_stats_timeout(Config) ->
@ -497,7 +471,8 @@ t_connack_session_present(Config) ->
t_connack_max_qos_allowed(init, Config) -> t_connack_max_qos_allowed(init, Config) ->
Config; Config;
t_connack_max_qos_allowed('end', _Config) -> t_connack_max_qos_allowed('end', _Config) ->
emqx_zone:set_env(external, max_qos_allowed, 2), emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, max_qos_allowed], 2),
emqx_config:put_listener_conf(default, mqtt_quic, [mqtt, max_qos_allowed], 2),
ok. ok.
t_connack_max_qos_allowed(Config) -> t_connack_max_qos_allowed(Config) ->
ConnFun = ?config(conn_fun, Config), ConnFun = ?config(conn_fun, Config),
@ -505,9 +480,8 @@ t_connack_max_qos_allowed(Config) ->
Topic = nth(1, ?TOPICS), Topic = nth(1, ?TOPICS),
%% max_qos_allowed = 0 %% max_qos_allowed = 0
emqx_zone:set_env(external, max_qos_allowed, 0), emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, max_qos_allowed], 0),
persistent_term:erase({emqx_zone, external, '$mqtt_caps'}), emqx_config:put_listener_conf(default, mqtt_quic, [mqtt, max_qos_allowed], 0),
persistent_term:erase({emqx_zone, external, '$mqtt_pub_caps'}),
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]), {ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, Connack1} = emqtt:ConnFun(Client1), {ok, Connack1} = emqtt:ConnFun(Client1),
@ -532,9 +506,8 @@ t_connack_max_qos_allowed(Config) ->
waiting_client_process_exit(Client2), waiting_client_process_exit(Client2),
%% max_qos_allowed = 1 %% max_qos_allowed = 1
emqx_zone:set_env(external, max_qos_allowed, 1), emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, max_qos_allowed], 1),
persistent_term:erase({emqx_zone, external, '$mqtt_caps'}), emqx_config:put_listener_conf(default, mqtt_quic, [mqtt, max_qos_allowed], 1),
persistent_term:erase({emqx_zone, external, '$mqtt_pub_caps'}),
{ok, Client3} = emqtt:start_link([{proto_ver, v5} | Config]), {ok, Client3} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, Connack3} = emqtt:ConnFun(Client3), {ok, Connack3} = emqtt:ConnFun(Client3),
@ -559,9 +532,8 @@ t_connack_max_qos_allowed(Config) ->
waiting_client_process_exit(Client4), waiting_client_process_exit(Client4),
%% max_qos_allowed = 2 %% max_qos_allowed = 2
emqx_zone:set_env(external, max_qos_allowed, 2), emqx_config:put_listener_conf(default, mqtt_tcp, [mqtt, max_qos_allowed], 2),
persistent_term:erase({emqx_zone, external, '$mqtt_caps'}), emqx_config:put_listener_conf(default, mqtt_quic, [mqtt, max_qos_allowed], 2),
persistent_term:erase({emqx_zone, external, '$mqtt_pub_caps'}),
{ok, Client5} = emqtt:start_link([{proto_ver, v5} | Config]), {ok, Client5} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, Connack5} = emqtt:ConnFun(Client5), {ok, Connack5} = emqtt:ConnFun(Client5),

View File

@ -24,46 +24,34 @@
all() -> emqx_ct:all(?MODULE). all() -> emqx_ct:all(?MODULE).
init_per_suite(Config) -> init_per_suite(Config) ->
emqx_config:put([sysmon, os], #{
cpu_check_interval => 60,cpu_high_watermark => 0.8,
cpu_low_watermark => 0.6,mem_check_interval => 60,
procmem_high_watermark => 0.05,sysmem_high_watermark => 0.7}),
application:ensure_all_started(os_mon), application:ensure_all_started(os_mon),
Config. Config.
end_per_suite(_Config) -> end_per_suite(_Config) ->
application:stop(os_mon). application:stop(os_mon).
% t_set_mem_check_interval(_) ->
% error('TODO').
% t_set_sysmem_high_watermark(_) ->
% error('TODO').
% t_set_procmem_high_watermark(_) ->
% error('TODO').
t_api(_) -> t_api(_) ->
gen_event:swap_handler(alarm_handler, {emqx_alarm_handler, swap}, {alarm_handler, []}), gen_event:swap_handler(alarm_handler, {emqx_alarm_handler, swap}, {alarm_handler, []}),
{ok, _} = emqx_os_mon:start_link([{cpu_check_interval, 1}, {ok, _} = emqx_os_mon:start_link(),
{cpu_high_watermark, 5},
{cpu_low_watermark, 80}, ?assertEqual(60, emqx_os_mon:get_mem_check_interval()),
{mem_check_interval, 60}, ?assertEqual(ok, emqx_os_mon:set_mem_check_interval(30)),
{sysmem_high_watermark, 70}, ?assertEqual(60, emqx_os_mon:get_mem_check_interval()),
{procmem_high_watermark, 5}]), ?assertEqual(ok, emqx_os_mon:set_mem_check_interval(122)),
?assertEqual(1, emqx_os_mon:get_cpu_check_interval()), ?assertEqual(120, emqx_os_mon:get_mem_check_interval()),
?assertEqual(5, emqx_os_mon:get_cpu_high_watermark()),
?assertEqual(80, emqx_os_mon:get_cpu_low_watermark()), ?assertEqual(70, emqx_os_mon:get_sysmem_high_watermark()),
?assertEqual(60, emqx_os_mon:get_mem_check_interval()), ?assertEqual(ok, emqx_os_mon:set_sysmem_high_watermark(0.8)),
?assertEqual(70, emqx_os_mon:get_sysmem_high_watermark()), ?assertEqual(80, emqx_os_mon:get_sysmem_high_watermark()),
?assertEqual(5, emqx_os_mon:get_procmem_high_watermark()),
% timer:sleep(2000), ?assertEqual(5, emqx_os_mon:get_procmem_high_watermark()),
% ?assertEqual(true, lists:keymember(cpu_high_watermark, 1, alarm_handler:get_alarms())), ?assertEqual(ok, emqx_os_mon:set_procmem_high_watermark(0.11)),
?assertEqual(11, emqx_os_mon:get_procmem_high_watermark()),
emqx_os_mon:set_cpu_check_interval(0.05),
emqx_os_mon:set_cpu_high_watermark(80),
emqx_os_mon:set_cpu_low_watermark(75),
?assertEqual(0.05, emqx_os_mon:get_cpu_check_interval()),
?assertEqual(80, emqx_os_mon:get_cpu_high_watermark()),
?assertEqual(75, emqx_os_mon:get_cpu_low_watermark()),
% timer:sleep(3000),
% ?assertEqual(false, lists:keymember(cpu_high_watermark, 1, alarm_handler:get_alarms())),
?assertEqual(ignored, gen_server:call(emqx_os_mon, ignored)), ?assertEqual(ignored, gen_server:call(emqx_os_mon, ignored)),
?assertEqual(ok, gen_server:cast(emqx_os_mon, ignored)), ?assertEqual(ok, gen_server:cast(emqx_os_mon, ignored)),
emqx_os_mon ! ignored, emqx_os_mon ! ignored,

View File

@ -29,6 +29,7 @@ all() -> emqx_ct:all(?MODULE).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init_per_suite(Config) -> init_per_suite(Config) ->
emqx_channel_SUITE:set_default_zone_conf(),
ok = meck:new([emqx_hooks, emqx_metrics, emqx_broker], ok = meck:new([emqx_hooks, emqx_metrics, emqx_broker],
[passthrough, no_history, no_link]), [passthrough, no_history, no_link]),
ok = meck:expect(emqx_metrics, inc, fun(_) -> ok end), ok = meck:expect(emqx_metrics, inc, fun(_) -> ok end),
@ -50,15 +51,16 @@ end_per_testcase(_TestCase, Config) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
t_session_init(_) -> t_session_init(_) ->
Session = emqx_session:init(#{zone => zone}, #{receive_maximum => 64}), Session = emqx_session:init(#{zone => default, listener => mqtt_tcp},
#{receive_maximum => 64}),
?assertEqual(#{}, emqx_session:info(subscriptions, Session)), ?assertEqual(#{}, emqx_session:info(subscriptions, Session)),
?assertEqual(0, emqx_session:info(subscriptions_cnt, Session)), ?assertEqual(0, emqx_session:info(subscriptions_cnt, Session)),
?assertEqual(0, emqx_session:info(subscriptions_max, Session)), ?assertEqual(infinity, emqx_session:info(subscriptions_max, Session)),
?assertEqual(false, emqx_session:info(upgrade_qos, Session)), ?assertEqual(false, emqx_session:info(upgrade_qos, Session)),
?assertEqual(0, emqx_session:info(inflight_cnt, Session)), ?assertEqual(0, emqx_session:info(inflight_cnt, Session)),
?assertEqual(64, emqx_session:info(inflight_max, Session)), ?assertEqual(64, emqx_session:info(inflight_max, Session)),
?assertEqual(1, emqx_session:info(next_pkt_id, Session)), ?assertEqual(1, emqx_session:info(next_pkt_id, Session)),
?assertEqual(0, emqx_session:info(retry_interval, Session)), ?assertEqual(30, emqx_session:info(retry_interval, Session)),
?assertEqual(0, emqx_mqueue:len(emqx_session:info(mqueue, Session))), ?assertEqual(0, emqx_mqueue:len(emqx_session:info(mqueue, Session))),
?assertEqual(0, emqx_session:info(awaiting_rel_cnt, Session)), ?assertEqual(0, emqx_session:info(awaiting_rel_cnt, Session)),
?assertEqual(100, emqx_session:info(awaiting_rel_max, Session)), ?assertEqual(100, emqx_session:info(awaiting_rel_max, Session)),
@ -72,13 +74,13 @@ t_session_init(_) ->
t_session_info(_) -> t_session_info(_) ->
?assertMatch(#{subscriptions := #{}, ?assertMatch(#{subscriptions := #{},
upgrade_qos := false, upgrade_qos := false,
retry_interval := 0, retry_interval := 30,
await_rel_timeout := 300 await_rel_timeout := 300
}, emqx_session:info(session())). }, emqx_session:info(session())).
t_session_stats(_) -> t_session_stats(_) ->
Stats = emqx_session:stats(session()), Stats = emqx_session:stats(session()),
?assertMatch(#{subscriptions_max := 0, ?assertMatch(#{subscriptions_max := infinity,
inflight_max := 0, inflight_max := 0,
mqueue_len := 0, mqueue_len := 0,
mqueue_max := 1000, mqueue_max := 1000,
@ -99,7 +101,7 @@ t_subscribe(_) ->
?assertEqual(1, emqx_session:info(subscriptions_cnt, Session)). ?assertEqual(1, emqx_session:info(subscriptions_cnt, Session)).
t_is_subscriptions_full_false(_) -> t_is_subscriptions_full_false(_) ->
Session = session(#{max_subscriptions => 0}), Session = session(#{max_subscriptions => infinity}),
?assertNot(emqx_session:is_subscriptions_full(Session)). ?assertNot(emqx_session:is_subscriptions_full(Session)).
t_is_subscriptions_full_true(_) -> t_is_subscriptions_full_true(_) ->
@ -152,7 +154,7 @@ t_publish_qos2_with_error_return(_) ->
{error, ?RC_RECEIVE_MAXIMUM_EXCEEDED} = emqx_session:publish(3, Msg, Session1). {error, ?RC_RECEIVE_MAXIMUM_EXCEEDED} = emqx_session:publish(3, Msg, Session1).
t_is_awaiting_full_false(_) -> t_is_awaiting_full_false(_) ->
Session = session(#{max_awaiting_rel => 0}), Session = session(#{max_awaiting_rel => infinity}),
?assertNot(emqx_session:is_awaiting_full(Session)). ?assertNot(emqx_session:is_awaiting_full(Session)).
t_is_awaiting_full_true(_) -> t_is_awaiting_full_true(_) ->
@ -375,7 +377,8 @@ session(InitFields) when is_map(InitFields) ->
maps:fold(fun(Field, Value, Session) -> maps:fold(fun(Field, Value, Session) ->
emqx_session:set_field(Field, Value, Session) emqx_session:set_field(Field, Value, Session)
end, end,
emqx_session:init(#{zone => channel}, #{receive_maximum => 0}), emqx_session:init(#{zone => default, listener => mqtt_tcp},
#{receive_maximum => 0}),
InitFields). InitFields).

View File

@ -23,17 +23,16 @@
all() -> emqx_ct:all(?MODULE). all() -> emqx_ct:all(?MODULE).
init_per_testcase(t_api, Config) -> init_per_testcase(t_alarms, Config) ->
emqx_ct_helpers:boot_modules(all), emqx_ct_helpers:boot_modules(all),
emqx_ct_helpers:start_apps([], emqx_ct_helpers:start_apps([]),
fun(emqx) -> emqx_config:put([sysmon, vm], #{
application:set_env(emqx, vm_mon, [{check_interval, 1}, process_high_watermark => 0,
{process_high_watermark, 80}, process_low_watermark => 0,
{process_low_watermark, 75}]), process_check_interval => 100 %% 1s
ok; }),
(_) -> ok = supervisor:terminate_child(emqx_sys_sup, emqx_vm_mon),
ok {ok, _} = supervisor:restart_child(emqx_sys_sup, emqx_vm_mon),
end),
Config; Config;
init_per_testcase(_, Config) -> init_per_testcase(_, Config) ->
emqx_ct_helpers:boot_modules(all), emqx_ct_helpers:boot_modules(all),
@ -43,18 +42,12 @@ init_per_testcase(_, Config) ->
end_per_testcase(_, _Config) -> end_per_testcase(_, _Config) ->
emqx_ct_helpers:stop_apps([]). emqx_ct_helpers:stop_apps([]).
t_api(_) -> t_alarms(_) ->
?assertEqual(1, emqx_vm_mon:get_check_interval()), timer:sleep(500),
?assertEqual(80, emqx_vm_mon:get_process_high_watermark()),
?assertEqual(75, emqx_vm_mon:get_process_low_watermark()),
emqx_vm_mon:set_process_high_watermark(0),
emqx_vm_mon:set_process_low_watermark(60),
?assertEqual(0, emqx_vm_mon:get_process_high_watermark()),
?assertEqual(60, emqx_vm_mon:get_process_low_watermark()),
timer:sleep(emqx_vm_mon:get_check_interval() * 1000 * 2),
?assert(is_existing(too_many_processes, emqx_alarm:get_alarms(activated))), ?assert(is_existing(too_many_processes, emqx_alarm:get_alarms(activated))),
emqx_vm_mon:set_process_high_watermark(70), emqx_config:put([sysmon, vm, process_high_watermark], 70),
timer:sleep(emqx_vm_mon:get_check_interval() * 1000 * 2), emqx_config:put([sysmon, vm, process_low_watermark], 60),
timer:sleep(500),
?assertNot(is_existing(too_many_processes, emqx_alarm:get_alarms(activated))). ?assertNot(is_existing(too_many_processes, emqx_alarm:get_alarms(activated))).
is_existing(Name, [#{name := Name} | _More]) -> is_existing(Name, [#{name := Name} | _More]) ->

View File

@ -48,6 +48,7 @@ init_per_testcase(TestCase, Config) when
TestCase =/= t_ws_pingreq_before_connected, TestCase =/= t_ws_pingreq_before_connected,
TestCase =/= t_ws_non_check_origin TestCase =/= t_ws_non_check_origin
-> ->
emqx_channel_SUITE:set_default_zone_conf(),
%% Mock cowboy_req %% Mock cowboy_req
ok = meck:new(cowboy_req, [passthrough, no_history, no_link]), ok = meck:new(cowboy_req, [passthrough, no_history, no_link]),
ok = meck:expect(cowboy_req, header, fun(_, _, _) -> <<>> end), ok = meck:expect(cowboy_req, header, fun(_, _, _) -> <<>> end),
@ -55,13 +56,6 @@ init_per_testcase(TestCase, Config) when
ok = meck:expect(cowboy_req, sock, fun(_) -> {{127,0,0,1}, 18083} end), ok = meck:expect(cowboy_req, sock, fun(_) -> {{127,0,0,1}, 18083} end),
ok = meck:expect(cowboy_req, cert, fun(_) -> undefined end), ok = meck:expect(cowboy_req, cert, fun(_) -> undefined end),
ok = meck:expect(cowboy_req, parse_cookies, fun(_) -> error(badarg) end), ok = meck:expect(cowboy_req, parse_cookies, fun(_) -> error(badarg) end),
%% Mock emqx_zone
ok = meck:new(emqx_zone, [passthrough, no_history, no_link]),
ok = meck:expect(emqx_zone, oom_policy,
fun(_) -> #{max_heap_size => 838860800,
message_queue_len => 8000
}
end),
%% Mock emqx_access_control %% Mock emqx_access_control
ok = meck:new(emqx_access_control, [passthrough, no_history, no_link]), ok = meck:new(emqx_access_control, [passthrough, no_history, no_link]),
ok = meck:expect(emqx_access_control, authorize, fun(_, _, _) -> allow end), ok = meck:expect(emqx_access_control, authorize, fun(_, _, _) -> allow end),
@ -85,6 +79,7 @@ init_per_testcase(TestCase, Config) when
Config; Config;
init_per_testcase(_, Config) -> init_per_testcase(_, Config) ->
ok = emqx_ct_helpers:start_apps([]),
Config. Config.
end_per_testcase(TestCase, _Config) when end_per_testcase(TestCase, _Config) when
@ -96,7 +91,6 @@ end_per_testcase(TestCase, _Config) when
-> ->
lists:foreach(fun meck:unload/1, lists:foreach(fun meck:unload/1,
[cowboy_req, [cowboy_req,
emqx_zone,
emqx_access_control, emqx_access_control,
emqx_broker, emqx_broker,
emqx_hooks, emqx_hooks,
@ -104,6 +98,7 @@ end_per_testcase(TestCase, _Config) when
]); ]);
end_per_testcase(_, Config) -> end_per_testcase(_, Config) ->
emqx_ct_helpers:stop_apps([]),
Config. Config.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -118,18 +113,21 @@ t_info(_) ->
end), end),
#{sockinfo := SockInfo} = ?ws_conn:call(WsPid, info), #{sockinfo := SockInfo} = ?ws_conn:call(WsPid, info),
#{socktype := ws, #{socktype := ws,
active_n := 100,
peername := {{127,0,0,1}, 3456}, peername := {{127,0,0,1}, 3456},
sockname := {{127,0,0,1}, 18083}, sockname := {{127,0,0,1}, 18083},
sockstate := running sockstate := running
} = SockInfo. } = SockInfo.
set_ws_opts(Key, Val) ->
emqx_config:put_listener_conf(default, mqtt_ws, [websocket, Key], Val).
t_header(_) -> t_header(_) ->
ok = meck:expect(cowboy_req, header, fun(<<"x-forwarded-for">>, _, _) -> <<"100.100.100.100, 99.99.99.99">>; ok = meck:expect(cowboy_req, header,
fun(<<"x-forwarded-for">>, _, _) -> <<"100.100.100.100, 99.99.99.99">>;
(<<"x-forwarded-port">>, _, _) -> <<"1000">> end), (<<"x-forwarded-port">>, _, _) -> <<"1000">> end),
{ok, St, _} = ?ws_conn:websocket_init([req, [{zone, external}, set_ws_opts(proxy_address_header, <<"x-forwarded-for">>),
{proxy_address_header, <<"x-forwarded-for">>}, set_ws_opts(proxy_port_header, <<"x-forwarded-port">>),
{proxy_port_header, <<"x-forwarded-port">>}]]), {ok, St, _} = ?ws_conn:websocket_init([req, #{zone => default, listener => mqtt_ws}]),
WsPid = spawn(fun() -> WsPid = spawn(fun() ->
receive {call, From, info} -> receive {call, From, info} ->
gen_server:reply(From, ?ws_conn:info(St)) gen_server:reply(From, ?ws_conn:info(St))
@ -175,12 +173,10 @@ t_call(_) ->
?assertEqual(Info, ?ws_conn:call(WsPid, info)). ?assertEqual(Info, ?ws_conn:call(WsPid, info)).
t_ws_pingreq_before_connected(_) -> t_ws_pingreq_before_connected(_) ->
ok = emqx_ct_helpers:start_apps([]),
{ok, _} = application:ensure_all_started(gun), {ok, _} = application:ensure_all_started(gun),
{ok, WPID} = gun:open("127.0.0.1", 8083), {ok, WPID} = gun:open("127.0.0.1", 8083),
ws_pingreq(#{}), ws_pingreq(#{}),
gun:close(WPID), gun:close(WPID).
emqx_ct_helpers:stop_apps([]).
ws_pingreq(State) -> ws_pingreq(State) ->
receive receive
@ -209,14 +205,11 @@ ws_pingreq(State) ->
end. end.
t_ws_sub_protocols_mqtt(_) -> t_ws_sub_protocols_mqtt(_) ->
ok = emqx_ct_helpers:start_apps([]),
{ok, _} = application:ensure_all_started(gun), {ok, _} = application:ensure_all_started(gun),
?assertMatch({gun_upgrade, _}, ?assertMatch({gun_upgrade, _},
start_ws_client(#{protocols => [<<"mqtt">>]})), start_ws_client(#{protocols => [<<"mqtt">>]})).
emqx_ct_helpers:stop_apps([]).
t_ws_sub_protocols_mqtt_equivalents(_) -> t_ws_sub_protocols_mqtt_equivalents(_) ->
ok = emqx_ct_helpers:start_apps([]),
{ok, _} = application:ensure_all_started(gun), {ok, _} = application:ensure_all_started(gun),
%% also support mqtt-v3, mqtt-v3.1.1, mqtt-v5 %% also support mqtt-v3, mqtt-v3.1.1, mqtt-v5
?assertMatch({gun_upgrade, _}, ?assertMatch({gun_upgrade, _},
@ -226,58 +219,39 @@ t_ws_sub_protocols_mqtt_equivalents(_) ->
?assertMatch({gun_upgrade, _}, ?assertMatch({gun_upgrade, _},
start_ws_client(#{protocols => [<<"mqtt-v5">>]})), start_ws_client(#{protocols => [<<"mqtt-v5">>]})),
?assertMatch({gun_response, {_, 400, _}}, ?assertMatch({gun_response, {_, 400, _}},
start_ws_client(#{protocols => [<<"not-mqtt">>]})), start_ws_client(#{protocols => [<<"not-mqtt">>]})).
emqx_ct_helpers:stop_apps([]).
t_ws_check_origin(_) -> t_ws_check_origin(_) ->
emqx_ct_helpers:start_apps([], emqx_config:put_listener_conf(default, mqtt_ws, [websocket, check_origin_enable], true),
fun(emqx) -> emqx_config:put_listener_conf(default, mqtt_ws, [websocket, check_origins],
{ok, Listeners} = application:get_env(emqx, listeners), [<<"http://localhost:18083">>]),
NListeners = lists:map(fun(#{listen_on := 8083, opts := Opts} = Listener) ->
NOpts = proplists:delete(check_origin_enable, Opts),
Listener#{opts => [{check_origin_enable, true} | NOpts]};
(Listener) ->
Listener
end, Listeners),
application:set_env(emqx, listeners, NListeners),
ok;
(_) -> ok
end),
{ok, _} = application:ensure_all_started(gun), {ok, _} = application:ensure_all_started(gun),
?assertMatch({gun_upgrade, _}, ?assertMatch({gun_upgrade, _},
start_ws_client(#{protocols => [<<"mqtt">>], start_ws_client(#{protocols => [<<"mqtt">>],
headers => [{<<"origin">>, <<"http://localhost:18083">>}]})), headers => [{<<"origin">>, <<"http://localhost:18083">>}]})),
?assertMatch({gun_response, {_, 500, _}}, ?assertMatch({gun_response, {_, 500, _}},
start_ws_client(#{protocols => [<<"mqtt">>], start_ws_client(#{protocols => [<<"mqtt">>],
headers => [{<<"origin">>, <<"http://localhost:18080">>}]})), headers => [{<<"origin">>, <<"http://localhost:18080">>}]})).
emqx_ct_helpers:stop_apps([]).
t_ws_non_check_origin(_) -> t_ws_non_check_origin(_) ->
emqx_ct_helpers:start_apps([]), emqx_config:put_listener_conf(default, mqtt_ws, [websocket, check_origin_enable], false),
emqx_config:put_listener_conf(default, mqtt_ws, [websocket, check_origins], []),
{ok, _} = application:ensure_all_started(gun), {ok, _} = application:ensure_all_started(gun),
?assertMatch({gun_upgrade, _}, ?assertMatch({gun_upgrade, _},
start_ws_client(#{protocols => [<<"mqtt">>], start_ws_client(#{protocols => [<<"mqtt">>],
headers => [{<<"origin">>, <<"http://localhost:18083">>}]})), headers => [{<<"origin">>, <<"http://localhost:18083">>}]})),
?assertMatch({gun_upgrade, _}, ?assertMatch({gun_upgrade, _},
start_ws_client(#{protocols => [<<"mqtt">>], start_ws_client(#{protocols => [<<"mqtt">>],
headers => [{<<"origin">>, <<"http://localhost:18080">>}]})), headers => [{<<"origin">>, <<"http://localhost:18080">>}]})).
emqx_ct_helpers:stop_apps([]).
t_init(_) -> t_init(_) ->
Opts = [{idle_timeout, 300000}, Opts = #{listener => mqtt_ws, zone => default},
{fail_if_no_subprotocol, false},
{supported_subprotocols, ["mqtt"]}],
WsOpts = #{compress => false,
deflate_opts => #{},
max_frame_size => infinity,
idle_timeout => 300000
},
ok = meck:expect(cowboy_req, parse_header, fun(_, req) -> undefined end), ok = meck:expect(cowboy_req, parse_header, fun(_, req) -> undefined end),
{cowboy_websocket, req, [req, Opts], WsOpts} = ?ws_conn:init(req, Opts), ok = meck:expect(cowboy_req, reply, fun(_, Req) -> Req end),
{ok, req, _} = ?ws_conn:init(req, Opts),
ok = meck:expect(cowboy_req, parse_header, fun(_, req) -> [<<"mqtt">>] end), ok = meck:expect(cowboy_req, parse_header, fun(_, req) -> [<<"mqtt">>] end),
ok = meck:expect(cowboy_req, set_resp_header, fun(_, <<"mqtt">>, req) -> resp end), ok = meck:expect(cowboy_req, set_resp_header, fun(_, <<"mqtt">>, req) -> resp end),
{cowboy_websocket, resp, [req, Opts], WsOpts} = ?ws_conn:init(req, Opts). {cowboy_websocket, resp, [req, Opts], _} = ?ws_conn:init(req, Opts).
t_websocket_handle_binary(_) -> t_websocket_handle_binary(_) ->
{ok, _} = websocket_handle({binary, <<>>}, st()), {ok, _} = websocket_handle({binary, <<>>}, st()),
@ -450,15 +424,6 @@ t_run_gc(_) ->
WsSt = st(#{gc_state => GcSt}), WsSt = st(#{gc_state => GcSt}),
?ws_conn:run_gc(#{cnt => 100, oct => 10000}, WsSt). ?ws_conn:run_gc(#{cnt => 100, oct => 10000}, WsSt).
t_check_oom(_) ->
%%Policy = #{max_heap_size => 10, message_queue_len => 10},
%%meck:expect(emqx_zone, oom_policy, fun(_) -> Policy end),
_St = ?ws_conn:check_oom(st()),
ok = timer:sleep(10).
%%receive {shutdown, proc_heap_too_large} -> ok
%%after 0 -> error(expect_shutdown)
%%end.
t_enqueue(_) -> t_enqueue(_) ->
Packet = ?PUBLISH_PACKET(?QOS_0), Packet = ?PUBLISH_PACKET(?QOS_0),
St = ?ws_conn:enqueue(Packet, st()), St = ?ws_conn:enqueue(Packet, st()),
@ -473,7 +438,7 @@ t_shutdown(_) ->
st() -> st(#{}). st() -> st(#{}).
st(InitFields) when is_map(InitFields) -> st(InitFields) when is_map(InitFields) ->
{ok, St, _} = ?ws_conn:websocket_init([req, [{zone, external}]]), {ok, St, _} = ?ws_conn:websocket_init([req, #{zone => default, listener => mqtt_ws}]),
maps:fold(fun(N, V, S) -> ?ws_conn:set_field(N, V, S) end, maps:fold(fun(N, V, S) -> ?ws_conn:set_field(N, V, S) end,
?ws_conn:set_field(channel, channel(), St), ?ws_conn:set_field(channel, channel(), St),
InitFields InitFields
@ -493,7 +458,8 @@ channel(InitFields) ->
receive_maximum => 100, receive_maximum => 100,
expiry_interval => 0 expiry_interval => 0
}, },
ClientInfo = #{zone => zone, ClientInfo = #{zone => default,
listener => mqtt_ws,
protocol => mqtt, protocol => mqtt,
peerhost => {127,0,0,1}, peerhost => {127,0,0,1},
clientid => <<"clientid">>, clientid => <<"clientid">>,
@ -502,13 +468,13 @@ channel(InitFields) ->
peercert => undefined, peercert => undefined,
mountpoint => undefined mountpoint => undefined
}, },
Session = emqx_session:init(#{zone => external}, Session = emqx_session:init(#{zone => default, listener => mqtt_ws},
#{receive_maximum => 0} #{receive_maximum => 0}
), ),
maps:fold(fun(Field, Value, Channel) -> maps:fold(fun(Field, Value, Channel) ->
emqx_channel:set_field(Field, Value, Channel) emqx_channel:set_field(Field, Value, Channel)
end, end,
emqx_channel:init(ConnInfo, [{zone, zone}]), emqx_channel:init(ConnInfo, #{zone => default, listener => mqtt_ws}),
maps:merge(#{clientinfo => ClientInfo, maps:merge(#{clientinfo => ClientInfo,
session => Session, session => Session,
conn_state => connected conn_state => connected

View File

@ -59,6 +59,8 @@ prop_sys() ->
do_setup() -> do_setup() ->
ok = emqx_logger:set_log_level(emergency), ok = emqx_logger:set_log_level(emergency),
emqx_config:put([broker, sys_msg_interval], 60000),
emqx_config:put([broker, sys_heartbeat_interval], 30000),
[mock(Mod) || Mod <- ?mock_modules], [mock(Mod) || Mod <- ?mock_modules],
ok. ok.
@ -98,8 +100,6 @@ command(_State) ->
{call, emqx_sys, uptime, []}, {call, emqx_sys, uptime, []},
{call, emqx_sys, datetime, []}, {call, emqx_sys, datetime, []},
{call, emqx_sys, sysdescr, []}, {call, emqx_sys, sysdescr, []},
{call, emqx_sys, sys_interval, []},
{call, emqx_sys, sys_heatbeat_interval, []},
%------------ unexpected message ----------------------% %------------ unexpected message ----------------------%
{call, emqx_sys, handle_call, [emqx_sys, other, state]}, {call, emqx_sys, handle_call, [emqx_sys, other, state]},
{call, emqx_sys, handle_cast, [emqx_sys, other]}, {call, emqx_sys, handle_cast, [emqx_sys, other]},

View File

@ -49,6 +49,7 @@
-export([mnesia/1]). -export([mnesia/1]).
-boot_mnesia({mnesia, [boot]}). -boot_mnesia({mnesia, [boot]}).
-copy_mnesia({mnesia, [copy]}).
-define(CHAIN_TAB, emqx_authn_chain). -define(CHAIN_TAB, emqx_authn_chain).
@ -69,7 +70,10 @@ mnesia(boot) ->
{record_name, chain}, {record_name, chain},
{local_content, true}, {local_content, true},
{attributes, record_info(fields, chain)}, {attributes, record_info(fields, chain)},
{storage_properties, StoreProps}]). {storage_properties, StoreProps}]);
mnesia(copy) ->
ok = ekka_mnesia:copy_table(?CHAIN_TAB, ram_copies).
enable() -> enable() ->
case emqx:hook('client.authenticate', {?MODULE, authenticate, []}) of case emqx:hook('client.authenticate', {?MODULE, authenticate, []}) of

View File

@ -42,7 +42,7 @@ end_per_suite(_) ->
set_special_configs(emqx_authn) -> set_special_configs(emqx_authn) ->
application:set_env(emqx, plugins_etc_dir, application:set_env(emqx, plugins_etc_dir,
emqx_ct_helpers:deps_path(emqx_authn, "test")), emqx_ct_helpers:deps_path(emqx_authn, "test")),
Conf = #{<<"emqx_authn">> => #{<<"authenticators">> => []}}, Conf = #{<<"emqx_authn">> => #{<<"authenticators">> => [], <<"enable">> => false}},
ok = file:write_file(filename:join(emqx:get_env(plugins_etc_dir), 'emqx_authn.conf'), jsx:encode(Conf)), ok = file:write_file(filename:join(emqx:get_env(plugins_etc_dir), 'emqx_authn.conf'), jsx:encode(Conf)),
ok; ok;
set_special_configs(_App) -> set_special_configs(_App) ->

View File

@ -41,7 +41,7 @@ end_per_suite(_) ->
set_special_configs(emqx_authn) -> set_special_configs(emqx_authn) ->
application:set_env(emqx, plugins_etc_dir, application:set_env(emqx, plugins_etc_dir,
emqx_ct_helpers:deps_path(emqx_authn, "test")), emqx_ct_helpers:deps_path(emqx_authn, "test")),
Conf = #{<<"emqx_authn">> => #{<<"authenticators">> => []}}, Conf = #{<<"emqx_authn">> => #{<<"authenticators">> => [], <<"enable">> => false}},
ok = file:write_file(filename:join(emqx:get_env(plugins_etc_dir), 'emqx_authn.conf'), jsx:encode(Conf)), ok = file:write_file(filename:join(emqx:get_env(plugins_etc_dir), 'emqx_authn.conf'), jsx:encode(Conf)),
ok; ok;
set_special_configs(_App) -> set_special_configs(_App) ->

View File

@ -41,7 +41,7 @@ end_per_suite(_) ->
set_special_configs(emqx_authn) -> set_special_configs(emqx_authn) ->
application:set_env(emqx, plugins_etc_dir, application:set_env(emqx, plugins_etc_dir,
emqx_ct_helpers:deps_path(emqx_authn, "test")), emqx_ct_helpers:deps_path(emqx_authn, "test")),
Conf = #{<<"emqx_authn">> => #{<<"authenticators">> => []}}, Conf = #{<<"emqx_authn">> => #{<<"authenticators">> => [], <<"enable">> => false}},
ok = file:write_file(filename:join(emqx:get_env(plugins_etc_dir), 'emqx_authn.conf'), jsx:encode(Conf)), ok = file:write_file(filename:join(emqx:get_env(plugins_etc_dir), 'emqx_authn.conf'), jsx:encode(Conf)),
ok; ok;
set_special_configs(_App) -> set_special_configs(_App) ->

View File

@ -57,7 +57,7 @@ emqx_authz:{
# type: mongo # type: mongo
# config: { # config: {
# mongo_type: single # mongo_type: single
# servers: "127.0.0.1:27017" # server: "127.0.0.1:27017"
# pool_size: 1 # pool_size: 1
# database: mqtt # database: mqtt
# ssl: {enable: false} # ssl: {enable: false}

View File

@ -15,6 +15,7 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-module(emqx_authz). -module(emqx_authz).
-behaviour(emqx_config_handler).
-include("emqx_authz.hrl"). -include("emqx_authz.hrl").
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
@ -23,33 +24,55 @@
-export([ register_metrics/0 -export([ register_metrics/0
, init/0 , init/0
, compile/1 , init_rule/1
, lookup/0 , lookup/0
, update/1 , update/2
, authorize/5 , authorize/5
, match/4 , match/4
]). ]).
-export([post_update_config/2, handle_update_config/2]).
-define(CONF_KEY_PATH, [emqx_authz, rules]).
-spec(register_metrics() -> ok). -spec(register_metrics() -> ok).
register_metrics() -> register_metrics() ->
lists:foreach(fun emqx_metrics:ensure/1, ?AUTHZ_METRICS). lists:foreach(fun emqx_metrics:ensure/1, ?AUTHZ_METRICS).
init() -> init() ->
ok = register_metrics(), ok = register_metrics(),
Rules = emqx_config:get([emqx_authz, rules], []), emqx_config_handler:add_handler(?CONF_KEY_PATH, ?MODULE),
NRules = [compile(Rule) || Rule <- Rules], NRules = [init_rule(Rule) || Rule <- lookup()],
ok = emqx_hooks:add('client.authorize', {?MODULE, authorize, [NRules]}, -1). ok = emqx_hooks:add('client.authorize', {?MODULE, authorize, [NRules]}, -1).
lookup() -> lookup() ->
emqx_config:get([emqx_authz, rules], []). emqx_config:get(?CONF_KEY_PATH, []).
update(Rules) -> update(Cmd, Rules) ->
emqx_config:put([emqx_authz], #{rules => Rules}), emqx_config:update_config(?CONF_KEY_PATH, {Cmd, Rules}).
NRules = [compile(Rule) || Rule <- Rules],
%% For now we only support re-creating the entire rule list
handle_update_config({head, Rule}, OldConf) when is_map(Rule), is_list(OldConf) ->
[Rule | OldConf];
handle_update_config({tail, Rule}, OldConf) when is_map(Rule), is_list(OldConf) ->
OldConf ++ [Rule];
handle_update_config({_, NewConf}, _OldConf) ->
%% overwrite the entire config!
case is_list(NewConf) of
true -> NewConf;
false -> [NewConf]
end.
post_update_config(undefined, _OldConf) ->
%_ = [release_rules(Rule) || Rule <- OldConf],
ok;
post_update_config(NewRules, _OldConf) ->
%_ = [release_rules(Rule) || Rule <- OldConf],
InitedRules = [init_rule(Rule) || Rule <- NewRules],
Action = find_action_in_hooks(), Action = find_action_in_hooks(),
ok = emqx_hooks:del('client.authorize', Action), ok = emqx_hooks:del('client.authorize', Action),
ok = emqx_hooks:add('client.authorize', {?MODULE, authorize, [NRules]}, -1), ok = emqx_hooks:add('client.authorize', {?MODULE, authorize, [InitedRules]}, -1),
ok = emqx_acl_cache:empty_acl_cache(). ok = emqx_acl_cache:drain_cache().
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Internal functions %% Internal functions
@ -77,8 +100,8 @@ create_resource(#{type := DB,
error({load_config_error, Reason}) error({load_config_error, Reason})
end. end.
-spec(compile(rule()) -> rule()). -spec(init_rule(rule()) -> rule()).
compile(#{topics := Topics, init_rule(#{topics := Topics,
action := Action, action := Action,
permission := Permission, permission := Permission,
principal := Principal principal := Principal
@ -88,7 +111,7 @@ compile(#{topics := Topics,
topics => NTopics topics => NTopics
}; };
compile(#{principal := Principal, init_rule(#{principal := Principal,
type := http, type := http,
config := #{url := Url} = Config config := #{url := Url} = Config
} = Rule) -> } = Rule) ->
@ -96,14 +119,14 @@ compile(#{principal := Principal,
NRule = create_resource(Rule#{config := NConfig}), NRule = create_resource(Rule#{config := NConfig}),
NRule#{principal => compile_principal(Principal)}; NRule#{principal => compile_principal(Principal)};
compile(#{principal := Principal, init_rule(#{principal := Principal,
type := DB type := DB
} = Rule) when DB =:= redis; } = Rule) when DB =:= redis;
DB =:= mongo -> DB =:= mongo ->
NRule = create_resource(Rule), NRule = create_resource(Rule),
NRule#{principal => compile_principal(Principal)}; NRule#{principal => compile_principal(Principal)};
compile(#{principal := Principal, init_rule(#{principal := Principal,
type := DB, type := DB,
sql := SQL sql := SQL
} = Rule) when DB =:= mysql; } = Rule) when DB =:= mysql;

View File

@ -56,28 +56,23 @@ lookup_authz(_Bindings, _Params) ->
return({ok, emqx_authz:lookup()}). return({ok, emqx_authz:lookup()}).
update_authz(_Bindings, Params) -> update_authz(_Bindings, Params) ->
Rules = get_rules(Params), Rules = form_rules(Params),
return(emqx_authz:update(Rules)). return(emqx_authz:update(replace, Rules)).
append_authz(_Bindings, Params) -> append_authz(_Bindings, Params) ->
Rules = get_rules(Params), Rules = form_rules(Params),
NRules = lists:append(emqx_authz:lookup(), Rules), return(emqx_authz:update(tail, Rules)).
return(emqx_authz:update(NRules)).
push_authz(_Bindings, Params) -> push_authz(_Bindings, Params) ->
Rules = get_rules(Params), Rules = form_rules(Params),
NRules = lists:append(Rules, emqx_authz:lookup()), return(emqx_authz:update(head, Rules)).
return(emqx_authz:update(NRules)).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Interval Funcs %% Interval Funcs
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
get_rules(Params) -> form_rules(Params) ->
{ok, Conf} = hocon:binary(jsx:encode(#{<<"emqx_authz">> => Params}), #{format => richmap}), Params.
CheckConf = hocon_schema:check(emqx_authz_schema, Conf, #{atom_key => true}),
#{emqx_authz := #{rules := Rules}} = hocon_schema:richmap_to_map(CheckConf),
Rules.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% EUnits %% EUnits

View File

@ -71,7 +71,7 @@ match(Client, PubSub, Topic,
#{<<"simple_rule">> => Rule}, #{<<"simple_rule">> => Rule},
#{atom_key => true}, #{atom_key => true},
[simple_rule]), [simple_rule]),
case emqx_authz:match(Client, PubSub, Topic, emqx_authz:compile(NRule)) of case emqx_authz:match(Client, PubSub, Topic, emqx_authz:init_rule(NRule)) of
true -> {matched, NPermission}; true -> {matched, NPermission};
false -> nomatch false -> nomatch
end. end.

View File

@ -90,7 +90,7 @@ match(Client, PubSub, Topic,
#{<<"simple_rule">> => Rule}, #{<<"simple_rule">> => Rule},
#{atom_key => true}, #{atom_key => true},
[simple_rule]), [simple_rule]),
case emqx_authz:match(Client, PubSub, Topic, emqx_authz:compile(NRule)) of case emqx_authz:match(Client, PubSub, Topic, emqx_authz:init_rule(NRule)) of
true -> {matched, NPermission}; true -> {matched, NPermission};
false -> nomatch false -> nomatch
end. end.

View File

@ -94,7 +94,7 @@ match(Client, PubSub, Topic,
#{<<"simple_rule">> => Rule}, #{<<"simple_rule">> => Rule},
#{atom_key => true}, #{atom_key => true},
[simple_rule]), [simple_rule]),
case emqx_authz:match(Client, PubSub, Topic, emqx_authz:compile(NRule)) of case emqx_authz:match(Client, PubSub, Topic, emqx_authz:init_rule(NRule)) of
true -> {matched, NPermission}; true -> {matched, NPermission};
false -> nomatch false -> nomatch
end. end.

View File

@ -74,7 +74,7 @@ match(Client, PubSub, Topic,
#{<<"simple_rule">> => Rule}, #{<<"simple_rule">> => Rule},
#{atom_key => true}, #{atom_key => true},
[simple_rule]), [simple_rule]),
case emqx_authz:match(Client, PubSub, Topic, emqx_authz:compile(NRule)) of case emqx_authz:match(Client, PubSub, Topic, emqx_authz:init_rule(NRule)) of
true -> {matched, allow}; true -> {matched, allow};
false -> nomatch false -> nomatch
end. end.

View File

@ -2,10 +2,6 @@
-include_lib("typerefl/include/types.hrl"). -include_lib("typerefl/include/types.hrl").
-type action() :: publish | subscribe | all.
-type permission() :: allow | deny.
-type url() :: emqx_http_lib:uri_map().
-reflect_type([ permission/0 -reflect_type([ permission/0
, action/0 , action/0
, url/0 , url/0
@ -13,6 +9,18 @@
-typerefl_from_string({url/0, emqx_http_lib, uri_parse}). -typerefl_from_string({url/0, emqx_http_lib, uri_parse}).
-type action() :: publish | subscribe | all.
-type permission() :: allow | deny.
-type url() :: #{
scheme := http | https,
host := string(),
port := non_neg_integer(),
path => string(),
query => string(),
fragment => string(),
userinfo => string()
}.
-export([ structs/0 -export([ structs/0
, fields/1 , fields/1
]). ]).
@ -51,9 +59,8 @@ fields(http_get) ->
end end
} }
} }
, {method, #{type => get, , {method, #{type => get, default => get }}
default => get , {request_timeout, #{type => timeout(), default => 30000 }}
}}
] ++ proplists:delete(base_url, emqx_connector_http:fields(config)); ] ++ proplists:delete(base_url, emqx_connector_http:fields(config));
fields(http_post) -> fields(http_post) ->
[ {url, #{type => url()}} [ {url, #{type => url()}}

View File

@ -29,23 +29,16 @@ groups() ->
[]. [].
init_per_suite(Config) -> init_per_suite(Config) ->
ok = emqx_ct_helpers:start_apps([emqx_authz], fun set_special_configs/1), ok = emqx_ct_helpers:start_apps([emqx_authz]),
ok = emqx_config:update_config([zones, default, acl, cache, enable], false),
ok = emqx_config:update_config([zones, default, acl, enable], true),
emqx_authz:update(replace, []),
Config. Config.
end_per_suite(_Config) -> end_per_suite(_Config) ->
file:delete(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf')), file:delete(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf')),
emqx_ct_helpers:stop_apps([emqx_authz]). emqx_ct_helpers:stop_apps([emqx_authz]).
set_special_configs(emqx) ->
application:set_env(emqx, allow_anonymous, true),
application:set_env(emqx, enable_acl_cache, false),
ok;
set_special_configs(emqx_authz) ->
emqx_config:put([emqx_authz], #{rules => []}),
ok;
set_special_configs(_App) ->
ok.
-define(RULE1, #{principal => all, -define(RULE1, #{principal => all,
topics => [<<"#">>], topics => [<<"#">>],
action => all, action => all,
@ -81,19 +74,19 @@ set_special_configs(_App) ->
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Testcases %% Testcases
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
t_compile(_) -> t_init_rule(_) ->
?assertEqual(#{permission => deny, ?assertEqual(#{permission => deny,
action => all, action => all,
principal => all, principal => all,
topics => [['#']] topics => [['#']]
},emqx_authz:compile(?RULE1)), }, emqx_authz:init_rule(?RULE1)),
?assertEqual(#{permission => allow, ?assertEqual(#{permission => allow,
action => all, action => all,
principal => principal =>
#{ipaddress => {{127,0,0,1},{127,0,0,1},32}}, #{ipaddress => {{127,0,0,1},{127,0,0,1},32}},
topics => [#{eq => ['#']}, topics => [#{eq => ['#']},
#{eq => ['+']}] #{eq => ['+']}]
}, emqx_authz:compile(?RULE2)), }, emqx_authz:init_rule(?RULE2)),
?assertMatch( ?assertMatch(
#{permission := allow, #{permission := allow,
action := publish, action := publish,
@ -103,7 +96,7 @@ t_compile(_) ->
] ]
}, },
topics := [[<<"test">>]] topics := [[<<"test">>]]
}, emqx_authz:compile(?RULE3)), }, emqx_authz:init_rule(?RULE3)),
?assertMatch( ?assertMatch(
#{permission := deny, #{permission := deny,
action := publish, action := publish,
@ -115,31 +108,39 @@ t_compile(_) ->
topics := [#{pattern := [<<"%u">>]}, topics := [#{pattern := [<<"%u">>]},
#{pattern := [<<"%c">>]} #{pattern := [<<"%c">>]}
] ]
}, emqx_authz:compile(?RULE4)), }, emqx_authz:init_rule(?RULE4)),
ok. ok.
t_authz(_) -> t_authz(_) ->
ClientInfo1 = #{clientid => <<"test">>, ClientInfo1 = #{clientid => <<"test">>,
username => <<"test">>, username => <<"test">>,
peerhost => {127,0,0,1} peerhost => {127,0,0,1},
zone => default,
listener => mqtt_tcp
}, },
ClientInfo2 = #{clientid => <<"test">>, ClientInfo2 = #{clientid => <<"test">>,
username => <<"test">>, username => <<"test">>,
peerhost => {192,168,0,10} peerhost => {192,168,0,10},
zone => default,
listener => mqtt_tcp
}, },
ClientInfo3 = #{clientid => <<"test">>, ClientInfo3 = #{clientid => <<"test">>,
username => <<"fake">>, username => <<"fake">>,
peerhost => {127,0,0,1} peerhost => {127,0,0,1},
zone => default,
listener => mqtt_tcp
}, },
ClientInfo4 = #{clientid => <<"fake">>, ClientInfo4 = #{clientid => <<"fake">>,
username => <<"test">>, username => <<"test">>,
peerhost => {127,0,0,1} peerhost => {127,0,0,1},
zone => default,
listener => mqtt_tcp
}, },
Rules1 = [emqx_authz:compile(Rule) || Rule <- [?RULE1, ?RULE2]], Rules1 = [emqx_authz:init_rule(Rule) || Rule <- [?RULE1, ?RULE2]],
Rules2 = [emqx_authz:compile(Rule) || Rule <- [?RULE2, ?RULE1]], Rules2 = [emqx_authz:init_rule(Rule) || Rule <- [?RULE2, ?RULE1]],
Rules3 = [emqx_authz:compile(Rule) || Rule <- [?RULE3, ?RULE4]], Rules3 = [emqx_authz:init_rule(Rule) || Rule <- [?RULE3, ?RULE4]],
Rules4 = [emqx_authz:compile(Rule) || Rule <- [?RULE4, ?RULE1]], Rules4 = [emqx_authz:init_rule(Rule) || Rule <- [?RULE4, ?RULE1]],
?assertEqual({stop, deny}, ?assertEqual({stop, deny},
emqx_authz:authorize(ClientInfo1, subscribe, <<"#">>, deny, [])), emqx_authz:authorize(ClientInfo1, subscribe, <<"#">>, deny, [])),

View File

@ -18,61 +18,66 @@
-compile(nowarn_export_all). -compile(nowarn_export_all).
-compile(export_all). -compile(export_all).
-include("emqx_authz.hrl"). % -include("emqx_authz.hrl").
-include_lib("eunit/include/eunit.hrl"). % -include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl"). % -include_lib("common_test/include/ct.hrl").
-import(emqx_ct_http, [ request_api/3 % -import(emqx_ct_http, [ request_api/3
, request_api/5 % , request_api/5
, get_http_data/1 % , get_http_data/1
, create_default_app/0 % , create_default_app/0
, delete_default_app/0 % , delete_default_app/0
, default_auth_header/0 % , default_auth_header/0
]). % ]).
-define(HOST, "http://127.0.0.1:8081/"). % -define(HOST, "http://127.0.0.1:8081/").
-define(API_VERSION, "v4"). % -define(API_VERSION, "v4").
-define(BASE_PATH, "api"). % -define(BASE_PATH, "api").
all() -> all() ->
%% TODO: V5 API %% TODO: V5 API
%% emqx_ct:all(?MODULE). %% emqx_ct:all(?MODULE).
[]. [t_api_unit_test].
groups() -> groups() ->
[]. [].
init_per_suite(Config) -> init_per_suite(Config) ->
ok = emqx_ct_helpers:start_apps([emqx_authz, emqx_management], fun set_special_configs/1), %% important! let emqx_schema include the current app!
create_default_app(), meck:new(emqx_schema, [non_strict, passthrough, no_history, no_link]),
meck:expect(emqx_schema, includes, fun() -> ["emqx_authz"] end ),
ok = emqx_ct_helpers:start_apps([emqx_authz]),
%create_default_app(),
Config. Config.
end_per_suite(_Config) -> end_per_suite(_Config) ->
delete_default_app(), %delete_default_app(),
file:delete(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf')), file:delete(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf')),
emqx_ct_helpers:stop_apps([emqx_authz, emqx_management]). meck:unload(emqx_schema),
emqx_ct_helpers:stop_apps([emqx_authz]).
set_special_configs(emqx) -> % set_special_configs(emqx) ->
application:set_env(emqx, allow_anonymous, true), % application:set_env(emqx, allow_anonymous, true),
application:set_env(emqx, enable_acl_cache, false), % application:set_env(emqx, enable_acl_cache, false),
ok; % ok;
set_special_configs(emqx_authz) -> % set_special_configs(emqx_authz) ->
emqx_config:put([emqx_authz], #{rules => []}), % emqx_config:put([emqx_authz], #{rules => []}),
ok; % ok;
set_special_configs(emqx_management) -> % set_special_configs(emqx_management) ->
emqx_config:put([emqx_management], #{listeners => [#{protocol => http, port => 8081}], % emqx_config:put([emqx_management], #{listeners => [#{protocol => http, port => 8081}],
applications =>[#{id => "admin", secret => "public"}]}), % applications =>[#{id => "admin", secret => "public"}]}),
ok; % ok;
set_special_configs(_App) -> % set_special_configs(_App) ->
ok. % ok.
%%------------------------------------------------------------------------------ % %%------------------------------------------------------------------------------
%% Testcases % %% Testcases
%%------------------------------------------------------------------------------ % %%------------------------------------------------------------------------------
t_api(_Config) -> t_api_unit_test(_Config) ->
Rule1 = #{<<"principal">> => Rule1 = #{<<"principal">> =>
#{<<"and">> => [#{<<"username">> => <<"^test?">>}, #{<<"and">> => [#{<<"username">> => <<"^test?">>},
#{<<"clientid">> => <<"^test?">>} #{<<"clientid">> => <<"^test?">>}
@ -81,53 +86,70 @@ t_api(_Config) ->
<<"topics">> => [<<"%u">>], <<"topics">> => [<<"%u">>],
<<"permission">> => <<"allow">> <<"permission">> => <<"allow">>
}, },
{ok, _} = request_http_rest_add(["authz/push"], #{rules => [Rule1]}), ok = emqx_authz_api:push_authz(#{}, Rule1),
{ok, Result1} = request_http_rest_lookup(["authz"]), [#{action := subscribe,
?assertMatch([Rule1 | _ ], get_http_data(Result1)), permission := allow,
principal :=
#{'and' := [#{username := <<"^test?">>},
#{clientid := <<"^test?">>}]},
topics := [<<"%u">>]}] = emqx_config:get([emqx_authz, rules]).
Rule2 = #{<<"principal">> => #{<<"ipaddress">> => <<"127.0.0.1">>}, % t_api(_Config) ->
<<"action">> => <<"publish">>, % Rule1 = #{<<"principal">> =>
<<"topics">> => [#{<<"eq">> => <<"#">>}, % #{<<"and">> => [#{<<"username">> => <<"^test?">>},
#{<<"eq">> => <<"+">>} % #{<<"clientid">> => <<"^test?">>}
], % ]},
<<"permission">> => <<"deny">> % <<"action">> => <<"subscribe">>,
}, % <<"topics">> => [<<"%u">>],
{ok, _} = request_http_rest_add(["authz/append"], #{rules => [Rule2]}), % <<"permission">> => <<"allow">>
{ok, Result2} = request_http_rest_lookup(["authz"]), % },
?assertEqual(Rule2#{<<"principal">> => #{<<"ipaddress">> => "127.0.0.1"}}, % {ok, _} = request_http_rest_add(["authz/push"], #{rules => [Rule1]}),
lists:last(get_http_data(Result2))), % {ok, Result1} = request_http_rest_lookup(["authz"]),
% ?assertMatch([Rule1 | _ ], get_http_data(Result1)),
{ok, _} = request_http_rest_update(["authz"], #{rules => []}), % Rule2 = #{<<"principal">> => #{<<"ipaddress">> => <<"127.0.0.1">>},
{ok, Result3} = request_http_rest_lookup(["authz"]), % <<"action">> => <<"publish">>,
?assertEqual([], get_http_data(Result3)), % <<"topics">> => [#{<<"eq">> => <<"#">>},
ok. % #{<<"eq">> => <<"+">>}
% ],
% <<"permission">> => <<"deny">>
% },
% {ok, _} = request_http_rest_add(["authz/append"], #{rules => [Rule2]}),
% {ok, Result2} = request_http_rest_lookup(["authz"]),
% ?assertEqual(Rule2#{<<"principal">> => #{<<"ipaddress">> => "127.0.0.1"}},
% lists:last(get_http_data(Result2))),
%%-------------------------------------------------------------------- % {ok, _} = request_http_rest_update(["authz"], #{rules => []}),
%% HTTP Request % {ok, Result3} = request_http_rest_lookup(["authz"]),
%%-------------------------------------------------------------------- % ?assertEqual([], get_http_data(Result3)),
% ok.
request_http_rest_list(Path) -> % %%--------------------------------------------------------------------
request_api(get, uri(Path), default_auth_header()). % %% HTTP Request
% %%--------------------------------------------------------------------
request_http_rest_lookup(Path) -> % request_http_rest_list(Path) ->
request_api(get, uri([Path]), default_auth_header()). % request_api(get, uri(Path), default_auth_header()).
request_http_rest_add(Path, Params) -> % request_http_rest_lookup(Path) ->
request_api(post, uri(Path), [], default_auth_header(), Params). % request_api(get, uri([Path]), default_auth_header()).
request_http_rest_update(Path, Params) -> % request_http_rest_add(Path, Params) ->
request_api(put, uri([Path]), [], default_auth_header(), Params). % request_api(post, uri(Path), [], default_auth_header(), Params).
request_http_rest_delete(Login) -> % request_http_rest_update(Path, Params) ->
request_api(delete, uri([Login]), default_auth_header()). % request_api(put, uri([Path]), [], default_auth_header(), Params).
uri() -> uri([]). % request_http_rest_delete(Login) ->
uri(Parts) when is_list(Parts) -> % request_api(delete, uri([Login]), default_auth_header()).
NParts = [b2l(E) || E <- Parts],
?HOST ++ filename:join([?BASE_PATH, ?API_VERSION | NParts]).
%% @private % uri() -> uri([]).
b2l(B) when is_binary(B) -> % uri(Parts) when is_list(Parts) ->
binary_to_list(B); % NParts = [b2l(E) || E <- Parts],
b2l(L) when is_list(L) -> % ?HOST ++ filename:join([?BASE_PATH, ?API_VERSION | NParts]).
L.
% %% @private
% b2l(B) when is_binary(B) ->
% binary_to_list(B);
% b2l(L) when is_list(L) ->
% L.

View File

@ -29,41 +29,33 @@ groups() ->
[]. [].
init_per_suite(Config) -> init_per_suite(Config) ->
%% important! let emqx_schema include the current app!
meck:new(emqx_schema, [non_strict, passthrough, no_history, no_link]),
meck:expect(emqx_schema, includes, fun() -> ["emqx_authz"] end ),
meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]), meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]),
meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ), meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ),
ok = emqx_ct_helpers:start_apps([emqx_authz], fun set_special_configs/1), ok = emqx_ct_helpers:start_apps([emqx_authz]),
ok = emqx_config:update_config([zones, default, acl, cache, enable], false),
ok = emqx_config:update_config([zones, default, acl, enable], true),
Rules = [#{ <<"config">> => #{
<<"url">> => <<"https://fake.com:443/">>,
<<"headers">> => #{},
<<"method">> => <<"get">>,
<<"request_timeout">> => 5000
},
<<"principal">> => <<"all">>,
<<"type">> => <<"http">>}
],
ok = emqx_authz:update(replace, Rules),
Config. Config.
end_per_suite(_Config) -> end_per_suite(_Config) ->
file:delete(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf')), file:delete(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf')),
emqx_ct_helpers:stop_apps([emqx_authz, emqx_resource]), emqx_ct_helpers:stop_apps([emqx_authz, emqx_resource]),
meck:unload(emqx_schema),
meck:unload(emqx_resource). meck:unload(emqx_resource).
set_special_configs(emqx) ->
application:set_env(emqx, allow_anonymous, true),
application:set_env(emqx, enable_acl_cache, false),
application:set_env(emqx, acl_nomatch, deny),
application:set_env(emqx, plugins_loaded_file,
emqx_ct_helpers:deps_path(emqx, "test/loaded_plguins")),
ok;
set_special_configs(emqx_authz) ->
Rules = [#{config =>#{
url => #{host => "fake.com",
path => "/",
port => 443,
scheme => https},
headers => #{},
method => get,
request_timeout => 5000
},
principal => all,
type => http}
],
emqx_config:put([emqx_authz], #{rules => Rules}),
ok;
set_special_configs(_App) ->
ok.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Testcases %% Testcases
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
@ -73,7 +65,9 @@ t_authz(_) ->
username => <<"username">>, username => <<"username">>,
peerhost => {127,0,0,1}, peerhost => {127,0,0,1},
protocol => mqtt, protocol => mqtt,
mountpoint => <<"fake">> mountpoint => <<"fake">>,
zone => default,
listener => mqtt_tcp
}, },
meck:expect(emqx_resource, query, fun(_, _) -> {ok, 204, fake_headers} end), meck:expect(emqx_resource, query, fun(_, _) -> {ok, 204, fake_headers} end),

View File

@ -31,33 +31,35 @@ groups() ->
init_per_suite(Config) -> init_per_suite(Config) ->
meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]), meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]),
meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ), meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ),
ok = emqx_ct_helpers:start_apps([emqx_authz], fun set_special_configs/1),
%% important! let emqx_schema include the current app!
meck:new(emqx_schema, [non_strict, passthrough, no_history, no_link]),
meck:expect(emqx_schema, includes, fun() -> ["emqx_authz"] end ),
ok = emqx_ct_helpers:start_apps([emqx_authz]),
ct:pal("---- emqx_hooks: ~p", [ets:tab2list(emqx_hooks)]),
ok = emqx_config:update_config([zones, default, acl, cache, enable], false),
ok = emqx_config:update_config([zones, default, acl, enable], true),
Rules = [#{ <<"config">> => #{
<<"mongo_type">> => <<"single">>,
<<"server">> => <<"127.0.0.1:27017">>,
<<"pool_size">> => 1,
<<"database">> => <<"mqtt">>,
<<"ssl">> => #{<<"enable">> => false}},
<<"principal">> => <<"all">>,
<<"collection">> => <<"fake">>,
<<"find">> => #{<<"a">> => <<"b">>},
<<"type">> => <<"mongo">>}
],
ok = emqx_authz:update(replace, Rules),
Config. Config.
end_per_suite(_Config) -> end_per_suite(_Config) ->
file:delete(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf')), file:delete(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf')),
emqx_ct_helpers:stop_apps([emqx_authz, emqx_resource]), emqx_ct_helpers:stop_apps([emqx_authz, emqx_resource]),
meck:unload(emqx_schema),
meck:unload(emqx_resource). meck:unload(emqx_resource).
set_special_configs(emqx) ->
application:set_env(emqx, allow_anonymous, true),
application:set_env(emqx, enable_acl_cache, false),
application:set_env(emqx, acl_nomatch, deny),
application:set_env(emqx, plugins_loaded_file,
emqx_ct_helpers:deps_path(emqx, "test/loaded_plguins")),
ok;
set_special_configs(emqx_authz) ->
Rules = [#{config =>#{},
principal => all,
collection => <<"fake">>,
find => #{<<"a">> => <<"b">>},
type => mongo}
],
emqx_config:put([emqx_authz], #{rules => Rules}),
ok;
set_special_configs(_App) ->
ok.
-define(RULE1,[#{<<"topics">> => [<<"#">>], -define(RULE1,[#{<<"topics">> => [<<"#">>],
<<"permission">> => <<"deny">>, <<"permission">> => <<"deny">>,
<<"action">> => <<"all">>}]). <<"action">> => <<"all">>}]).
@ -78,15 +80,21 @@ set_special_configs(_App) ->
t_authz(_) -> t_authz(_) ->
ClientInfo1 = #{clientid => <<"test">>, ClientInfo1 = #{clientid => <<"test">>,
username => <<"test">>, username => <<"test">>,
peerhost => {127,0,0,1} peerhost => {127,0,0,1},
zone => default,
listener => mqtt_tcp
}, },
ClientInfo2 = #{clientid => <<"test_clientid">>, ClientInfo2 = #{clientid => <<"test_clientid">>,
username => <<"test_username">>, username => <<"test_username">>,
peerhost => {192,168,0,10} peerhost => {192,168,0,10},
zone => default,
listener => mqtt_tcp
}, },
ClientInfo3 = #{clientid => <<"test_clientid">>, ClientInfo3 = #{clientid => <<"test_clientid">>,
username => <<"fake_username">>, username => <<"fake_username">>,
peerhost => {127,0,0,1} peerhost => {127,0,0,1},
zone => default,
listener => mqtt_tcp
}, },
meck:expect(emqx_resource, query, fun(_, _) -> [] end), meck:expect(emqx_resource, query, fun(_, _) -> [] end),

View File

@ -29,34 +29,36 @@ groups() ->
[]. [].
init_per_suite(Config) -> init_per_suite(Config) ->
%% important! let emqx_schema include the current app!
meck:new(emqx_schema, [non_strict, passthrough, no_history, no_link]),
meck:expect(emqx_schema, includes, fun() -> ["emqx_authz"] end ),
meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]), meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]),
meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ), meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ),
ok = emqx_ct_helpers:start_apps([emqx_authz], fun set_special_configs/1), ok = emqx_ct_helpers:start_apps([emqx_authz]),
ok = emqx_config:update_config([zones, default, acl, cache, enable], false),
ok = emqx_config:update_config([zones, default, acl, enable], true),
Rules = [#{ <<"config">> => #{
<<"server">> => <<"127.0.0.1:27017">>,
<<"pool_size">> => 1,
<<"database">> => <<"mqtt">>,
<<"username">> => <<"xx">>,
<<"password">> => <<"ee">>,
<<"auto_reconnect">> => true,
<<"ssl">> => #{<<"enable">> => false}
},
<<"principal">> => <<"all">>,
<<"sql">> => <<"abcb">>,
<<"type">> => <<"mysql">> }],
emqx_authz:update(replace, Rules),
Config. Config.
end_per_suite(_Config) -> end_per_suite(_Config) ->
file:delete(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf')), file:delete(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf')),
emqx_ct_helpers:stop_apps([emqx_authz, emqx_resource]), emqx_ct_helpers:stop_apps([emqx_authz, emqx_resource]),
meck:unload(emqx_schema),
meck:unload(emqx_resource). meck:unload(emqx_resource).
set_special_configs(emqx) ->
application:set_env(emqx, allow_anonymous, false),
application:set_env(emqx, enable_acl_cache, false),
application:set_env(emqx, acl_nomatch, deny),
application:set_env(emqx, plugins_loaded_file,
emqx_ct_helpers:deps_path(emqx, "test/loaded_plguins")),
ok;
set_special_configs(emqx_authz) ->
Rules = [#{config =>#{},
principal => all,
sql => <<"fake">>,
type => mysql}
],
emqx_config:put([emqx_authz], #{rules => Rules}),
ok;
set_special_configs(_App) ->
ok.
-define(COLUMNS, [ <<"ipaddress">> -define(COLUMNS, [ <<"ipaddress">>
, <<"username">> , <<"username">>
, <<"clientid">> , <<"clientid">>
@ -76,15 +78,21 @@ set_special_configs(_App) ->
t_authz(_) -> t_authz(_) ->
ClientInfo1 = #{clientid => <<"test">>, ClientInfo1 = #{clientid => <<"test">>,
username => <<"test">>, username => <<"test">>,
peerhost => {127,0,0,1} peerhost => {127,0,0,1},
zone => default,
listener => mqtt_tcp
}, },
ClientInfo2 = #{clientid => <<"test_clientid">>, ClientInfo2 = #{clientid => <<"test_clientid">>,
username => <<"test_username">>, username => <<"test_username">>,
peerhost => {192,168,0,10} peerhost => {192,168,0,10},
zone => default,
listener => mqtt_tcp
}, },
ClientInfo3 = #{clientid => <<"test_clientid">>, ClientInfo3 = #{clientid => <<"test_clientid">>,
username => <<"fake_username">>, username => <<"fake_username">>,
peerhost => {127,0,0,1} peerhost => {127,0,0,1},
zone => default,
listener => mqtt_tcp
}, },
meck:expect(emqx_resource, query, fun(_, _) -> {ok, ?COLUMNS, []} end), meck:expect(emqx_resource, query, fun(_, _) -> {ok, ?COLUMNS, []} end),

View File

@ -29,34 +29,35 @@ groups() ->
[]. [].
init_per_suite(Config) -> init_per_suite(Config) ->
%% important! let emqx_schema include the current app!
meck:new(emqx_schema, [non_strict, passthrough, no_history, no_link]),
meck:expect(emqx_schema, includes, fun() -> ["emqx_authz"] end ),
meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]), meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]),
meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ), meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ),
ok = emqx_ct_helpers:start_apps([emqx_authz], fun set_special_configs/1), ok = emqx_ct_helpers:start_apps([emqx_authz]),
ok = emqx_config:update_config([zones, default, acl, cache, enable], false),
ok = emqx_config:update_config([zones, default, acl, enable], true),
Rules = [#{ <<"config">> => #{
<<"server">> => <<"127.0.0.1:27017">>,
<<"pool_size">> => 1,
<<"database">> => <<"mqtt">>,
<<"username">> => <<"xx">>,
<<"password">> => <<"ee">>,
<<"auto_reconnect">> => true,
<<"ssl">> => #{<<"enable">> => false}
},
<<"sql">> => <<"abcb">>,
<<"type">> => <<"pgsql">> }],
emqx_authz:update(replace, Rules),
Config. Config.
end_per_suite(_Config) -> end_per_suite(_Config) ->
file:delete(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf')), file:delete(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf')),
emqx_ct_helpers:stop_apps([emqx_authz, emqx_resource]), emqx_ct_helpers:stop_apps([emqx_authz, emqx_resource]),
meck:unload(emqx_schema),
meck:unload(emqx_resource). meck:unload(emqx_resource).
set_special_configs(emqx) ->
application:set_env(emqx, allow_anonymous, false),
application:set_env(emqx, enable_acl_cache, false),
application:set_env(emqx, acl_nomatch, deny),
application:set_env(emqx, plugins_loaded_file,
emqx_ct_helpers:deps_path(emqx, "test/loaded_plguins")),
ok;
set_special_configs(emqx_authz) ->
Rules = [#{config =>#{},
principal => all,
sql => <<"fake">>,
type => pgsql}
],
emqx_config:put([emqx_authz], #{rules => Rules}),
ok;
set_special_configs(_App) ->
ok.
-define(COLUMNS, [ {column, <<"ipaddress">>, meck, meck, meck, meck, meck, meck, meck} -define(COLUMNS, [ {column, <<"ipaddress">>, meck, meck, meck, meck, meck, meck, meck}
, {column, <<"username">>, meck, meck, meck, meck, meck, meck, meck} , {column, <<"username">>, meck, meck, meck, meck, meck, meck, meck}
, {column, <<"clientid">>, meck, meck, meck, meck, meck, meck, meck} , {column, <<"clientid">>, meck, meck, meck, meck, meck, meck, meck}
@ -76,15 +77,21 @@ set_special_configs(_App) ->
t_authz(_) -> t_authz(_) ->
ClientInfo1 = #{clientid => <<"test">>, ClientInfo1 = #{clientid => <<"test">>,
username => <<"test">>, username => <<"test">>,
peerhost => {127,0,0,1} peerhost => {127,0,0,1},
zone => default,
listener => mqtt_tcp
}, },
ClientInfo2 = #{clientid => <<"test_clientid">>, ClientInfo2 = #{clientid => <<"test_clientid">>,
username => <<"test_username">>, username => <<"test_username">>,
peerhost => {192,168,0,10} peerhost => {192,168,0,10},
zone => default,
listener => mqtt_tcp
}, },
ClientInfo3 = #{clientid => <<"test_clientid">>, ClientInfo3 = #{clientid => <<"test_clientid">>,
username => <<"fake_username">>, username => <<"fake_username">>,
peerhost => {127,0,0,1} peerhost => {127,0,0,1},
zone => default,
listener => mqtt_tcp
}, },
meck:expect(emqx_resource, query, fun(_, _) -> {ok, ?COLUMNS, []} end), meck:expect(emqx_resource, query, fun(_, _) -> {ok, ?COLUMNS, []} end),

View File

@ -29,34 +29,34 @@ groups() ->
[]. [].
init_per_suite(Config) -> init_per_suite(Config) ->
%% important! let emqx_schema include the current app!
meck:new(emqx_schema, [non_strict, passthrough, no_history, no_link]),
meck:expect(emqx_schema, includes, fun() -> ["emqx_authz"] end ),
meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]), meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]),
meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ), meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ),
ok = emqx_ct_helpers:start_apps([emqx_authz], fun set_special_configs/1), ok = emqx_ct_helpers:start_apps([emqx_authz]),
ok = emqx_config:update_config([zones, default, acl, cache, enable], false),
ok = emqx_config:update_config([zones, default, acl, enable], true),
Rules = [#{ <<"config">> => #{
<<"server">> => <<"127.0.0.1:27017">>,
<<"pool_size">> => 1,
<<"database">> => 0,
<<"password">> => <<"ee">>,
<<"auto_reconnect">> => true,
<<"ssl">> => #{<<"enable">> => false}
},
<<"cmd">> => <<"HGETALL mqtt_acl:%u">>,
<<"type">> => <<"redis">> }],
emqx_authz:update(replace, Rules),
Config. Config.
end_per_suite(_Config) -> end_per_suite(_Config) ->
file:delete(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf')), file:delete(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf')),
emqx_ct_helpers:stop_apps([emqx_authz, emqx_resource]), emqx_ct_helpers:stop_apps([emqx_authz, emqx_resource]),
meck:unload(emqx_schema),
meck:unload(emqx_resource). meck:unload(emqx_resource).
set_special_configs(emqx) ->
application:set_env(emqx, allow_anonymous, true),
application:set_env(emqx, enable_acl_cache, false),
application:set_env(emqx, acl_nomatch, deny),
application:set_env(emqx, plugins_loaded_file,
emqx_ct_helpers:deps_path(emqx, "test/loaded_plguins")),
ok;
set_special_configs(emqx_authz) ->
Rules = [#{config =>#{},
principal => all,
cmd => <<"fake">>,
type => redis}
],
emqx_config:put([emqx_authz], #{rules => Rules}),
ok;
set_special_configs(_App) ->
ok.
-define(RULE1, [<<"test/%u">>, <<"publish">>]). -define(RULE1, [<<"test/%u">>, <<"publish">>]).
-define(RULE2, [<<"test/%c">>, <<"publish">>]). -define(RULE2, [<<"test/%c">>, <<"publish">>]).
-define(RULE3, [<<"#">>, <<"subscribe">>]). -define(RULE3, [<<"#">>, <<"subscribe">>]).
@ -68,7 +68,9 @@ set_special_configs(_App) ->
t_authz(_) -> t_authz(_) ->
ClientInfo = #{clientid => <<"clientid">>, ClientInfo = #{clientid => <<"clientid">>,
username => <<"username">>, username => <<"username">>,
peerhost => {127,0,0,1} peerhost => {127,0,0,1},
zone => default,
listener => mqtt_tcp
}, },
meck:expect(emqx_resource, query, fun(_, _) -> {ok, []} end), meck:expect(emqx_resource, query, fun(_, _) -> {ok, []} end),

View File

@ -34,7 +34,10 @@ stop(_State) ->
handle_update_config({update, Bridge = #{<<"name">> := Name}}, OldConf) -> handle_update_config({update, Bridge = #{<<"name">> := Name}}, OldConf) ->
[Bridge | remove_bridge(Name, OldConf)]; [Bridge | remove_bridge(Name, OldConf)];
handle_update_config({delete, Name}, OldConf) -> handle_update_config({delete, Name}, OldConf) ->
remove_bridge(Name, OldConf). remove_bridge(Name, OldConf);
handle_update_config(NewConf, _OldConf) when is_list(NewConf) ->
%% overwrite the entire config!
NewConf.
remove_bridge(_Name, undefined) -> remove_bridge(_Name, undefined) ->
[]; [];

View File

@ -29,46 +29,46 @@ emqx_gateway: {
} }
} }
mqttsn.1: { # mqttsn.1: {
## The MQTT-SN Gateway ID in ADVERTISE message. # ## The MQTT-SN Gateway ID in ADVERTISE message.
gateway_id: 1 # gateway_id: 1
## Enable broadcast this gateway to WLAN # ## Enable broadcast this gateway to WLAN
broadcast: true # broadcast: true
## To control whether write statistics data into ETS table # ## To control whether write statistics data into ETS table
## for dashbord to read. # ## for dashbord to read.
enable_stats: true # enable_stats: true
## To control whether accept and process the received # ## To control whether accept and process the received
## publish message with qos=-1. # ## publish message with qos=-1.
enable_qos3: true # enable_qos3: true
## Idle timeout for a MQTT-SN channel # ## Idle timeout for a MQTT-SN channel
idle_timeout: 30s # idle_timeout: 30s
## The pre-defined topic name corresponding to the pre-defined topic # ## The pre-defined topic name corresponding to the pre-defined topic
## id of N. # ## id of N.
## Note that the pre-defined topic id of 0 is reserved. # ## Note that the pre-defined topic id of 0 is reserved.
predefined: [ # predefined: [
{ id: 1 # { id: 1
topic: "/predefined/topic/name/hello" # topic: "/predefined/topic/name/hello"
}, # },
{ id: 2 # { id: 2
topic: "/predefined/topic/name/nice" # topic: "/predefined/topic/name/nice"
} # }
] # ]
### ClientInfo override # ### ClientInfo override
clientinfo_override: { # clientinfo_override: {
username: "mqtt_sn_user" # username: "mqtt_sn_user"
password: "abc" # password: "abc"
} # }
listener.udp.1: { # listener.udp.1: {
bind: 1884 # bind: 1884
max_connections: 10240000 # max_connections: 10240000
max_conn_rate: 1000 # max_conn_rate: 1000
} # }
} # }
} }

View File

@ -371,7 +371,8 @@ clientinfo(#state{peername = {PeerHost, _},
clientid = ClientId, clientid = ClientId,
username = Username, username = Username,
password = Password}) -> password = Password}) ->
#{zone => undefined, #{zone => default,
listener => mqtt_tcp, %% FIXME: this won't work
protocol => coap, protocol => coap,
peerhost => PeerHost, peerhost => PeerHost,
sockport => 5683, %% FIXME: sockport => 5683, %% FIXME:

View File

@ -39,7 +39,10 @@ unload() ->
lists:foreach(fun(Cmd) -> emqx_ctl:unregister_command(Cmd) end, Cmds). lists:foreach(fun(Cmd) -> emqx_ctl:unregister_command(Cmd) end, Cmds).
is_cmd(Fun) -> is_cmd(Fun) ->
not lists:member(Fun, [init, load, module_info]). case atom_to_list(Fun) of
"gateway" ++ _ -> true;
_ -> false
end.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------

View File

@ -68,7 +68,8 @@
| {error, any()}. | {error, any()}.
authenticate(_Ctx = #{auth := ChainId}, ClientInfo0) -> authenticate(_Ctx = #{auth := ChainId}, ClientInfo0) ->
ClientInfo = ClientInfo0#{ ClientInfo = ClientInfo0#{
zone => undefined, zone => default,
listener => mqtt_tcp,
chain_id => ChainId chain_id => ChainId
}, },
case emqx_access_control:authenticate(ClientInfo) of case emqx_access_control:authenticate(ClientInfo) of

View File

@ -8,13 +8,11 @@
-include_lib("typerefl/include/types.hrl"). -include_lib("typerefl/include/types.hrl").
-type flag() :: true | false.
-type duration() :: integer(). -type duration() :: integer().
-type bytesize() :: integer(). -type bytesize() :: integer().
-type comma_separated_list() :: list(). -type comma_separated_list() :: list().
-type ip_port() :: tuple(). -type ip_port() :: tuple().
-typerefl_from_string({flag/0, emqx_schema, to_flag}).
-typerefl_from_string({duration/0, emqx_schema, to_duration}). -typerefl_from_string({duration/0, emqx_schema, to_duration}).
-typerefl_from_string({bytesize/0, emqx_schema, to_bytesize}). -typerefl_from_string({bytesize/0, emqx_schema, to_bytesize}).
-typerefl_from_string({comma_separated_list/0, emqx_schema, to_comma_separated_list}). -typerefl_from_string({comma_separated_list/0, emqx_schema, to_comma_separated_list}).
@ -22,8 +20,7 @@
-behaviour(hocon_schema). -behaviour(hocon_schema).
-reflect_type([ flag/0 -reflect_type([ duration/0
, duration/0
, bytesize/0 , bytesize/0
, comma_separated_list/0 , comma_separated_list/0
, ip_port/0 , ip_port/0
@ -114,16 +111,16 @@ fields(listener_settings) ->
%, {zone, t(string())} %, {zone, t(string())}
%, {rate_limit, t(comma_separated_list())} %, {rate_limit, t(comma_separated_list())}
, {access, t(ref(access))} , {access, t(ref(access))}
, {proxy_protocol, t(flag())} , {proxy_protocol, t(boolean())}
, {proxy_protocol_timeout, t(duration())} , {proxy_protocol_timeout, t(duration())}
, {backlog, t(integer(), undefined, 1024)} , {backlog, t(integer(), undefined, 1024)}
, {send_timeout, t(duration(), undefined, "15s")} , {send_timeout, t(duration(), undefined, "15s")}
, {send_timeout_close, t(flag(), undefined, true)} , {send_timeout_close, t(boolean(), undefined, true)}
, {recbuf, t(bytesize())} , {recbuf, t(bytesize())}
, {sndbuf, t(bytesize())} , {sndbuf, t(bytesize())}
, {buffer, t(bytesize())} , {buffer, t(bytesize())}
, {high_watermark, t(bytesize(), undefined, "1MB")} , {high_watermark, t(bytesize(), undefined, "1MB")}
, {tune_buffer, t(flag())} , {tune_buffer, t(boolean())}
, {nodelay, t(boolean())} , {nodelay, t(boolean())}
, {reuseaddr, t(boolean())} , {reuseaddr, t(boolean())}
]; ];
@ -202,15 +199,15 @@ ssl(Mapping, Defaults) ->
_ -> Mapping ++ "." ++ Field _ -> Mapping ++ "." ++ Field
end end, end end,
D = fun (Field) -> maps:get(list_to_atom(Field), Defaults, undefined) end, D = fun (Field) -> maps:get(list_to_atom(Field), Defaults, undefined) end,
[ {"enable", t(flag(), M("enable"), D("enable"))} [ {"enable", t(boolean(), M("enable"), D("enable"))}
, {"cacertfile", t(string(), M("cacertfile"), D("cacertfile"))} , {"cacertfile", t(string(), M("cacertfile"), D("cacertfile"))}
, {"certfile", t(string(), M("certfile"), D("certfile"))} , {"certfile", t(string(), M("certfile"), D("certfile"))}
, {"keyfile", t(string(), M("keyfile"), D("keyfile"))} , {"keyfile", t(string(), M("keyfile"), D("keyfile"))}
, {"verify", t(union(verify_peer, verify_none), M("verify"), D("verify"))} , {"verify", t(union(verify_peer, verify_none), M("verify"), D("verify"))}
, {"fail_if_no_peer_cert", t(boolean(), M("fail_if_no_peer_cert"), D("fail_if_no_peer_cert"))} , {"fail_if_no_peer_cert", t(boolean(), M("fail_if_no_peer_cert"), D("fail_if_no_peer_cert"))}
, {"secure_renegotiate", t(flag(), M("secure_renegotiate"), D("secure_renegotiate"))} , {"secure_renegotiate", t(boolean(), M("secure_renegotiate"), D("secure_renegotiate"))}
, {"reuse_sessions", t(flag(), M("reuse_sessions"), D("reuse_sessions"))} , {"reuse_sessions", t(boolean(), M("reuse_sessions"), D("reuse_sessions"))}
, {"honor_cipher_order", t(flag(), M("honor_cipher_order"), D("honor_cipher_order"))} , {"honor_cipher_order", t(boolean(), M("honor_cipher_order"), D("honor_cipher_order"))}
, {"handshake_timeout", t(duration(), M("handshake_timeout"), D("handshake_timeout"))} , {"handshake_timeout", t(duration(), M("handshake_timeout"), D("handshake_timeout"))}
, {"depth", t(integer(), M("depth"), D("depth"))} , {"depth", t(integer(), M("depth"), D("depth"))}
, {"password", hoconsc:t(string(), #{mapping => M("key_password"), , {"password", hoconsc:t(string(), #{mapping => M("key_password"),

View File

@ -219,7 +219,8 @@ send(Data, #state{socket = {esockd_transport, Sock}}) ->
-define(DEFAULT_GC_OPTS, #{count => 1000, bytes => 1024*1024}). -define(DEFAULT_GC_OPTS, #{count => 1000, bytes => 1024*1024}).
-define(DEFAULT_IDLE_TIMEOUT, 30000). -define(DEFAULT_IDLE_TIMEOUT, 30000).
-define(DEFAULT_OOM_POLICY, #{max_heap_size => 4194304,message_queue_len => 32000}). -define(DEFAULT_OOM_POLICY, #{enable => true, max_heap_size => 4194304,
max_message_queue_len => 32000}).
init(Parent, WrappedSock, Peername, Options) -> init(Parent, WrappedSock, Peername, Options) ->
case esockd_wait(WrappedSock) of case esockd_wait(WrappedSock) of

View File

@ -440,7 +440,8 @@ take_place(Text, Placeholder, Value) ->
clientinfo(#lwm2m_state{peername = {PeerHost, _}, clientinfo(#lwm2m_state{peername = {PeerHost, _},
endpoint_name = EndpointName, endpoint_name = EndpointName,
mountpoint = Mountpoint}) -> mountpoint = Mountpoint}) ->
#{zone => undefined, #{zone => default,
listener => mqtt_tcp, %% FIXME: this won't work
protocol => lwm2m, protocol => lwm2m,
peerhost => PeerHost, peerhost => PeerHost,
sockport => 5683, %% FIXME: sockport => 5683, %% FIXME:

View File

@ -104,7 +104,7 @@
-define(STAT_TIMEOUT, 10000). -define(STAT_TIMEOUT, 10000).
-define(IDLE_TIMEOUT, 30000). -define(IDLE_TIMEOUT, 30000).
-define(DEFAULT_CHAN_OPTIONS, [{max_packet_size, 256}, {zone, external}]). -define(DEFAULT_CHAN_OPTIONS, #{zone => default, listener => mqtt_tcp}).
-define(NEG_QOS_CLIENT_ID, <<"NegQoS-Client">>). -define(NEG_QOS_CLIENT_ID, <<"NegQoS-Client">>).

View File

@ -113,7 +113,8 @@ init(ConnInfo = #{peername := {PeerHost, _},
Mountpoint = maps:get(mountpoint, Option, undefined), Mountpoint = maps:get(mountpoint, Option, undefined),
ClientInfo = setting_peercert_infos( ClientInfo = setting_peercert_infos(
Peercert, Peercert,
#{ zone => undefined #{ zone => default
, listener => mqtt_tcp
, protocol => stomp , protocol => stomp
, peerhost => PeerHost , peerhost => PeerHost
, sockport => SockPort , sockport => SockPort
@ -583,7 +584,8 @@ handle_call(discard, Channel) ->
% shutdown_and_reply(takeovered, AllPendings, Channel); % shutdown_and_reply(takeovered, AllPendings, Channel);
handle_call(list_acl_cache, Channel) -> handle_call(list_acl_cache, Channel) ->
{reply, emqx_acl_cache:list_acl_cache(), Channel}; %% This won't work
{reply, emqx_acl_cache:list_acl_cache(default, mqtt_tcp), Channel};
%% XXX: No Quota Now %% XXX: No Quota Now
% handle_call({quota, Policy}, Channel) -> % handle_call({quota, Policy}, Channel) ->

View File

@ -245,7 +245,9 @@ init_state(Transport, Socket, Options) ->
peername => Peername, peername => Peername,
sockname => Sockname, sockname => Sockname,
peercert => Peercert, peercert => Peercert,
conn_mod => ?MODULE conn_mod => ?MODULE,
zone => default,
listener => mqtt_tcp
}, },
ActiveN = emqx_gateway_utils:active_n(Options), ActiveN = emqx_gateway_utils:active_n(Options),
%% TODO: RateLimit ? How ? %% TODO: RateLimit ? How ?

View File

@ -14,7 +14,7 @@ emqx_management:{
port: 8081 port: 8081
backlog: 512 backlog: 512
send_timeout: 15s send_timeout: 15s
send_timeout_close: on send_timeout_close: true
inet6: false inet6: false
ipv6_v6only: false ipv6_v6only: false
} }
@ -25,7 +25,7 @@ emqx_management:{
## acceptors: 2 ## acceptors: 2
## backlog: 512 ## backlog: 512
## send_timeout: 15s ## send_timeout: 15s
## send_timeout_close: on ## send_timeout_close: true
## inet6: false ## inet6: false
## ipv6_v6only: false ## ipv6_v6only: false
## certfile = "etc/certs/cert.pem" ## certfile = "etc/certs/cert.pem"

View File

@ -43,13 +43,13 @@ fields("http") ->
, {"max_connections", emqx_schema:t(integer(), undefined, 512)} , {"max_connections", emqx_schema:t(integer(), undefined, 512)}
, {"backlog", emqx_schema:t(integer(), undefined, 1024)} , {"backlog", emqx_schema:t(integer(), undefined, 1024)}
, {"send_timeout", emqx_schema:t(emqx_schema:duration(), undefined, "15s")} , {"send_timeout", emqx_schema:t(emqx_schema:duration(), undefined, "15s")}
, {"send_timeout_close", emqx_schema:t(emqx_schema:flag(), undefined, true)} , {"send_timeout_close", emqx_schema:t(boolean(), undefined, true)}
, {"inet6", emqx_schema:t(boolean(), undefined, false)} , {"inet6", emqx_schema:t(boolean(), undefined, false)}
, {"ipv6_v6only", emqx_schema:t(boolean(), undefined, false)} , {"ipv6_v6only", emqx_schema:t(boolean(), undefined, false)}
]; ];
fields("https") -> fields("https") ->
emqx_schema:ssl(undefined, #{enable => true}) ++ fields("http"). emqx_schema:ssl(#{enable => true}) ++ fields("http").
max_row_limit(type) -> integer(); max_row_limit(type) -> integer();
max_row_limit(default) -> 1000; max_row_limit(default) -> 1000;

View File

@ -457,7 +457,7 @@ list_listeners(Node) when Node =:= node() ->
Tcp = lists:map(fun({{Protocol, ListenOn}, _Pid}) -> Tcp = lists:map(fun({{Protocol, ListenOn}, _Pid}) ->
#{protocol => Protocol, #{protocol => Protocol,
listen_on => ListenOn, listen_on => ListenOn,
identifier => emqx_listeners:find_id_by_listen_on(ListenOn), identifier => Protocol,
acceptors => esockd:get_acceptors({Protocol, ListenOn}), acceptors => esockd:get_acceptors({Protocol, ListenOn}),
max_conns => esockd:get_max_connections({Protocol, ListenOn}), max_conns => esockd:get_max_connections({Protocol, ListenOn}),
current_conns => esockd:get_current_connections({Protocol, ListenOn}), current_conns => esockd:get_current_connections({Protocol, ListenOn}),
@ -476,6 +476,7 @@ list_listeners(Node) when Node =:= node() ->
list_listeners(Node) -> list_listeners(Node) ->
rpc_call(Node, list_listeners, [Node]). rpc_call(Node, list_listeners, [Node]).
-spec restart_listener(node(), atom()) -> ok | {error, term()}.
restart_listener(Node, Identifier) when Node =:= node() -> restart_listener(Node, Identifier) when Node =:= node() ->
emqx_listeners:restart_listener(Identifier); emqx_listeners:restart_listener(Identifier);

View File

@ -30,13 +30,13 @@
-rest_api(#{name => restart_listener, -rest_api(#{name => restart_listener,
method => 'PUT', method => 'PUT',
path => "/listeners/:bin:identifier/restart", path => "/listeners/:atom:identifier/restart",
func => restart, func => restart,
descr => "Restart a listener in the cluster"}). descr => "Restart a listener in the cluster"}).
-rest_api(#{name => restart_node_listener, -rest_api(#{name => restart_node_listener,
method => 'PUT', method => 'PUT',
path => "/nodes/:atom:node/listeners/:bin:identifier/restart", path => "/nodes/:atom:node/listeners/:atom:identifier/restart",
func => restart, func => restart,
descr => "Restart a listener on a node"}). descr => "Restart a listener on a node"}).
@ -57,10 +57,7 @@ restart(#{node := Node, identifier := Identifier}, _Params) ->
ok -> emqx_mgmt:return({ok, "Listener restarted."}); ok -> emqx_mgmt:return({ok, "Listener restarted."});
{error, Error} -> emqx_mgmt:return({error, Error}) {error, Error} -> emqx_mgmt:return({error, Error})
end; end;
%% Restart listeners on all nodes in the cluster.
%% Restart listeners in the cluster.
restart(#{identifier := <<"http", _/binary>>}, _Params) ->
{403, <<"http_listener_restart_unsupported">>};
restart(#{identifier := Identifier}, _Params) -> restart(#{identifier := Identifier}, _Params) ->
Results = [{Node, emqx_mgmt:restart_listener(Node, Identifier)} || {Node, _Info} <- emqx_mgmt:list_nodes()], Results = [{Node, emqx_mgmt:restart_listener(Node, Identifier)} || {Node, _Info} <- emqx_mgmt:list_nodes()],
case lists:filter(fun({_, Result}) -> Result =/= ok end, Results) of case lists:filter(fun({_, Result}) -> Result =/= ok end, Results) of

View File

@ -463,86 +463,57 @@ trace_off(Who, Name) ->
listeners([]) -> listeners([]) ->
lists:foreach(fun({{Protocol, ListenOn}, _Pid}) -> lists:foreach(fun({{Protocol, ListenOn}, _Pid}) ->
Info = [{listen_on, {string, emqx_listeners:format_listen_on(ListenOn)}}, Info = [{listen_on, {string, format_listen_on(ListenOn)}},
{acceptors, esockd:get_acceptors({Protocol, ListenOn})}, {acceptors, esockd:get_acceptors({Protocol, ListenOn})},
{max_conns, esockd:get_max_connections({Protocol, ListenOn})}, {max_conns, esockd:get_max_connections({Protocol, ListenOn})},
{current_conn, esockd:get_current_connections({Protocol, ListenOn})}, {current_conn, esockd:get_current_connections({Protocol, ListenOn})},
{shutdown_count, esockd:get_shutdown_count({Protocol, ListenOn})} {shutdown_count, esockd:get_shutdown_count({Protocol, ListenOn})}
], ],
emqx_ctl:print("~s~n", [listener_identifier(Protocol, ListenOn)]), emqx_ctl:print("~s~n", [Protocol]),
lists:foreach(fun indent_print/1, Info) lists:foreach(fun indent_print/1, Info)
end, esockd:listeners()), end, esockd:listeners()),
lists:foreach(fun({Protocol, Opts}) -> lists:foreach(fun({Protocol, Opts}) ->
Port = proplists:get_value(port, Opts), Port = proplists:get_value(port, Opts),
Info = [{listen_on, {string, emqx_listeners:format_listen_on(Port)}}, Info = [{listen_on, {string, format_listen_on(Port)}},
{acceptors, maps:get(num_acceptors, proplists:get_value(transport_options, Opts, #{}), 0)}, {acceptors, maps:get(num_acceptors, proplists:get_value(transport_options, Opts, #{}), 0)},
{max_conns, proplists:get_value(max_connections, Opts)}, {max_conns, proplists:get_value(max_connections, Opts)},
{current_conn, proplists:get_value(all_connections, Opts)}, {current_conn, proplists:get_value(all_connections, Opts)},
{shutdown_count, []}], {shutdown_count, []}],
emqx_ctl:print("~s~n", [listener_identifier(Protocol, Port)]), emqx_ctl:print("~s~n", [Protocol]),
lists:foreach(fun indent_print/1, Info) lists:foreach(fun indent_print/1, Info)
end, ranch:info()); end, ranch:info());
listeners(["stop", Name = "http" ++ _N | _MaybePort]) -> listeners(["stop", ListenerId]) ->
%% _MaybePort is to be backward compatible, to stop http listener, there is no need for the port number case emqx_listeners:stop_listener(list_to_atom(ListenerId)) of
case minirest:stop(list_to_atom(Name)) of
ok -> ok ->
emqx_ctl:print("Stop ~s listener successfully.~n", [Name]); emqx_ctl:print("Stop ~s listener successfully.~n", [ListenerId]);
{error, Error} -> {error, Error} ->
emqx_ctl:print("Failed to stop ~s listener: ~0p~n", [Name, Error]) emqx_ctl:print("Failed to stop ~s listener: ~0p~n", [ListenerId, Error])
end; end;
listeners(["stop", "mqtt:" ++ _ = Identifier]) -> listeners(["start", ListenerId]) ->
stop_listener(emqx_listeners:find_by_id(Identifier), Identifier); case emqx_listeners:start_listener(list_to_atom(ListenerId)) of
listeners(["stop", _Proto, ListenOn]) ->
%% this clause is kept to be backward compatible
ListenOn1 = case string:tokens(ListenOn, ":") of
[Port] -> list_to_integer(Port);
[IP, Port] -> {IP, list_to_integer(Port)}
end,
stop_listener(emqx_listeners:find_by_listen_on(ListenOn1), ListenOn1);
listeners(["restart", "http:management"]) ->
restart_http_listener(http, emqx_management);
listeners(["restart", "https:management"]) ->
restart_http_listener(https, emqx_management);
listeners(["restart", "http:dashboard"]) ->
restart_http_listener(http, emqx_dashboard);
listeners(["restart", "https:dashboard"]) ->
restart_http_listener(https, emqx_dashboard);
listeners(["restart", Identifier]) ->
case emqx_listeners:restart_listener(Identifier) of
ok -> ok ->
emqx_ctl:print("Restarted ~s listener successfully.~n", [Identifier]); emqx_ctl:print("Started ~s listener successfully.~n", [ListenerId]);
{error, Error} -> {error, Error} ->
emqx_ctl:print("Failed to restart ~s listener: ~0p~n", [Identifier, Error]) emqx_ctl:print("Failed to start ~s listener: ~0p~n", [ListenerId, Error])
end;
listeners(["restart", ListenerId]) ->
case emqx_listeners:restart_listener(list_to_atom(ListenerId)) of
ok ->
emqx_ctl:print("Restarted ~s listener successfully.~n", [ListenerId]);
{error, Error} ->
emqx_ctl:print("Failed to restart ~s listener: ~0p~n", [ListenerId, Error])
end; end;
listeners(_) -> listeners(_) ->
emqx_ctl:usage([{"listeners", "List listeners"}, emqx_ctl:usage([{"listeners", "List listeners"},
{"listeners stop <Identifier>", "Stop a listener"}, {"listeners stop <Identifier>", "Stop a listener"},
{"listeners stop <Proto> <Port>", "Stop a listener"}, {"listeners start <Identifier>", "Start a listener"},
{"listeners restart <Identifier>", "Restart a listener"} {"listeners restart <Identifier>", "Restart a listener"}
]). ]).
stop_listener(false, Input) ->
emqx_ctl:print("No such listener ~p~n", [Input]);
stop_listener(#{listen_on := ListenOn} = Listener, _Input) ->
ID = emqx_listeners:identifier(Listener),
ListenOnStr = emqx_listeners:format_listen_on(ListenOn),
case emqx_listeners:stop_listener(Listener) of
ok ->
emqx_ctl:print("Stop ~s listener on ~s successfully.~n", [ID, ListenOnStr]);
{error, Reason} ->
emqx_ctl:print("Failed to stop ~s listener on ~s: ~0p~n",
[ID, ListenOnStr, Reason])
end.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc acl Command %% @doc acl Command
@ -661,24 +632,9 @@ indent_print({Key, {string, Val}}) ->
indent_print({Key, Val}) -> indent_print({Key, Val}) ->
emqx_ctl:print(" ~-16s: ~w~n", [Key, Val]). emqx_ctl:print(" ~-16s: ~w~n", [Key, Val]).
listener_identifier(Protocol, ListenOn) -> format_listen_on(Port) when is_integer(Port) ->
case emqx_listeners:find_id_by_listen_on(ListenOn) of io_lib:format("0.0.0.0:~w", [Port]);
false -> format_listen_on({Addr, Port}) when is_list(Addr) ->
atom_to_list(Protocol); io_lib:format("~s:~w", [Addr, Port]);
ID -> format_listen_on({Addr, Port}) when is_tuple(Addr) ->
ID io_lib:format("~s:~w", [inet:ntoa(Addr), Port]).
end.
restart_http_listener(Scheme, AppName) ->
Listeners = application:get_env(AppName, listeners, []),
case lists:keyfind(Scheme, 1, Listeners) of
false ->
emqx_ctl:print("Listener ~s not exists!~n", [AppName]);
{Scheme, Port, Options} ->
ModName = http_mod_name(AppName),
ModName:stop_listener({Scheme, Port, Options}),
ModName:start_listener({Scheme, Port, Options})
end.
http_mod_name(emqx_management) -> emqx_mgmt_http;
http_mod_name(Name) -> Name.

View File

@ -20,24 +20,6 @@
-define(SERVER, "http://127.0.0.1:8081"). -define(SERVER, "http://127.0.0.1:8081").
-define(BASE_PATH, "/api/v5"). -define(BASE_PATH, "/api/v5").
default_init() ->
ekka_mnesia:start(),
emqx_mgmt_auth:mnesia(boot),
emqx_ct_helpers:start_apps([emqx_management], fun set_special_configs/1),
ok.
default_end() ->
emqx_ct_helpers:stop_apps([emqx_management]).
set_special_configs(emqx_management) ->
emqx_config:put([emqx_management], #{listeners => [#{protocol => http, port => 8081}],
applications =>[#{id => "admin", secret => "public"}]}),
ok;
set_special_configs(_App) ->
ok.
request_api(Method, Url) -> request_api(Method, Url) ->
request_api(Method, Url, [], auth_header_(), []). request_api(Method, Url, [], auth_header_(), []).

View File

@ -25,11 +25,20 @@ all() ->
emqx_ct:all(?MODULE). emqx_ct:all(?MODULE).
init_per_suite(Config) -> init_per_suite(Config) ->
emqx_mgmt_api_test_util:default_init(), ekka_mnesia:start(),
emqx_mgmt_auth:mnesia(boot),
emqx_ct_helpers:start_apps([emqx_management], fun set_special_configs/1),
Config. Config.
end_per_suite(_) -> end_per_suite(_) ->
emqx_mgmt_api_test_util:default_end(). emqx_ct_helpers:stop_apps([emqx_management]).
set_special_configs(emqx_management) ->
emqx_config:put([emqx_management], #{listeners => [#{protocol => http, port => 8081}],
applications =>[#{id => "admin", secret => "public"}]}),
ok;
set_special_configs(_App) ->
ok.
t_clients(_) -> t_clients(_) ->
process_flag(trap_exit, true), process_flag(trap_exit, true),

View File

@ -112,7 +112,7 @@ dispatch(Context, Pid, Topic, Cursor) ->
case Cursor =/= undefined orelse emqx_topic:wildcard(Topic) of case Cursor =/= undefined orelse emqx_topic:wildcard(Topic) of
false -> false ->
{ok, Result} = Mod:read_message(Context, Topic), {ok, Result} = Mod:read_message(Context, Topic),
deliver(Result, Context, Pid, Topic, undefiend); deliver(Result, Context, Pid, Topic, undefined);
true -> true ->
{ok, Result, NewCursor} = Mod:match_messages(Context, Topic, Cursor), {ok, Result, NewCursor} = Mod:match_messages(Context, Topic, Cursor),
deliver(Result, Context, Pid, Topic, NewCursor) deliver(Result, Context, Pid, Topic, NewCursor)

View File

@ -50,7 +50,7 @@ init_per_suite(Config) ->
end_per_suite(_Config) -> end_per_suite(_Config) ->
delete_default_app(), delete_default_app(),
emqx_ct_helpers:stop_apps([emqx_retainer]). emqx_ct_helpers:stop_apps([emqx_management, emqx_retainer]).
init_per_testcase(_, Config) -> init_per_testcase(_, Config) ->
Config. Config.

View File

@ -205,6 +205,7 @@ relx_nodetool() {
call_hocon() { call_hocon() {
export RUNNER_ROOT_DIR export RUNNER_ROOT_DIR
export RUNNER_ETC_DIR
export REL_VSN export REL_VSN
"$ERTS_DIR/bin/escript" "$ROOTDIR/bin/nodetool" hocon "$@" \ "$ERTS_DIR/bin/escript" "$ROOTDIR/bin/nodetool" hocon "$@" \
|| die "ERROR: call_hocon failed: $*" $? || die "ERROR: call_hocon failed: $*" $?
@ -592,7 +593,7 @@ case "$1" in
# set before generate_config # set before generate_config
if [ "${_EMQX_START_MODE:-}" = '' ]; then if [ "${_EMQX_START_MODE:-}" = '' ]; then
export EMQX_LOG__TO="${EMQX_LOG__TO:-console}" export EMQX_LOG__CONSOLE_HANDLER__ENABLE="${EMQX_LOG__CONSOLE_HANDLER__ENABLE:-true}"
fi fi
#generate app.config and vm.args #generate app.config and vm.args
@ -636,7 +637,7 @@ case "$1" in
# or other supervision services # or other supervision services
# set before generate_config # set before generate_config
export EMQX_LOG__TO="${EMQX_LOG__TO:-console}" export EMQX_LOG__CONSOLE_HANDLER__ENABLE="${EMQX_LOG__CONSOLE_HANDLER__ENABLE:-true}"
#generate app.config and vm.args #generate app.config and vm.args
generate_config generate_config

View File

@ -20,8 +20,6 @@
@set erts_vsn={{ erts_vsn }} @set erts_vsn={{ erts_vsn }}
@set erl_opts={{ erl_opts }} @set erl_opts={{ erl_opts }}
@set "CUTTLEFISH_ENV_OVERRIDE_PREFIX=EMQX_"
@set script=%~n0 @set script=%~n0
:: Discover the release root directory from the directory :: Discover the release root directory from the directory

View File

@ -116,7 +116,7 @@ spec:
value: {{ .Release.Name }} value: {{ .Release.Name }}
- name: EMQX_CLUSTER__K8S__APP_NAME - name: EMQX_CLUSTER__K8S__APP_NAME
value: {{ .Release.Name }} value: {{ .Release.Name }}
- name: EMQX_CLUSTER__DISCOVERY - name: EMQX_CLUSTER__DISCOVERY_STRATEGY
value: k8s value: k8s
- name: EMQX_CLUSTER__K8S__SERVICE_NAME - name: EMQX_CLUSTER__K8S__SERVICE_NAME
value: {{ include "emqx.fullname" . }}-headless value: {{ include "emqx.fullname" . }}-headless

View File

@ -48,8 +48,8 @@ You can change the prefix by overriding "HOCON_ENV_OVERRIDE_PREFIX".
Example: Example:
```bash ```bash
EMQX_LISTENER__SSL__EXTERNAL__ACCEPTORS <--> listener.ssl.external.acceptors EMQX_ZONES__DEFAULT__LISTENERS__MQTT_SSL__ACCEPTORS <--> zones.default.listeners.mqtt_ssl.acceptors
EMQX_MQTT__MAX_PACKET_SIZE <--> mqtt.max_packet_size EMQX_ZONES__DEFAULT__MQTT__MAX_PACKET_SIZE <--> zones.default.mqtt.max_packet_size
``` ```
+ Prefix ``EMQX_`` is removed + Prefix ``EMQX_`` is removed
@ -87,7 +87,7 @@ If set ``EMQX_NAME`` and ``EMQX_HOST``, and unset ``EMQX_NODE_NAME``, ``EMQX_NOD
For example, set mqtt tcp port to 1883 For example, set mqtt tcp port to 1883
``docker run -d --name emqx -e EMQX_LISTENER__TCP__EXTERNAL=1883 -p 18083:18083 -p 1883:1883 emqx/emqx:latest`` ``docker run -d --name emqx -e EMQX_ZONES__DEFAULT__LISTENERS__MQTT_TCP__BIND=1883 -p 18083:18083 -p 1883:1883 emqx/emqx:latest``
#### EMQ Loaded Modules Configuration #### EMQ Loaded Modules Configuration
@ -213,8 +213,8 @@ Let's create a static node list cluster from docker-compose.
environment: environment:
- "EMQX_NAME=emqx" - "EMQX_NAME=emqx"
- "EMQX_HOST=node1.emqx.io" - "EMQX_HOST=node1.emqx.io"
- "EMQX_CLUSTER__DISCOVERY=static" - "EMQX_CLUSTER__DISCOVERY_STRATEGY=static"
- "EMQX_CLUSTER__STATIC__SEEDS=emqx@node1.emqx.io, emqx@node2.emqx.io" - "EMQX_CLUSTER__STATIC__SEEDS=[emqx@node1.emqx.io, emqx@node2.emqx.io]"
networks: networks:
emqx-bridge: emqx-bridge:
aliases: aliases:
@ -225,8 +225,8 @@ Let's create a static node list cluster from docker-compose.
environment: environment:
- "EMQX_NAME=emqx" - "EMQX_NAME=emqx"
- "EMQX_HOST=node2.emqx.io" - "EMQX_HOST=node2.emqx.io"
- "EMQX_CLUSTER__DISCOVERY=static" - "EMQX_CLUSTER__DISCOVERY_STRATEGY=static"
- "EMQX_CLUSTER__STATIC__SEEDS=emqx@node1.emqx.io, emqx@node2.emqx.io" - "EMQX_CLUSTER__STATIC__SEEDS=[emqx@node1.emqx.io, emqx@node2.emqx.io]"
networks: networks:
emqx-bridge: emqx-bridge:
aliases: aliases:

View File

@ -42,52 +42,6 @@ if [[ -z "$EMQX_NODE_NAME" ]]; then
export EMQX_NODE_NAME="$EMQX_NAME@$EMQX_HOST" export EMQX_NODE_NAME="$EMQX_NAME@$EMQX_HOST"
fi fi
# Set hosts to prevent cluster mode failed
if [[ -z "$EMQX_NODE__PROCESS_LIMIT" ]]; then
export EMQX_NODE__PROCESS_LIMIT=2097152
fi
if [[ -z "$EMQX_NODE__MAX_PORTS" ]]; then
export EMQX_NODE__MAX_PORTS=1048576
fi
if [[ -z "$EMQX_NODE__MAX_ETS_TABLES" ]]; then
export EMQX_NODE__MAX_ETS_TABLES=2097152
fi
if [[ -z "$EMQX_LISTENER__TCP__EXTERNAL__ACCEPTORS" ]]; then
export EMQX_LISTENER__TCP__EXTERNAL__ACCEPTORS=64
fi
if [[ -z "$EMQX_LISTENER__TCP__EXTERNAL__MAX_CONNECTIONS" ]]; then
export EMQX_LISTENER__TCP__EXTERNAL__MAX_CONNECTIONS=1024000
fi
if [[ -z "$EMQX_LISTENER__SSL__EXTERNAL__ACCEPTORS" ]]; then
export EMQX_LISTENER__SSL__EXTERNAL__ACCEPTORS=32
fi
if [[ -z "$EMQX_LISTENER__SSL__EXTERNAL__MAX_CONNECTIONS" ]]; then
export EMQX_LISTENER__SSL__EXTERNAL__MAX_CONNECTIONS=102400
fi
if [[ -z "$EMQX_LISTENER__WS__EXTERNAL__ACCEPTORS" ]]; then
export EMQX_LISTENER__WS__EXTERNAL__ACCEPTORS=16
fi
if [[ -z "$EMQX_LISTENER__WS__EXTERNAL__MAX_CONNECTIONS" ]]; then
export EMQX_LISTENER__WS__EXTERNAL__MAX_CONNECTIONS=102400
fi
if [[ -z "$EMQX_LISTENER__WSS__EXTERNAL__ACCEPTORS" ]]; then
export EMQX_LISTENER__WSS__EXTERNAL__ACCEPTORS=16
fi
if [[ -z "$EMQX_LISTENER__WSS__EXTERNAL__MAX_CONNECTIONS" ]]; then
export EMQX_LISTENER__WSS__EXTERNAL__MAX_CONNECTIONS=102400
fi
# fill tuples on specific file # fill tuples on specific file
# SYNOPSIS # SYNOPSIS
# fill_tuples FILE [ELEMENTS ...] # fill_tuples FILE [ELEMENTS ...]

View File

@ -47,7 +47,7 @@
, {gproc, {git, "https://github.com/uwiger/gproc", {tag, "0.8.0"}}} , {gproc, {git, "https://github.com/uwiger/gproc", {tag, "0.8.0"}}}
, {jiffy, {git, "https://github.com/emqx/jiffy", {tag, "1.0.5"}}} , {jiffy, {git, "https://github.com/emqx/jiffy", {tag, "1.0.5"}}}
, {cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.8.2"}}} , {cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.8.2"}}}
, {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.8.0"}}} , {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.8.2"}}}
, {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.10.3"}}} , {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.10.3"}}}
, {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.5.1"}}} , {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.5.1"}}}
, {minirest, {git, "https://github.com/emqx/minirest", {tag, "1.1.2"}}} , {minirest, {git, "https://github.com/emqx/minirest", {tag, "1.1.2"}}}