Merge branch '0.13' of github.com:emqtt/emqttd into 0.13
This commit is contained in:
commit
f0ffdfb095
|
@ -209,6 +209,11 @@
|
||||||
#mqtt_packet{header = #mqtt_packet_header{type = ?CONNACK},
|
#mqtt_packet{header = #mqtt_packet_header{type = ?CONNACK},
|
||||||
variable = #mqtt_packet_connack{return_code = ReturnCode}}).
|
variable = #mqtt_packet_connack{return_code = ReturnCode}}).
|
||||||
|
|
||||||
|
-define(CONNACK_PACKET(ReturnCode, SessPresent),
|
||||||
|
#mqtt_packet{header = #mqtt_packet_header{type = ?CONNACK},
|
||||||
|
variable = #mqtt_packet_connack{ack_flags = SessPresent,
|
||||||
|
return_code = ReturnCode}}).
|
||||||
|
|
||||||
-define(PUBLISH_PACKET(Qos, PacketId),
|
-define(PUBLISH_PACKET(Qos, PacketId),
|
||||||
#mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH,
|
#mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH,
|
||||||
qos = Qos},
|
qos = Qos},
|
||||||
|
|
|
@ -29,8 +29,8 @@
|
||||||
{deps, [
|
{deps, [
|
||||||
{gproc, ".*", {git, "git://github.com/uwiger/gproc.git", {branch, "master"}}},
|
{gproc, ".*", {git, "git://github.com/uwiger/gproc.git", {branch, "master"}}},
|
||||||
{lager, ".*", {git, "git://github.com/basho/lager.git", {branch, "master"}}},
|
{lager, ".*", {git, "git://github.com/basho/lager.git", {branch, "master"}}},
|
||||||
{esockd, "3.*", {git, "git://github.com/emqtt/esockd.git", {branch, "3.0"}}},
|
{esockd, "3.*", {git, "git://github.com/emqtt/esockd.git", {branch, "master"}}},
|
||||||
{mochiweb, ".*", {git, "git://github.com/emqtt/mochiweb.git", {branch, "4.0"}}}
|
{mochiweb, "4.*", {git, "git://github.com/emqtt/mochiweb.git", {branch, "master"}}}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{recursive_cmds, [ct, eunit, clean]}.
|
{recursive_cmds, [ct, eunit, clean]}.
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
%%%-----------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
%%
|
%%%
|
||||||
%% [ACL](https://github.com/emqtt/emqttd/wiki/ACL)
|
%%% [ACL](https://github.com/emqtt/emqttd/wiki/ACL)
|
||||||
%%
|
%%%
|
||||||
%% -type who() :: all | binary() |
|
%%% -type who() :: all | binary() |
|
||||||
%% {ipaddr, esockd_access:cidr()} |
|
%%% {ipaddr, esockd_access:cidr()} |
|
||||||
%% {client, binary()} |
|
%%% {client, binary()} |
|
||||||
%% {user, binary()}.
|
%%% {user, binary()}.
|
||||||
%%
|
%%%
|
||||||
%% -type access() :: subscribe | publish | pubsub.
|
%%% -type access() :: subscribe | publish | pubsub.
|
||||||
%%
|
%%%
|
||||||
%% -type topic() :: binary().
|
%%% -type topic() :: binary().
|
||||||
%%
|
%%%
|
||||||
%% -type rule() :: {allow, all} |
|
%%% -type rule() :: {allow, all} |
|
||||||
%% {allow, who(), access(), list(topic())} |
|
%%% {allow, who(), access(), list(topic())} |
|
||||||
%% {deny, all} |
|
%%% {deny, all} |
|
||||||
%% {deny, who(), access(), list(topic())}.
|
%%% {deny, who(), access(), list(topic())}.
|
||||||
%%
|
%%%
|
||||||
%%%-----------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
|
|
||||||
{allow, {user, "dashboard"}, subscribe, ["$SYS/#"]}.
|
{allow, {user, "dashboard"}, subscribe, ["$SYS/#"]}.
|
||||||
|
|
|
@ -246,7 +246,7 @@
|
||||||
{long_gc, false},
|
{long_gc, false},
|
||||||
|
|
||||||
%% Long Schedule(ms)
|
%% Long Schedule(ms)
|
||||||
{long_schedule, 50},
|
{long_schedule, 100},
|
||||||
|
|
||||||
%% 8M words. 32MB on 32-bit VM, 64MB on 64-bit VM.
|
%% 8M words. 32MB on 32-bit VM, 64MB on 64-bit VM.
|
||||||
%% 8 * 1024 * 1024
|
%% 8 * 1024 * 1024
|
||||||
|
|
|
@ -91,8 +91,7 @@ open_listener({https, Port, Options}) ->
|
||||||
mochiweb:start_http(Port, Options, MFArgs).
|
mochiweb:start_http(Port, Options, MFArgs).
|
||||||
|
|
||||||
open_listener(Protocol, Port, Options) ->
|
open_listener(Protocol, Port, Options) ->
|
||||||
Rl = rate_limiter(emqttd_opts:g(rate_limit, Options)),
|
MFArgs = {emqttd_client, start_link, [env(mqtt)]},
|
||||||
MFArgs = {emqttd_client, start_link, [[{rate_limiter, Rl} | env(mqtt)]]},
|
|
||||||
esockd:open(Protocol, Port, merge_sockopts(Options) , MFArgs).
|
esockd:open(Protocol, Port, merge_sockopts(Options) , MFArgs).
|
||||||
|
|
||||||
merge_sockopts(Options) ->
|
merge_sockopts(Options) ->
|
||||||
|
@ -100,14 +99,6 @@ merge_sockopts(Options) ->
|
||||||
proplists:get_value(sockopts, Options, [])),
|
proplists:get_value(sockopts, Options, [])),
|
||||||
emqttd_opts:merge(Options, [{sockopts, SockOpts}]).
|
emqttd_opts:merge(Options, [{sockopts, SockOpts}]).
|
||||||
|
|
||||||
%% TODO: will refactor in 0.14.0 release.
|
|
||||||
rate_limiter(undefined) ->
|
|
||||||
undefined;
|
|
||||||
rate_limiter(Config) ->
|
|
||||||
Bps = fun(S) -> list_to_integer(string:strip(S)) * 1024 end,
|
|
||||||
[Burst, Rate] = [Bps(S) || S <- string:tokens(Config, ",")],
|
|
||||||
esockd_rate_limiter:new(Burst, Rate).
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc Close Listeners
|
%% @doc Close Listeners
|
||||||
%% @end
|
%% @end
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
%%%
|
%%%
|
||||||
%%% @end
|
%%% @end
|
||||||
%%%-----------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqttd_access_control).
|
-module(emqttd_access_control).
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
@ -36,14 +35,13 @@
|
||||||
-define(SERVER, ?MODULE).
|
-define(SERVER, ?MODULE).
|
||||||
|
|
||||||
%% API Function Exports
|
%% API Function Exports
|
||||||
-export([start_link/0,
|
-export([start_link/0, start_link/1,
|
||||||
start_link/1,
|
|
||||||
auth/2, % authentication
|
auth/2, % authentication
|
||||||
check_acl/3, % acl check
|
check_acl/3, % acl check
|
||||||
reload_acl/0, % reload acl
|
reload_acl/0, % reload acl
|
||||||
register_mod/3,
|
|
||||||
unregister_mod/2,
|
|
||||||
lookup_mods/1,
|
lookup_mods/1,
|
||||||
|
register_mod/3, register_mod/4,
|
||||||
|
unregister_mod/2,
|
||||||
stop/0]).
|
stop/0]).
|
||||||
|
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
|
@ -77,7 +75,7 @@ auth(Client, Password) when is_record(Client, mqtt_client) ->
|
||||||
auth(Client, Password, lookup_mods(auth)).
|
auth(Client, Password, lookup_mods(auth)).
|
||||||
auth(_Client, _Password, []) ->
|
auth(_Client, _Password, []) ->
|
||||||
{error, "No auth module to check!"};
|
{error, "No auth module to check!"};
|
||||||
auth(Client, Password, [{Mod, State} | Mods]) ->
|
auth(Client, Password, [{Mod, State, _Seq} | Mods]) ->
|
||||||
case Mod:check(Client, Password, State) of
|
case Mod:check(Client, Password, State) of
|
||||||
ok -> ok;
|
ok -> ok;
|
||||||
{error, Reason} -> {error, Reason};
|
{error, Reason} -> {error, Reason};
|
||||||
|
@ -100,7 +98,7 @@ check_acl(Client, PubSub, Topic) when ?IS_PUBSUB(PubSub) ->
|
||||||
check_acl(#mqtt_client{client_id = ClientId}, PubSub, Topic, []) ->
|
check_acl(#mqtt_client{client_id = ClientId}, PubSub, Topic, []) ->
|
||||||
lager:error("ACL: nomatch when ~s ~s ~s", [ClientId, PubSub, Topic]),
|
lager:error("ACL: nomatch when ~s ~s ~s", [ClientId, PubSub, Topic]),
|
||||||
allow;
|
allow;
|
||||||
check_acl(Client, PubSub, Topic, [{M, State}|AclMods]) ->
|
check_acl(Client, PubSub, Topic, [{M, State, _Seq}|AclMods]) ->
|
||||||
case M:check_acl({Client, PubSub, Topic}, State) of
|
case M:check_acl({Client, PubSub, Topic}, State) of
|
||||||
allow -> allow;
|
allow -> allow;
|
||||||
deny -> deny;
|
deny -> deny;
|
||||||
|
@ -113,7 +111,7 @@ check_acl(Client, PubSub, Topic, [{M, State}|AclMods]) ->
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-spec reload_acl() -> list() | {error, any()}.
|
-spec reload_acl() -> list() | {error, any()}.
|
||||||
reload_acl() ->
|
reload_acl() ->
|
||||||
[M:reload_acl(State) || {M, State} <- lookup_mods(acl)].
|
[M:reload_acl(State) || {M, State, _Seq} <- lookup_mods(acl)].
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc Register authentication or ACL module
|
%% @doc Register authentication or ACL module
|
||||||
|
@ -121,7 +119,11 @@ reload_acl() ->
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-spec register_mod(Type :: auth | acl, Mod :: atom(), Opts :: list()) -> ok | {error, any()}.
|
-spec register_mod(Type :: auth | acl, Mod :: atom(), Opts :: list()) -> ok | {error, any()}.
|
||||||
register_mod(Type, Mod, Opts) when Type =:= auth; Type =:= acl->
|
register_mod(Type, Mod, Opts) when Type =:= auth; Type =:= acl->
|
||||||
gen_server:call(?SERVER, {register_mod, Type, Mod, Opts}).
|
register_mod(Type, Mod, Opts, 0).
|
||||||
|
|
||||||
|
-spec register_mod(auth | acl, atom(), list(), pos_integer()) -> ok | {error, any()}.
|
||||||
|
register_mod(Type, Mod, Opts, Seq) when Type =:= auth; Type =:= acl->
|
||||||
|
gen_server:call(?SERVER, {register_mod, Type, Mod, Opts, Seq}).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc Unregister authentication or ACL module
|
%% @doc Unregister authentication or ACL module
|
||||||
|
@ -172,16 +174,20 @@ init_mods(acl, AclMods) ->
|
||||||
init_mod(Fun, Name, Opts) ->
|
init_mod(Fun, Name, Opts) ->
|
||||||
Module = Fun(Name),
|
Module = Fun(Name),
|
||||||
{ok, State} = Module:init(Opts),
|
{ok, State} = Module:init(Opts),
|
||||||
{Module, State}.
|
{Module, State, 0}.
|
||||||
|
|
||||||
handle_call({register_mod, Type, Mod, Opts}, _From, State) ->
|
handle_call({register_mod, Type, Mod, Opts, Seq}, _From, State) ->
|
||||||
Mods = lookup_mods(Type),
|
Mods = lookup_mods(Type),
|
||||||
Reply =
|
Reply =
|
||||||
case lists:keyfind(Mod, 1, Mods) of
|
case lists:keyfind(Mod, 1, Mods) of
|
||||||
false ->
|
false ->
|
||||||
case catch Mod:init(Opts) of
|
case catch Mod:init(Opts) of
|
||||||
{ok, ModState} ->
|
{ok, ModState} ->
|
||||||
ets:insert(?ACCESS_CONTROL_TAB, {tab_key(Type), [{Mod, ModState}|Mods]}),
|
NewMods =
|
||||||
|
lists:sort(fun({_, _, Seq1}, {_, _, Seq2}) ->
|
||||||
|
Seq1 >= Seq2
|
||||||
|
end, [{Mod, ModState, Seq} | Mods]),
|
||||||
|
ets:insert(?ACCESS_CONTROL_TAB, {tab_key(Type), NewMods}),
|
||||||
ok;
|
ok;
|
||||||
{'EXIT', Error} ->
|
{'EXIT', Error} ->
|
||||||
lager:error("Access Control: register ~s error - ~p", [Mod, Error]),
|
lager:error("Access Control: register ~s error - ~p", [Mod, Error]),
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
%%%
|
%%%
|
||||||
%%% @end
|
%%% @end
|
||||||
%%%-----------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqttd_access_rule).
|
-module(emqttd_access_rule).
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
@ -49,14 +48,19 @@
|
||||||
|
|
||||||
-export([compile/1, match/3]).
|
-export([compile/1, match/3]).
|
||||||
|
|
||||||
|
-define(ALLOW_DENY(A), ((A =:= allow) orelse (A =:= deny))).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc Compile access rule
|
%% @doc Compile access rule
|
||||||
%% @end
|
%% @end
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
compile({A, all}) when (A =:= allow) orelse (A =:= deny) ->
|
compile({A, all}) when ?ALLOW_DENY(A) ->
|
||||||
{A, all};
|
{A, all};
|
||||||
|
|
||||||
compile({A, Who, Access, TopicFilters}) when (A =:= allow) orelse (A =:= deny) ->
|
compile({A, Who, Access, Topic}) when ?ALLOW_DENY(A) andalso is_binary(Topic) ->
|
||||||
|
{A, compile(who, Who), Access, [compile(topic, Topic)]};
|
||||||
|
|
||||||
|
compile({A, Who, Access, TopicFilters}) when ?ALLOW_DENY(A) ->
|
||||||
{A, compile(who, Who), Access, [compile(topic, Topic) || Topic <- TopicFilters]}.
|
{A, compile(who, Who), Access, [compile(topic, Topic) || Topic <- TopicFilters]}.
|
||||||
|
|
||||||
compile(who, all) ->
|
compile(who, all) ->
|
||||||
|
@ -72,6 +76,10 @@ compile(who, {user, all}) ->
|
||||||
{user, all};
|
{user, all};
|
||||||
compile(who, {user, Username}) ->
|
compile(who, {user, Username}) ->
|
||||||
{user, bin(Username)};
|
{user, bin(Username)};
|
||||||
|
compile(who, {'and', Conds}) when is_list(Conds) ->
|
||||||
|
{'and', [compile(who, Cond) || Cond <- Conds]};
|
||||||
|
compile(who, {'or', Conds}) when is_list(Conds) ->
|
||||||
|
{'or', [compile(who, Cond) || Cond <- Conds]};
|
||||||
|
|
||||||
compile(topic, {eq, Topic}) ->
|
compile(topic, {eq, Topic}) ->
|
||||||
{eq, emqttd_topic:words(bin(Topic))};
|
{eq, emqttd_topic:words(bin(Topic))};
|
||||||
|
@ -120,6 +128,14 @@ match_who(#mqtt_client{peername = undefined}, {ipaddr, _Tup}) ->
|
||||||
match_who(#mqtt_client{peername = {IP, _}}, {ipaddr, {_CDIR, Start, End}}) ->
|
match_who(#mqtt_client{peername = {IP, _}}, {ipaddr, {_CDIR, Start, End}}) ->
|
||||||
I = esockd_access:atoi(IP),
|
I = esockd_access:atoi(IP),
|
||||||
I >= Start andalso I =< End;
|
I >= Start andalso I =< End;
|
||||||
|
match_who(Client, {'and', Conds}) when is_list(Conds) ->
|
||||||
|
lists:foldl(fun(Who, Allow) ->
|
||||||
|
match_who(Client, Who) andalso Allow
|
||||||
|
end, true, Conds);
|
||||||
|
match_who(Client, {'or', Conds}) when is_list(Conds) ->
|
||||||
|
lists:foldl(fun(Who, Allow) ->
|
||||||
|
match_who(Client, Who) orelse Allow
|
||||||
|
end, false, Conds);
|
||||||
match_who(_Client, _Who) ->
|
match_who(_Client, _Who) ->
|
||||||
false.
|
false.
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ start_servers(Sup) ->
|
||||||
{"emqttd session supervisor", {supervisor, emqttd_session_sup}},
|
{"emqttd session supervisor", {supervisor, emqttd_session_sup}},
|
||||||
{"emqttd broker", emqttd_broker},
|
{"emqttd broker", emqttd_broker},
|
||||||
{"emqttd alarm", emqttd_alarm},
|
{"emqttd alarm", emqttd_alarm},
|
||||||
{"emqttd mode supervisor", emqttd_mod_sup},
|
{"emqttd mod supervisor", emqttd_mod_sup},
|
||||||
{"emqttd bridge supervisor", {supervisor, emqttd_bridge_sup}},
|
{"emqttd bridge supervisor", {supervisor, emqttd_bridge_sup}},
|
||||||
{"emqttd access control", emqttd_access_control},
|
{"emqttd access control", emqttd_access_control},
|
||||||
{"emqttd system monitor", emqttd_sysmon, emqttd:env(sysmon)}],
|
{"emqttd system monitor", emqttd_sysmon, emqttd:env(sysmon)}],
|
||||||
|
|
|
@ -105,7 +105,7 @@ init(Opts) ->
|
||||||
mnesia:create_table(?AUTH_USERNAME_TAB, [
|
mnesia:create_table(?AUTH_USERNAME_TAB, [
|
||||||
{disc_copies, [node()]},
|
{disc_copies, [node()]},
|
||||||
{attributes, record_info(fields, ?AUTH_USERNAME_TAB)}]),
|
{attributes, record_info(fields, ?AUTH_USERNAME_TAB)}]),
|
||||||
mnesia:add_table_copy(?AUTH_USERNAME_TAB, node(), ram_copies),
|
mnesia:add_table_copy(?AUTH_USERNAME_TAB, node(), disc_copies),
|
||||||
emqttd_ctl:register_cmd(users, {?MODULE, cli}, []),
|
emqttd_ctl:register_cmd(users, {?MODULE, cli}, []),
|
||||||
{ok, Opts}.
|
{ok, Opts}.
|
||||||
|
|
||||||
|
|
|
@ -72,8 +72,8 @@ status([]) ->
|
||||||
case lists:keysearch(emqttd, 1, application:which_applications()) of
|
case lists:keysearch(emqttd, 1, application:which_applications()) of
|
||||||
false ->
|
false ->
|
||||||
?PRINT_MSG("emqttd is not running~n");
|
?PRINT_MSG("emqttd is not running~n");
|
||||||
{value,_Version} ->
|
{value, {emqttd, _Desc, Vsn}} ->
|
||||||
?PRINT_MSG("emqttd is running~n")
|
?PRINT("emqttd ~s is running~n", [Vsn])
|
||||||
end;
|
end;
|
||||||
status(_) ->
|
status(_) ->
|
||||||
?PRINT_CMD("status", "query broker status").
|
?PRINT_CMD("status", "query broker status").
|
||||||
|
|
|
@ -124,7 +124,7 @@ handle_call(info, _From, State = #client_state{connection = Connection,
|
||||||
ClientInfo = ?record_to_proplist(client_state, State, ?INFO_KEYS),
|
ClientInfo = ?record_to_proplist(client_state, State, ?INFO_KEYS),
|
||||||
ProtoInfo = emqttd_protocol:info(ProtoState),
|
ProtoInfo = emqttd_protocol:info(ProtoState),
|
||||||
{ok, SockStats} = Connection:getstat(?SOCK_STATS),
|
{ok, SockStats} = Connection:getstat(?SOCK_STATS),
|
||||||
{noreply, lists:append([ClientInfo, [{proto_info, ProtoInfo},
|
{reply, lists:append([ClientInfo, [{proto_info, ProtoInfo},
|
||||||
{sock_stats, SockStats}]]), State};
|
{sock_stats, SockStats}]]), State};
|
||||||
|
|
||||||
handle_call(kick, _From, State) ->
|
handle_call(kick, _From, State) ->
|
||||||
|
@ -170,7 +170,7 @@ handle_info({redeliver, {?PUBREL, PacketId}}, State) ->
|
||||||
|
|
||||||
handle_info({shutdown, conflict, {ClientId, NewPid}}, State) ->
|
handle_info({shutdown, conflict, {ClientId, NewPid}}, State) ->
|
||||||
?LOG(warning, "clientid '~s' conflict with ~p", [ClientId, NewPid], State),
|
?LOG(warning, "clientid '~s' conflict with ~p", [ClientId, NewPid], State),
|
||||||
shutdown(confict, State);
|
shutdown(conflict, State);
|
||||||
|
|
||||||
handle_info(activate_sock, State) ->
|
handle_info(activate_sock, State) ->
|
||||||
noreply(run_socket(State#client_state{conn_state = running}));
|
noreply(run_socket(State#client_state{conn_state = running}));
|
||||||
|
@ -281,14 +281,14 @@ received(Bytes, State = #client_state{parser_fun = ParserFun,
|
||||||
|
|
||||||
rate_limit(_Size, State = #client_state{rate_limit = undefined}) ->
|
rate_limit(_Size, State = #client_state{rate_limit = undefined}) ->
|
||||||
run_socket(State);
|
run_socket(State);
|
||||||
rate_limit(Size, State = #client_state{rate_limit = Limiter}) ->
|
rate_limit(Size, State = #client_state{rate_limit = Rl}) ->
|
||||||
case esockd_ratelimit:check(Limiter, Size) of
|
case Rl:check(Size) of
|
||||||
{0, Limiter1} ->
|
{0, Rl1} ->
|
||||||
run_socket(State#client_state{conn_state = running, rate_limit = Limiter1});
|
run_socket(State#client_state{conn_state = running, rate_limit = Rl1});
|
||||||
{Pause, Limiter1} ->
|
{Pause, Rl1} ->
|
||||||
?LOG(error, "Rate limiter pause for ~p", [Size, Pause], State),
|
?LOG(error, "Rate limiter pause for ~p", [Size, Pause], State),
|
||||||
erlang:send_after(Pause, self(), activate_sock),
|
erlang:send_after(Pause, self(), activate_sock),
|
||||||
State#client_state{conn_state = blocked, rate_limit = Limiter1}
|
State#client_state{conn_state = blocked, rate_limit = Rl1}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
run_socket(State = #client_state{conn_state = blocked}) ->
|
run_socket(State = #client_state{conn_state = blocked}) ->
|
||||||
|
|
|
@ -41,6 +41,9 @@
|
||||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
terminate/2, code_change/3]).
|
terminate/2, code_change/3]).
|
||||||
|
|
||||||
|
%% gen_server2 priorities
|
||||||
|
-export([prioritise_call/4, prioritise_cast/3, prioritise_info/3]).
|
||||||
|
|
||||||
-record(state, {id, statsfun}).
|
-record(state, {id, statsfun}).
|
||||||
|
|
||||||
-define(CM_POOL, ?MODULE).
|
-define(CM_POOL, ?MODULE).
|
||||||
|
@ -101,8 +104,21 @@ init([Id, StatsFun]) ->
|
||||||
gproc_pool:connect_worker(?CM_POOL, {?MODULE, Id}),
|
gproc_pool:connect_worker(?CM_POOL, {?MODULE, Id}),
|
||||||
{ok, #state{id = Id, statsfun = StatsFun}}.
|
{ok, #state{id = Id, statsfun = StatsFun}}.
|
||||||
|
|
||||||
|
prioritise_call(_Req, _From, _Len, _State) ->
|
||||||
|
1.
|
||||||
|
|
||||||
|
prioritise_cast(Msg, _Len, _State) ->
|
||||||
|
case Msg of
|
||||||
|
{register, _Client} -> 2;
|
||||||
|
{unregister, _ClientId, _Pid} -> 3;
|
||||||
|
_ -> 1
|
||||||
|
end.
|
||||||
|
|
||||||
|
prioritise_info(_Msg, _Len, _State) ->
|
||||||
|
1.
|
||||||
|
|
||||||
handle_call(Req, _From, State) ->
|
handle_call(Req, _From, State) ->
|
||||||
lager:error("unexpected request: ~p", [Req]),
|
lager:error("Unexpected request: ~p", [Req]),
|
||||||
{reply, {error, unsupported_req}, State}.
|
{reply, {error, unsupported_req}, State}.
|
||||||
|
|
||||||
handle_cast({register, Client = #mqtt_client{client_id = ClientId,
|
handle_cast({register, Client = #mqtt_client{client_id = ClientId,
|
||||||
|
@ -110,32 +126,45 @@ handle_cast({register, Client = #mqtt_client{client_id = ClientId,
|
||||||
case ets:lookup(mqtt_client, ClientId) of
|
case ets:lookup(mqtt_client, ClientId) of
|
||||||
[#mqtt_client{client_pid = Pid}] ->
|
[#mqtt_client{client_pid = Pid}] ->
|
||||||
ignore;
|
ignore;
|
||||||
[#mqtt_client{client_pid = OldPid}] ->
|
[#mqtt_client{client_pid = _OldPid, client_mon = MRef}] ->
|
||||||
%% TODO: should cancel monitor
|
%% demonitor
|
||||||
?LOG(warning, "client ~p conflict with ~p", [Pid, OldPid], Client);
|
erlang:demonitor(MRef, [flush]);
|
||||||
[] ->
|
[] ->
|
||||||
ok
|
ok
|
||||||
end,
|
end,
|
||||||
ets:insert(mqtt_client, Client),
|
ets:insert(mqtt_client, Client#mqtt_client{client_mon = erlang:monitor(process, Pid)}),
|
||||||
{noreply, setstats(State)};
|
{noreply, setstats(State)};
|
||||||
|
|
||||||
handle_cast({unregister, ClientId, Pid}, State) ->
|
handle_cast({unregister, ClientId, Pid}, State) ->
|
||||||
case ets:lookup(mqtt_client, ClientId) of
|
case ets:lookup(mqtt_client, ClientId) of
|
||||||
[#mqtt_client{client_pid = Pid}] ->
|
[#mqtt_client{client_pid = Pid, client_mon = MRef}] ->
|
||||||
ets:delete(mqtt_client, ClientId);
|
erlang:demonitor(MRef, [flush]),
|
||||||
[_] ->
|
ets:delete(mqtt_client, ClientId),
|
||||||
ignore;
|
|
||||||
[] ->
|
|
||||||
?LOG(error, "Cannot find registered: ~p", [Pid], State)
|
|
||||||
end,
|
|
||||||
{noreply, setstats(State)};
|
{noreply, setstats(State)};
|
||||||
|
[_] ->
|
||||||
|
{noreply, State};
|
||||||
|
[] ->
|
||||||
|
lager:warning("CM(~s): Cannot find pid ~p", [ClientId, Pid]),
|
||||||
|
{noreply, State}
|
||||||
|
end;
|
||||||
|
|
||||||
handle_cast(Msg, State) ->
|
handle_cast(Msg, State) ->
|
||||||
lager:error("Unexpected Msg: ~p", [Msg]),
|
lager:error("Unexpected Msg: ~p", [Msg]),
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
|
handle_info({'DOWN', MRef, process, DownPid, Reason}, State) ->
|
||||||
|
MP = #mqtt_client{client_pid = DownPid, client_mon = MRef, _ = '_'},
|
||||||
|
case ets:match_object(mqtt_client, MP) of
|
||||||
|
[Client] ->
|
||||||
|
?LOG(warning, "client ~p DOWN for ~p", [DownPid, Reason], Client),
|
||||||
|
ets:delete_object(mqtt_client, Client);
|
||||||
|
[] ->
|
||||||
|
ignore
|
||||||
|
end,
|
||||||
|
{noreply, setstats(State)};
|
||||||
|
|
||||||
handle_info(Info, State) ->
|
handle_info(Info, State) ->
|
||||||
lager:error("Unexpected Msg: ~p", [Info]),
|
lager:error("Unexpected Info: ~p", [Info]),
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
terminate(_Reason, #state{id = Id}) ->
|
terminate(_Reason, #state{id = Id}) ->
|
||||||
|
|
|
@ -149,7 +149,7 @@ process(Packet = ?CONNECT_PACKET(Var), State0) ->
|
||||||
|
|
||||||
trace(recv, Packet, State1),
|
trace(recv, Packet, State1),
|
||||||
|
|
||||||
{ReturnCode1, State3} =
|
{ReturnCode1, SessPresent, State3} =
|
||||||
case validate_connect(Var, State1) of
|
case validate_connect(Var, State1) of
|
||||||
?CONNACK_ACCEPT ->
|
?CONNACK_ACCEPT ->
|
||||||
case emqttd_access_control:auth(client(State1), Password) of
|
case emqttd_access_control:auth(client(State1), Password) of
|
||||||
|
@ -159,30 +159,30 @@ process(Packet = ?CONNECT_PACKET(Var), State0) ->
|
||||||
|
|
||||||
%% Start session
|
%% Start session
|
||||||
case emqttd_sm:start_session(CleanSess, clientid(State2)) of
|
case emqttd_sm:start_session(CleanSess, clientid(State2)) of
|
||||||
{ok, Session} ->
|
{ok, Session, SP} ->
|
||||||
%% Register the client
|
%% Register the client
|
||||||
emqttd_cm:register(client(State2)),
|
emqttd_cm:register(client(State2)),
|
||||||
%% Start keepalive
|
%% Start keepalive
|
||||||
start_keepalive(KeepAlive),
|
start_keepalive(KeepAlive),
|
||||||
%% ACCEPT
|
%% ACCEPT
|
||||||
{?CONNACK_ACCEPT, State2#proto_state{session = Session}};
|
{?CONNACK_ACCEPT, SP, State2#proto_state{session = Session}};
|
||||||
{error, Error} ->
|
{error, Error} ->
|
||||||
exit({shutdown, Error})
|
exit({shutdown, Error})
|
||||||
end;
|
end;
|
||||||
{error, Reason}->
|
{error, Reason}->
|
||||||
?LOG(error, "Username '~s' login failed for ~s", [Username, Reason], State1),
|
?LOG(error, "Username '~s' login failed for ~s", [Username, Reason], State1),
|
||||||
{?CONNACK_CREDENTIALS, State1}
|
{?CONNACK_CREDENTIALS, false, State1}
|
||||||
end;
|
end;
|
||||||
ReturnCode ->
|
ReturnCode ->
|
||||||
{ReturnCode, State1}
|
{ReturnCode, false, State1}
|
||||||
end,
|
end,
|
||||||
%% Run hooks
|
%% Run hooks
|
||||||
emqttd_broker:foreach_hooks('client.connected', [ReturnCode1, client(State3)]),
|
emqttd_broker:foreach_hooks('client.connected', [ReturnCode1, client(State3)]),
|
||||||
%% Send connack
|
%% Send connack
|
||||||
send(?CONNACK_PACKET(ReturnCode1), State3);
|
send(?CONNACK_PACKET(ReturnCode1, sp(SessPresent)), State3);
|
||||||
|
|
||||||
process(Packet = ?PUBLISH_PACKET(_Qos, Topic, _PacketId, _Payload), State) ->
|
process(Packet = ?PUBLISH_PACKET(_Qos, Topic, _PacketId, _Payload), State) ->
|
||||||
case check_acl(publish, Topic, State) of
|
case check_acl(publish, Topic, client(State)) of
|
||||||
allow ->
|
allow ->
|
||||||
publish(Packet, State);
|
publish(Packet, State);
|
||||||
deny ->
|
deny ->
|
||||||
|
@ -210,7 +210,8 @@ process(?SUBSCRIBE_PACKET(PacketId, []), State) ->
|
||||||
send(?SUBACK_PACKET(PacketId, []), State);
|
send(?SUBACK_PACKET(PacketId, []), State);
|
||||||
|
|
||||||
process(?SUBSCRIBE_PACKET(PacketId, TopicTable), State = #proto_state{session = Session}) ->
|
process(?SUBSCRIBE_PACKET(PacketId, TopicTable), State = #proto_state{session = Session}) ->
|
||||||
AllowDenies = [check_acl(subscribe, Topic, State) || {Topic, _Qos} <- TopicTable],
|
Client = client(State),
|
||||||
|
AllowDenies = [check_acl(subscribe, Topic, Client) || {Topic, _Qos} <- TopicTable],
|
||||||
case lists:member(deny, AllowDenies) of
|
case lists:member(deny, AllowDenies) of
|
||||||
true ->
|
true ->
|
||||||
?LOG(error, "Cannot SUBSCRIBE ~p for ACL Deny", [TopicTable], State),
|
?LOG(error, "Cannot SUBSCRIBE ~p for ACL Deny", [TopicTable], State),
|
||||||
|
@ -281,7 +282,7 @@ redeliver({?PUBREL, PacketId}, State) ->
|
||||||
shutdown(_Error, #proto_state{client_id = undefined}) ->
|
shutdown(_Error, #proto_state{client_id = undefined}) ->
|
||||||
ignore;
|
ignore;
|
||||||
|
|
||||||
shutdown(confict, #proto_state{client_id = ClientId}) ->
|
shutdown(conflict, #proto_state{client_id = ClientId}) ->
|
||||||
emqttd_cm:unregister(ClientId);
|
emqttd_cm:unregister(ClientId);
|
||||||
|
|
||||||
shutdown(Error, State = #proto_state{client_id = ClientId, will_msg = WillMsg}) ->
|
shutdown(Error, State = #proto_state{client_id = ClientId, will_msg = WillMsg}) ->
|
||||||
|
@ -391,16 +392,19 @@ validate_qos(_) ->
|
||||||
false.
|
false.
|
||||||
|
|
||||||
%% PUBLISH ACL is cached in process dictionary.
|
%% PUBLISH ACL is cached in process dictionary.
|
||||||
check_acl(publish, Topic, State) ->
|
check_acl(publish, Topic, Client) ->
|
||||||
case get({acl, publish, Topic}) of
|
case get({acl, publish, Topic}) of
|
||||||
undefined ->
|
undefined ->
|
||||||
AllowDeny = emqttd_access_control:check_acl(client(State), publish, Topic),
|
AllowDeny = emqttd_access_control:check_acl(Client, publish, Topic),
|
||||||
put({acl, publish, Topic}, AllowDeny),
|
put({acl, publish, Topic}, AllowDeny),
|
||||||
AllowDeny;
|
AllowDeny;
|
||||||
AllowDeny ->
|
AllowDeny ->
|
||||||
AllowDeny
|
AllowDeny
|
||||||
end;
|
end;
|
||||||
|
|
||||||
check_acl(subscribe, Topic, State) ->
|
check_acl(subscribe, Topic, Client) ->
|
||||||
emqttd_access_control:check_acl(client(State), subscribe, Topic).
|
emqttd_access_control:check_acl(Client, subscribe, Topic).
|
||||||
|
|
||||||
|
sp(true) -> 1;
|
||||||
|
sp(false) -> 0.
|
||||||
|
|
||||||
|
|
|
@ -378,6 +378,7 @@ handle_cast({destroy, ClientId}, Session = #session{client_id = ClientId}) ->
|
||||||
|
|
||||||
handle_cast({resume, ClientId, ClientPid}, Session = #session{client_id = ClientId,
|
handle_cast({resume, ClientId, ClientPid}, Session = #session{client_id = ClientId,
|
||||||
client_pid = OldClientPid,
|
client_pid = OldClientPid,
|
||||||
|
clean_sess = CleanSess,
|
||||||
inflight_queue = InflightQ,
|
inflight_queue = InflightQ,
|
||||||
awaiting_ack = AwaitingAck,
|
awaiting_ack = AwaitingAck,
|
||||||
awaiting_comp = AwaitingComp,
|
awaiting_comp = AwaitingComp,
|
||||||
|
@ -405,10 +406,21 @@ handle_cast({resume, ClientId, ClientPid}, Session = #session{client_id = C
|
||||||
[cancel_timer(TRef) || TRef <- maps:values(AwaitingComp)],
|
[cancel_timer(TRef) || TRef <- maps:values(AwaitingComp)],
|
||||||
|
|
||||||
Session1 = Session#session{client_pid = ClientPid,
|
Session1 = Session#session{client_pid = ClientPid,
|
||||||
|
clean_sess = false,
|
||||||
awaiting_ack = #{},
|
awaiting_ack = #{},
|
||||||
awaiting_comp = #{},
|
awaiting_comp = #{},
|
||||||
expired_timer = undefined},
|
expired_timer = undefined},
|
||||||
|
|
||||||
|
%% CleanSess: true -> false?
|
||||||
|
if
|
||||||
|
CleanSess =:= true ->
|
||||||
|
?LOG(warning, "CleanSess changed to false.", [], Session),
|
||||||
|
emqttd_sm:unregister_session(CleanSess, ClientId),
|
||||||
|
emqttd_sm:register_session(false, ClientId, sess_info(Session1));
|
||||||
|
CleanSess =:= false ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
|
|
||||||
%% Redeliver inflight messages
|
%% Redeliver inflight messages
|
||||||
Session2 =
|
Session2 =
|
||||||
lists:foldl(fun({_Id, Msg}, Sess) ->
|
lists:foldl(fun({_Id, Msg}, Sess) ->
|
||||||
|
@ -585,7 +597,8 @@ kick(_ClientId, Pid, Pid) ->
|
||||||
ignore;
|
ignore;
|
||||||
kick(ClientId, OldPid, Pid) ->
|
kick(ClientId, OldPid, Pid) ->
|
||||||
unlink(OldPid),
|
unlink(OldPid),
|
||||||
OldPid ! {shutdown, conflict, {ClientId, Pid}}.
|
OldPid ! {shutdown, conflict, {ClientId, Pid}},
|
||||||
|
ok.
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Check inflight and awaiting_rel
|
%% Check inflight and awaiting_rel
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
%%%
|
%%%
|
||||||
%%% @end
|
%%% @end
|
||||||
%%%-----------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqttd_sm).
|
-module(emqttd_sm).
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
@ -57,7 +56,7 @@
|
||||||
|
|
||||||
-define(SM_POOL, ?MODULE).
|
-define(SM_POOL, ?MODULE).
|
||||||
|
|
||||||
-define(CALL_TIMEOUT, 60000).
|
-define(TIMEOUT, 60000).
|
||||||
|
|
||||||
-define(LOG(Level, Format, Args, Session),
|
-define(LOG(Level, Format, Args, Session),
|
||||||
lager:Level("SM(~s): " ++ Format, [Session#mqtt_session.client_id | Args])).
|
lager:Level("SM(~s): " ++ Format, [Session#mqtt_session.client_id | Args])).
|
||||||
|
@ -103,7 +102,7 @@ pool() -> ?SM_POOL.
|
||||||
%% @doc Start a session
|
%% @doc Start a session
|
||||||
%% @end
|
%% @end
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-spec start_session(CleanSess :: boolean(), binary()) -> {ok, pid()} | {error, any()}.
|
-spec start_session(CleanSess :: boolean(), binary()) -> {ok, pid(), boolean()} | {error, any()}.
|
||||||
start_session(CleanSess, ClientId) ->
|
start_session(CleanSess, ClientId) ->
|
||||||
SM = gproc_pool:pick_worker(?SM_POOL, ClientId),
|
SM = gproc_pool:pick_worker(?SM_POOL, ClientId),
|
||||||
call(SM, {start_session, {CleanSess, ClientId, self()}}).
|
call(SM, {start_session, {CleanSess, ClientId, self()}}).
|
||||||
|
@ -144,7 +143,7 @@ sesstab(true) -> mqtt_transient_session;
|
||||||
sesstab(false) -> mqtt_persistent_session.
|
sesstab(false) -> mqtt_persistent_session.
|
||||||
|
|
||||||
call(SM, Req) ->
|
call(SM, Req) ->
|
||||||
gen_server2:call(SM, Req, ?CALL_TIMEOUT). %%infinity).
|
gen_server2:call(SM, Req, ?TIMEOUT). %%infinity).
|
||||||
|
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
%%% gen_server callbacks
|
%%% gen_server callbacks
|
||||||
|
@ -168,20 +167,20 @@ handle_call({start_session, {false, ClientId, ClientPid}}, _From, State) ->
|
||||||
case lookup_session(ClientId) of
|
case lookup_session(ClientId) of
|
||||||
undefined ->
|
undefined ->
|
||||||
%% create session locally
|
%% create session locally
|
||||||
{reply, create_session(false, ClientId, ClientPid), State};
|
reply(create_session(false, ClientId, ClientPid), false, State);
|
||||||
Session ->
|
Session ->
|
||||||
{reply, resume_session(Session, ClientPid), State}
|
reply(resume_session(Session, ClientPid), true, State)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
%% transient session
|
%% transient session
|
||||||
handle_call({start_session, {true, ClientId, ClientPid}}, _From, State) ->
|
handle_call({start_session, {true, ClientId, ClientPid}}, _From, State) ->
|
||||||
case lookup_session(ClientId) of
|
case lookup_session(ClientId) of
|
||||||
undefined ->
|
undefined ->
|
||||||
{reply, create_session(true, ClientId, ClientPid), State};
|
reply(create_session(true, ClientId, ClientPid), false, State);
|
||||||
Session ->
|
Session ->
|
||||||
case destroy_session(Session) of
|
case destroy_session(Session) of
|
||||||
ok ->
|
ok ->
|
||||||
{reply, create_session(true, ClientId, ClientPid), State};
|
reply(create_session(true, ClientId, ClientPid), false, State);
|
||||||
{error, Error} ->
|
{error, Error} ->
|
||||||
{reply, {error, Error}, State}
|
{reply, {error, Error}, State}
|
||||||
end
|
end
|
||||||
|
@ -302,3 +301,8 @@ remove_session(Session) ->
|
||||||
{aborted, Error} -> {error, Error}
|
{aborted, Error} -> {error, Error}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
reply({ok, SessPid}, SP, State) ->
|
||||||
|
{reply, {ok, SessPid, SP}, State};
|
||||||
|
reply({error, Error}, _SP, State) ->
|
||||||
|
{reply, {error, Error}, State}.
|
||||||
|
|
||||||
|
|
|
@ -196,7 +196,7 @@ handle_info({redeliver, {?PUBREL, PacketId}}, State) ->
|
||||||
|
|
||||||
handle_info({shutdown, conflict, {ClientId, NewPid}}, State = #wsclient_state{request = Req}) ->
|
handle_info({shutdown, conflict, {ClientId, NewPid}}, State = #wsclient_state{request = Req}) ->
|
||||||
?WSLOG(warning, "clientid '~s' conflict with ~p", [ClientId, NewPid], Req),
|
?WSLOG(warning, "clientid '~s' conflict with ~p", [ClientId, NewPid], Req),
|
||||||
shutdown(confict, State);
|
shutdown(conflict, State);
|
||||||
|
|
||||||
handle_info({keepalive, start, Interval}, State = #wsclient_state{request = Req}) ->
|
handle_info({keepalive, start, Interval}, State = #wsclient_state{request = Req}) ->
|
||||||
?WSLOG(debug, "Keepalive at the interval of ~p", [Interval], Req),
|
?WSLOG(debug, "Keepalive at the interval of ~p", [Interval], Req),
|
||||||
|
|
|
@ -42,30 +42,33 @@ register_mod_test() ->
|
||||||
with_acl(
|
with_acl(
|
||||||
fun() ->
|
fun() ->
|
||||||
emqttd_access_control:register_mod(acl, emqttd_acl_test_mod, []),
|
emqttd_access_control:register_mod(acl, emqttd_acl_test_mod, []),
|
||||||
?assertMatch([{emqttd_acl_test_mod, _}, {emqttd_acl_internal, _}],
|
?assertMatch([{emqttd_acl_test_mod, _, 0}, {emqttd_acl_internal, _, 0}],
|
||||||
emqttd_access_control:lookup_mods(acl)),
|
emqttd_access_control:lookup_mods(acl)),
|
||||||
emqttd_access_control:register_mod(auth, emqttd_auth_anonymous_test_mod,[]),
|
emqttd_access_control:register_mod(auth, emqttd_auth_anonymous_test_mod,[]),
|
||||||
?assertMatch([{emqttd_auth_anonymous_test_mod, _}, {emqttd_auth_anonymous, _}],
|
emqttd_access_control:register_mod(auth, emqttd_auth_dashboard, [], 99),
|
||||||
|
?assertMatch([{emqttd_auth_dashboard, _, 99},
|
||||||
|
{emqttd_auth_anonymous_test_mod, _, 0},
|
||||||
|
{emqttd_auth_anonymous, _, 0}],
|
||||||
emqttd_access_control:lookup_mods(auth))
|
emqttd_access_control:lookup_mods(auth))
|
||||||
end).
|
end).
|
||||||
|
|
||||||
unregister_mod_test() ->
|
unregister_mod_test() ->
|
||||||
with_acl(
|
with_acl(
|
||||||
fun() ->
|
fun() ->
|
||||||
emqttd_access_control:register_mod(acl,emqttd_acl_test_mod, []),
|
emqttd_access_control:register_mod(acl, emqttd_acl_test_mod, []),
|
||||||
?assertMatch([{emqttd_acl_test_mod, _}, {emqttd_acl_internal, _}],
|
?assertMatch([{emqttd_acl_test_mod, _, 0}, {emqttd_acl_internal, _, 0}],
|
||||||
emqttd_access_control:lookup_mods(acl)),
|
emqttd_access_control:lookup_mods(acl)),
|
||||||
emqttd_access_control:unregister_mod(acl, emqttd_acl_test_mod),
|
emqttd_access_control:unregister_mod(acl, emqttd_acl_test_mod),
|
||||||
timer:sleep(5),
|
timer:sleep(5),
|
||||||
?assertMatch([{emqttd_acl_internal, _}], emqttd_access_control:lookup_mods(acl)),
|
?assertMatch([{emqttd_acl_internal, _, 0}], emqttd_access_control:lookup_mods(acl)),
|
||||||
|
|
||||||
emqttd_access_control:register_mod(auth, emqttd_auth_anonymous_test_mod,[]),
|
emqttd_access_control:register_mod(auth, emqttd_auth_anonymous_test_mod,[]),
|
||||||
?assertMatch([{emqttd_auth_anonymous_test_mod, _}, {emqttd_auth_anonymous, _}],
|
?assertMatch([{emqttd_auth_anonymous_test_mod, _, 0}, {emqttd_auth_anonymous, _, 0}],
|
||||||
emqttd_access_control:lookup_mods(auth)),
|
emqttd_access_control:lookup_mods(auth)),
|
||||||
|
|
||||||
emqttd_access_control:unregister_mod(auth, emqttd_auth_anonymous_test_mod),
|
emqttd_access_control:unregister_mod(auth, emqttd_auth_anonymous_test_mod),
|
||||||
timer:sleep(5),
|
timer:sleep(5),
|
||||||
?assertMatch([{emqttd_auth_anonymous, _}], emqttd_access_control:lookup_mods(auth))
|
?assertMatch([{emqttd_auth_anonymous, _, 0}], emqttd_access_control:lookup_mods(auth))
|
||||||
end).
|
end).
|
||||||
|
|
||||||
check_acl_test() ->
|
check_acl_test() ->
|
||||||
|
|
|
@ -35,6 +35,14 @@
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
compile_test() ->
|
compile_test() ->
|
||||||
|
|
||||||
|
?assertMatch({allow, {'and', [{ipaddr, {"127.0.0.1", _I, _I}},
|
||||||
|
{user, <<"user">>}]}, subscribe, [ [<<"$SYS">>, '#'], ['#'] ]},
|
||||||
|
compile({allow, {'and', [{ipaddr, "127.0.0.1"}, {user, <<"user">>}]}, subscribe, ["$SYS/#", "#"]})),
|
||||||
|
?assertMatch({allow, {'or', [{ipaddr, {"127.0.0.1", _I, _I}},
|
||||||
|
{user, <<"user">>}]}, subscribe, [ [<<"$SYS">>, '#'], ['#'] ]},
|
||||||
|
compile({allow, {'or', [{ipaddr, "127.0.0.1"}, {user, <<"user">>}]}, subscribe, ["$SYS/#", "#"]})),
|
||||||
|
|
||||||
?assertMatch({allow, {ipaddr, {"127.0.0.1", _I, _I}}, subscribe, [ [<<"$SYS">>, '#'], ['#'] ]},
|
?assertMatch({allow, {ipaddr, {"127.0.0.1", _I, _I}}, subscribe, [ [<<"$SYS">>, '#'], ['#'] ]},
|
||||||
compile({allow, {ipaddr, "127.0.0.1"}, subscribe, ["$SYS/#", "#"]})),
|
compile({allow, {ipaddr, "127.0.0.1"}, subscribe, ["$SYS/#", "#"]})),
|
||||||
?assertMatch({allow, {user, <<"testuser">>}, subscribe, [ [<<"a">>, <<"b">>, <<"c">>], [<<"d">>, <<"e">>, <<"f">>, '#'] ]},
|
?assertMatch({allow, {user, <<"testuser">>}, subscribe, [ [<<"a">>, <<"b">>, <<"c">>], [<<"d">>, <<"e">>, <<"f">>, '#'] ]},
|
||||||
|
@ -70,9 +78,14 @@ match_test() ->
|
||||||
compile({allow, all, pubsub, ["clients/$c"]}))),
|
compile({allow, all, pubsub, ["clients/$c"]}))),
|
||||||
?assertMatch({matched, allow}, match(#mqtt_client{username = <<"user2">>}, <<"users/user2/abc/def">>,
|
?assertMatch({matched, allow}, match(#mqtt_client{username = <<"user2">>}, <<"users/user2/abc/def">>,
|
||||||
compile({allow, all, subscribe, ["users/$u/#"]}))),
|
compile({allow, all, subscribe, ["users/$u/#"]}))),
|
||||||
?assertMatch({matched, deny},
|
?assertMatch({matched, deny}, match(User, <<"d/e/f">>,
|
||||||
match(User, <<"d/e/f">>,
|
compile({deny, all, subscribe, ["$SYS/#", "#"]}))),
|
||||||
compile({deny, all, subscribe, ["$SYS/#", "#"]}))).
|
Rule = compile({allow, {'and', [{ipaddr, "127.0.0.1"}, {user, <<"WrongUser">>}]}, publish, <<"Topic">>}),
|
||||||
|
?assertMatch(nomatch, match(User, <<"Topic">>, Rule)),
|
||||||
|
AndRule = compile({allow, {'and', [{ipaddr, "127.0.0.1"}, {user, <<"TestUser">>}]}, publish, <<"Topic">>}),
|
||||||
|
?assertMatch({matched, allow}, match(User, <<"Topic">>, AndRule)),
|
||||||
|
OrRule = compile({allow, {'or', [{ipaddr, "127.0.0.1"}, {user, <<"WrongUser">>}]}, publish, ["Topic"]}),
|
||||||
|
?assertMatch({matched, allow}, match(User, <<"Topic">>, OrRule)).
|
||||||
|
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
|
||||||
|
-module(emqttd_auth_dashboard).
|
||||||
|
|
||||||
|
%% Auth callbacks
|
||||||
|
-export([init/1, check/3, description/0]).
|
||||||
|
|
||||||
|
init(Opts) ->
|
||||||
|
{ok, Opts}.
|
||||||
|
|
||||||
|
check(_Client, _Password, _Opts) ->
|
||||||
|
allow.
|
||||||
|
|
||||||
|
description() ->
|
||||||
|
"Test emqttd_auth_dashboard Mod".
|
Loading…
Reference in New Issue