Add flapping detect feature
This commit is contained in:
parent
5680b191ee
commit
bcbb4b68e9
2
Makefile
2
Makefile
|
@ -38,7 +38,7 @@ CT_SUITES = emqx emqx_client emqx_zone emqx_banned emqx_session \
|
||||||
emqx_listeners emqx_protocol emqx_pool emqx_shared_sub emqx_bridge \
|
emqx_listeners emqx_protocol emqx_pool emqx_shared_sub emqx_bridge \
|
||||||
emqx_hooks emqx_batch emqx_sequence emqx_pmon emqx_pd emqx_gc emqx_ws_connection \
|
emqx_hooks emqx_batch emqx_sequence emqx_pmon emqx_pd emqx_gc emqx_ws_connection \
|
||||||
emqx_packet emqx_connection emqx_tracer emqx_sys_mon emqx_message emqx_os_mon \
|
emqx_packet emqx_connection emqx_tracer emqx_sys_mon emqx_message emqx_os_mon \
|
||||||
emqx_vm_mon emqx_alarm_handler emqx_rpc
|
emqx_vm_mon emqx_alarm_handler emqx_rpc emqx_flapping
|
||||||
|
|
||||||
CT_NODE_NAME = emqxct@127.0.0.1
|
CT_NODE_NAME = emqxct@127.0.0.1
|
||||||
CT_OPTS = -cover test/ct.cover.spec -erl_args -name $(CT_NODE_NAME)
|
CT_OPTS = -cover test/ct.cover.spec -erl_args -name $(CT_NODE_NAME)
|
||||||
|
|
|
@ -438,6 +438,17 @@ acl_cache_ttl = 1m
|
||||||
## Default: ignore
|
## Default: ignore
|
||||||
acl_deny_action = ignore
|
acl_deny_action = ignore
|
||||||
|
|
||||||
|
## The cleanning interval for flapping
|
||||||
|
##
|
||||||
|
## Value: Duration
|
||||||
|
## -d: day
|
||||||
|
## -h: hour
|
||||||
|
## -m: minute
|
||||||
|
## -s: second
|
||||||
|
##
|
||||||
|
## Default: 1h, 1 hour
|
||||||
|
## flapping_clean_interval = 1h
|
||||||
|
|
||||||
##--------------------------------------------------------------------
|
##--------------------------------------------------------------------
|
||||||
## MQTT Protocol
|
## MQTT Protocol
|
||||||
##--------------------------------------------------------------------
|
##--------------------------------------------------------------------
|
||||||
|
@ -650,11 +661,35 @@ zone.external.mqueue_priorities = none
|
||||||
## Value: highest | lowest
|
## Value: highest | lowest
|
||||||
zone.external.mqueue_default_priority = highest
|
zone.external.mqueue_default_priority = highest
|
||||||
|
|
||||||
## Whether to enqueue Qos0 messages.
|
## Whether to enqueue QoS0 messages.
|
||||||
##
|
##
|
||||||
## Value: false | true
|
## Value: false | true
|
||||||
zone.external.mqueue_store_qos0 = true
|
zone.external.mqueue_store_qos0 = true
|
||||||
|
|
||||||
|
## Whether to turn on flapping detect
|
||||||
|
##
|
||||||
|
## Value: on | off
|
||||||
|
zone.external.enable_flapping_detect = off
|
||||||
|
|
||||||
|
## The times of state change per min, specifying the threshold which is used to
|
||||||
|
## detect if the connection starts flapping
|
||||||
|
##
|
||||||
|
## Value: number
|
||||||
|
zone.external.flapping_threshold = 10, 1m
|
||||||
|
|
||||||
|
## Flapping expiry interval for connections.
|
||||||
|
## This config entry is used to determine when the connection
|
||||||
|
## will be unbanned.
|
||||||
|
##
|
||||||
|
## Value: Duration
|
||||||
|
## -d: day
|
||||||
|
## -h: hour
|
||||||
|
## -m: minute
|
||||||
|
## -s: second
|
||||||
|
##
|
||||||
|
## Default: 1h, 1 hour
|
||||||
|
zone.external.flapping_expiry_interval = 1h
|
||||||
|
|
||||||
## All the topics will be prefixed with the mountpoint path if this option is enabled.
|
## All the topics will be prefixed with the mountpoint path if this option is enabled.
|
||||||
##
|
##
|
||||||
## Variables in mountpoint path:
|
## Variables in mountpoint path:
|
||||||
|
@ -726,6 +761,30 @@ zone.internal.max_mqueue_len = 1000
|
||||||
## Value: false | true
|
## Value: false | true
|
||||||
zone.internal.mqueue_store_qos0 = true
|
zone.internal.mqueue_store_qos0 = true
|
||||||
|
|
||||||
|
## Whether to turn on flapping detect
|
||||||
|
##
|
||||||
|
## Value: on | off
|
||||||
|
zone.internal.enable_flapping_detect = off
|
||||||
|
|
||||||
|
## The times of state change per second, specifying the threshold which is used to
|
||||||
|
## detect if the connection starts flapping
|
||||||
|
##
|
||||||
|
## Value: number
|
||||||
|
zone.internal.flapping_threshold = 10, 1m
|
||||||
|
|
||||||
|
## Flapping expiry interval for connections.
|
||||||
|
## This config entry is used to determine when the connection
|
||||||
|
## will be unbanned.
|
||||||
|
##
|
||||||
|
## Value: Duration
|
||||||
|
## -d: day
|
||||||
|
## -h: hour
|
||||||
|
## -m: minute
|
||||||
|
## -s: second
|
||||||
|
##
|
||||||
|
## Default: 1h, 1 hour
|
||||||
|
zone.internal.flapping_expiry_interval = 1h
|
||||||
|
|
||||||
## All the topics will be prefixed with the mountpoint path if this option is enabled.
|
## All the topics will be prefixed with the mountpoint path if this option is enabled.
|
||||||
##
|
##
|
||||||
## Variables in mountpoint path:
|
## Variables in mountpoint path:
|
||||||
|
@ -1784,13 +1843,13 @@ listener.wss.external.send_timeout_close = on
|
||||||
## SSL Ciphers used by the bridge.
|
## SSL Ciphers used by the bridge.
|
||||||
##
|
##
|
||||||
## Value: String
|
## Value: String
|
||||||
#bridge.aws.ciphers = ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384
|
## bridge.aws.ciphers = ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384
|
||||||
|
|
||||||
## Ciphers for TLS PSK.
|
## Ciphers for TLS PSK.
|
||||||
## Note that 'listener.ssl.external.ciphers' and 'listener.ssl.external.psk_ciphers' cannot
|
## Note that 'listener.ssl.external.ciphers' and 'listener.ssl.external.psk_ciphers' cannot
|
||||||
## be configured at the same time.
|
## be configured at the same time.
|
||||||
## See 'https://tools.ietf.org/html/rfc4279#section-2'.
|
## See 'https://tools.ietf.org/html/rfc4279#section-2'.
|
||||||
#bridge.aws.psk_ciphers = PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA
|
## bridge.aws.psk_ciphers = PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA
|
||||||
|
|
||||||
## Ping interval of a down bridge.
|
## Ping interval of a down bridge.
|
||||||
##
|
##
|
||||||
|
|
|
@ -19,4 +19,3 @@
|
||||||
-type(ok_or_error(Reason) :: ok | {error, Reason}).
|
-type(ok_or_error(Reason) :: ok | {error, Reason}).
|
||||||
|
|
||||||
-type(ok_or_error(Value, Reason) :: {ok, Value} | {error, Reason}).
|
-type(ok_or_error(Value, Reason) :: {ok, Value} | {error, Reason}).
|
||||||
|
|
||||||
|
|
|
@ -270,8 +270,7 @@ end}.
|
||||||
X when is_integer(X) -> cuttlefish_util:ceiling(X / 1024); %% Bytes to Kilobytes;
|
X when is_integer(X) -> cuttlefish_util:ceiling(X / 1024); %% Bytes to Kilobytes;
|
||||||
_ -> undefined
|
_ -> undefined
|
||||||
end
|
end
|
||||||
end
|
end}.
|
||||||
}.
|
|
||||||
|
|
||||||
{validator, "zdbbl_range", "must be between 1KB and 2097151KB",
|
{validator, "zdbbl_range", "must be between 1KB and 2097151KB",
|
||||||
fun(ZDBBL) ->
|
fun(ZDBBL) ->
|
||||||
|
@ -574,6 +573,11 @@ end}.
|
||||||
{datatype, {enum, [ignore, disconnect]}}
|
{datatype, {enum, [ignore, disconnect]}}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
|
%% @doc time interval to clean flapping records
|
||||||
|
{mapping, "flapping_clean_interval", "emqx.flapping_clean_interval", [
|
||||||
|
{datatype, {duration, ms}}
|
||||||
|
]}.
|
||||||
|
|
||||||
{validator, "range:gt_0", "must greater than 0",
|
{validator, "range:gt_0", "must greater than 0",
|
||||||
fun(X) -> X > 0 end
|
fun(X) -> X > 0 end
|
||||||
}.
|
}.
|
||||||
|
@ -814,6 +818,18 @@ end}.
|
||||||
{datatype, {enum, [true, false]}}
|
{datatype, {enum, [true, false]}}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
|
{mapping, "zone.$name.enable_flapping_detect", "emqx.zones", [
|
||||||
|
{datatype, flag}
|
||||||
|
]}.
|
||||||
|
|
||||||
|
{mapping, "zone.$name.flapping_threshold", "emqx.zones", [
|
||||||
|
{datatype, string}
|
||||||
|
]}.
|
||||||
|
|
||||||
|
{mapping, "zone.$name.flapping_expiry_interval", "emqx.zones", [
|
||||||
|
{datatype, {duration, s}}
|
||||||
|
]}.
|
||||||
|
|
||||||
%% @doc Force connection/session process GC after this number of
|
%% @doc Force connection/session process GC after this number of
|
||||||
%% messages | bytes passed through.
|
%% messages | bytes passed through.
|
||||||
%% Numbers delimited by `|'. Zero or negative is to disable.
|
%% Numbers delimited by `|'. Zero or negative is to disable.
|
||||||
|
@ -845,6 +861,15 @@ end}.
|
||||||
{translation, "emqx.zones", fun(Conf) ->
|
{translation, "emqx.zones", fun(Conf) ->
|
||||||
Mapping = fun("retain_available", Val) ->
|
Mapping = fun("retain_available", Val) ->
|
||||||
{mqtt_retain_available, Val};
|
{mqtt_retain_available, Val};
|
||||||
|
("flapping_threshold", Val) ->
|
||||||
|
[Limit, Duration] = string:tokens(Val, ", "),
|
||||||
|
FlappingThreshold = case cuttlefish_duration:parse(Duration, s) of
|
||||||
|
Min when is_integer(Min) ->
|
||||||
|
{list_to_integer(Limit), Min};
|
||||||
|
{error, Reason} ->
|
||||||
|
error(Reason)
|
||||||
|
end,
|
||||||
|
{flapping_threshold, FlappingThreshold};
|
||||||
("wildcard_subscription", Val) ->
|
("wildcard_subscription", Val) ->
|
||||||
{mqtt_wildcard_subscription, Val};
|
{mqtt_wildcard_subscription, Val};
|
||||||
("shared_subscription", Val) ->
|
("shared_subscription", Val) ->
|
||||||
|
@ -2053,11 +2078,8 @@ end}.
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{translation, "emqx.sysmon", fun(Conf) ->
|
{translation, "emqx.sysmon", fun(Conf) ->
|
||||||
[{long_gc, cuttlefish:conf_get("sysmon.long_gc", Conf)},
|
Configs = cuttlefish_variable:filter_by_prefix("sysmon", Conf),
|
||||||
{long_schedule, cuttlefish:conf_get("sysmon.long_schedule", Conf)},
|
[{list_to_atom(Name), Value} || {[_, Name], Value} <- Configs]
|
||||||
{large_heap, cuttlefish:conf_get("sysmon.large_heap", Conf)},
|
|
||||||
{busy_port, cuttlefish:conf_get("sysmon.busy_port", Conf)},
|
|
||||||
{busy_dist_port, cuttlefish:conf_get("sysmon.busy_dist_port", Conf)}]
|
|
||||||
end}.
|
end}.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -2095,12 +2117,8 @@ end}.
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{translation, "emqx.os_mon", fun(Conf) ->
|
{translation, "emqx.os_mon", fun(Conf) ->
|
||||||
[{cpu_check_interval, cuttlefish:conf_get("os_mon.cpu_check_interval", Conf)},
|
Configs = cuttlefish_variable:filter_by_prefix("os_mon", Conf),
|
||||||
{cpu_high_watermark, cuttlefish:conf_get("os_mon.cpu_high_watermark", Conf)},
|
[{list_to_atom(Name), Value} || {[_, Name], Value} <- Configs]
|
||||||
{cpu_low_watermark, cuttlefish:conf_get("os_mon.cpu_low_watermark", Conf)},
|
|
||||||
{mem_check_interval, cuttlefish:conf_get("os_mon.mem_check_interval", Conf)},
|
|
||||||
{sysmem_high_watermark, cuttlefish:conf_get("os_mon.sysmem_high_watermark", Conf)},
|
|
||||||
{procmem_high_watermark, cuttlefish:conf_get("os_mon.procmem_high_watermark", Conf)}]
|
|
||||||
end}.
|
end}.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -2122,7 +2140,6 @@ end}.
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{translation, "emqx.vm_mon", fun(Conf) ->
|
{translation, "emqx.vm_mon", fun(Conf) ->
|
||||||
[{check_interval, cuttlefish:conf_get("vm_mon.check_interval", Conf)},
|
Configs = cuttlefish_variable:filter_by_prefix("vm_mon", Conf),
|
||||||
{process_high_watermark, cuttlefish:conf_get("vm_mon.process_high_watermark", Conf)},
|
[{list_to_atom(Name), Value} || {[_, Name], Value} <- Configs]
|
||||||
{process_low_watermark, cuttlefish:conf_get("vm_mon.process_low_watermark", Conf)}]
|
|
||||||
end}.
|
end}.
|
||||||
|
|
|
@ -70,13 +70,13 @@ check(#{client_id := ClientId, username := Username, peername := {IPAddr, _}}) -
|
||||||
orelse ets:member(?TAB, {username, Username})
|
orelse ets:member(?TAB, {username, Username})
|
||||||
orelse ets:member(?TAB, {ipaddr, IPAddr}).
|
orelse ets:member(?TAB, {ipaddr, IPAddr}).
|
||||||
|
|
||||||
-spec(add(#banned{}) -> ok).
|
-spec(add(emqx_types:banned()) -> ok).
|
||||||
add(Banned) when is_record(Banned, banned) ->
|
add(Banned) when is_record(Banned, banned) ->
|
||||||
mnesia:dirty_write(?TAB, Banned).
|
mnesia:dirty_write(?TAB, Banned).
|
||||||
|
|
||||||
-spec(delete({client_id, emqx_types:client_id()}
|
-spec(delete({client_id, emqx_types:client_id()}
|
||||||
| {username, emqx_types:username()}
|
| {username, emqx_types:username()}
|
||||||
| {peername, emqx_types:peername()}) -> ok).
|
| {peername, emqx_types:peername()}) -> ok).
|
||||||
delete(Key) ->
|
delete(Key) ->
|
||||||
mnesia:dirty_delete(?TAB, Key).
|
mnesia:dirty_delete(?TAB, Key).
|
||||||
|
|
||||||
|
@ -127,4 +127,3 @@ expire_banned_items(Now) ->
|
||||||
mnesia:delete_object(?TAB, B, sticky_write);
|
mnesia:delete_object(?TAB, B, sticky_write);
|
||||||
(_, _Acc) -> ok
|
(_, _Acc) -> ok
|
||||||
end, ok, ?TAB).
|
end, ok, ?TAB).
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,9 @@ start(Config = #{address := Address}) ->
|
||||||
ClientConfig = Config#{msg_handler => Handlers,
|
ClientConfig = Config#{msg_handler => Handlers,
|
||||||
owner => AckCollector,
|
owner => AckCollector,
|
||||||
host => Host,
|
host => Host,
|
||||||
port => Port},
|
port => Port,
|
||||||
|
bridge_mode => true
|
||||||
|
},
|
||||||
case emqx_client:start_link(ClientConfig) of
|
case emqx_client:start_link(ClientConfig) of
|
||||||
{ok, Pid} ->
|
{ok, Pid} ->
|
||||||
case emqx_client:connect(Pid) of
|
case emqx_client:connect(Pid) of
|
||||||
|
|
|
@ -88,7 +88,7 @@
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% Default timeout
|
%% Default timeout
|
||||||
-define(DEFAULT_KEEPALIVE, 60000).
|
-define(DEFAULT_KEEPALIVE, 60).
|
||||||
-define(DEFAULT_ACK_TIMEOUT, 30000).
|
-define(DEFAULT_ACK_TIMEOUT, 30000).
|
||||||
-define(DEFAULT_CONNECT_TIMEOUT, 60000).
|
-define(DEFAULT_CONNECT_TIMEOUT, 60000).
|
||||||
|
|
||||||
|
|
|
@ -30,11 +30,20 @@ init([]) ->
|
||||||
shutdown => 1000,
|
shutdown => 1000,
|
||||||
type => worker,
|
type => worker,
|
||||||
modules => [emqx_banned]},
|
modules => [emqx_banned]},
|
||||||
|
FlappingOption = emqx_config:get_env(flapping_clean_interval, 3600000),
|
||||||
|
Flapping = #{id => flapping,
|
||||||
|
start => {emqx_flapping, start_link, [FlappingOption]},
|
||||||
|
restart => permanent,
|
||||||
|
shutdown => 1000,
|
||||||
|
type => worker,
|
||||||
|
modules => [emqx_flapping]},
|
||||||
Manager = #{id => manager,
|
Manager = #{id => manager,
|
||||||
start => {emqx_cm, start_link, []},
|
start => {emqx_cm, start_link, []},
|
||||||
restart => permanent,
|
restart => permanent,
|
||||||
shutdown => 2000,
|
shutdown => 2000,
|
||||||
type => worker,
|
type => worker,
|
||||||
modules => [emqx_cm]},
|
modules => [emqx_cm]},
|
||||||
{ok, {{one_for_one, 10, 100}, [Banned, Manager]}}.
|
SupFlags = #{strategy => one_for_one,
|
||||||
|
intensity => 100,
|
||||||
|
period => 10},
|
||||||
|
{ok, {SupFlags, [Banned, Manager, Flapping]}}.
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
%% 1. Store in mnesia database?
|
%% 1. Store in mnesia database?
|
||||||
%% 2. Store in dets?
|
%% 2. Store in dets?
|
||||||
%% 3. Store in data/app.config?
|
%% 3. Store in data/app.config?
|
||||||
%%
|
|
||||||
|
|
||||||
-module(emqx_config).
|
-module(emqx_config).
|
||||||
|
|
||||||
|
@ -138,4 +137,3 @@ read_(_App) -> error(no_impl).
|
||||||
% end, [], Configs),
|
% end, [], Configs),
|
||||||
% RequiredCfg ++ OptionalCfg
|
% RequiredCfg ++ OptionalCfg
|
||||||
% end.
|
% end.
|
||||||
|
|
||||||
|
|
|
@ -242,10 +242,10 @@ connected(info, {deliver, PubOrAck}, State = #state{proto_state = ProtoState}) -
|
||||||
connected(info, {keepalive, start, Interval},
|
connected(info, {keepalive, start, Interval},
|
||||||
State = #state{transport = Transport, socket = Socket}) ->
|
State = #state{transport = Transport, socket = Socket}) ->
|
||||||
StatFun = fun() ->
|
StatFun = fun() ->
|
||||||
case Transport:getstat(Socket, [recv_oct]) of
|
case Transport:getstat(Socket, [recv_oct]) of
|
||||||
{ok, [{recv_oct, RecvOct}]} -> {ok, RecvOct};
|
{ok, [{recv_oct, RecvOct}]} -> {ok, RecvOct};
|
||||||
Error -> Error
|
Error -> Error
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
case emqx_keepalive:start(StatFun, Interval, {keepalive, check}) of
|
case emqx_keepalive:start(StatFun, Interval, {keepalive, check}) of
|
||||||
{ok, KeepAlive} ->
|
{ok, KeepAlive} ->
|
||||||
|
|
|
@ -12,70 +12,150 @@
|
||||||
%% See the License for the specific language governing permissions and
|
%% See the License for the specific language governing permissions and
|
||||||
%% limitations under the License.
|
%% limitations under the License.
|
||||||
|
|
||||||
%% @doc TODO:
|
|
||||||
%% 1. Flapping Detection
|
|
||||||
%% 2. Conflict Detection?
|
|
||||||
-module(emqx_flapping).
|
-module(emqx_flapping).
|
||||||
|
|
||||||
%% Use ets:update_counter???
|
-include("emqx.hrl").
|
||||||
|
-include("logger.hrl").
|
||||||
|
-include("types.hrl").
|
||||||
|
|
||||||
-behaviour(gen_server).
|
-behaviour(gen_statem).
|
||||||
|
|
||||||
-export([start_link/0]).
|
-export([start_link/1]).
|
||||||
|
|
||||||
-export([ is_banned/1
|
%% This module is used to garbage clean the flapping records
|
||||||
, banned/1
|
|
||||||
|
%% gen_statem callbacks
|
||||||
|
-export([ terminate/3
|
||||||
|
, code_change/4
|
||||||
|
, init/1
|
||||||
|
, initialized/3
|
||||||
|
, callback_mode/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% gen_server callbacks
|
-define(FLAPPING_TAB, ?MODULE).
|
||||||
-export([ init/1
|
|
||||||
, handle_call/3
|
|
||||||
, handle_cast/2
|
|
||||||
, handle_info/2
|
|
||||||
, terminate/2
|
|
||||||
, code_change/3
|
|
||||||
]).
|
|
||||||
|
|
||||||
-define(SERVER, ?MODULE).
|
-export([check/3]).
|
||||||
|
|
||||||
-record(state, {}).
|
-record(flapping,
|
||||||
|
{ client_id :: binary()
|
||||||
|
, check_count :: integer()
|
||||||
|
, timestamp :: integer()
|
||||||
|
}).
|
||||||
|
|
||||||
-spec(start_link() -> {ok, pid()} | ignore | {error, any()}).
|
-type(flapping_record() :: #flapping{}).
|
||||||
start_link() ->
|
-type(flapping_state() :: flapping | ok).
|
||||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
|
||||||
|
|
||||||
is_banned(ClientId) ->
|
|
||||||
ets:member(banned, ClientId).
|
|
||||||
|
|
||||||
banned(ClientId) ->
|
%% @doc This function is used to initialize flapping records
|
||||||
ets:insert(banned, {ClientId, os:timestamp()}).
|
%% the expiry time unit is minutes.
|
||||||
|
-spec(init_flapping(ClientId :: binary(), Interval :: integer()) -> flapping_record()).
|
||||||
|
init_flapping(ClientId, Interval) ->
|
||||||
|
#flapping{ client_id = ClientId
|
||||||
|
, check_count = 1
|
||||||
|
, timestamp = emqx_time:now_secs() + Interval
|
||||||
|
}.
|
||||||
|
|
||||||
|
%% @doc This function is used to initialize flapping records
|
||||||
|
%% the expiry time unit is minutes.
|
||||||
|
-spec(check( Action :: atom()
|
||||||
|
, ClientId :: binary()
|
||||||
|
, Threshold :: {integer(), integer()})
|
||||||
|
-> flapping_state()).
|
||||||
|
check(Action, ClientId, Threshold = {_TimesThreshold, TimeInterval}) ->
|
||||||
|
check(Action, ClientId, Threshold, init_flapping(ClientId, TimeInterval)).
|
||||||
|
|
||||||
|
-spec(check( Action :: atom()
|
||||||
|
, ClientId :: binary()
|
||||||
|
, Threshold :: {integer(), integer()}
|
||||||
|
, InitFlapping :: flapping_record())
|
||||||
|
-> flapping_state()).
|
||||||
|
check(Action, ClientId, Threshold, InitFlapping) ->
|
||||||
|
try ets:update_counter(?FLAPPING_TAB, ClientId, {_Pos = #flapping.check_count, 1}) of
|
||||||
|
CheckCount ->
|
||||||
|
case ets:lookup(?FLAPPING_TAB, ClientId) of
|
||||||
|
[Flapping] ->
|
||||||
|
check_flapping(Action, CheckCount, Threshold, Flapping);
|
||||||
|
_Flapping ->
|
||||||
|
ok
|
||||||
|
end
|
||||||
|
catch
|
||||||
|
error:badarg ->
|
||||||
|
ets:insert_new(?FLAPPING_TAB, InitFlapping),
|
||||||
|
ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec(check_flapping( Action :: atom()
|
||||||
|
, CheckTimes :: integer()
|
||||||
|
, Threshold :: {integer(), integer()}
|
||||||
|
, InitFlapping :: flapping_record())
|
||||||
|
-> flapping_state()).
|
||||||
|
check_flapping(Action, CheckTimes, _Threshold = {TimesThreshold, TimeInterval},
|
||||||
|
Flapping = #flapping{ client_id = ClientId
|
||||||
|
, timestamp = Timestamp }) ->
|
||||||
|
case emqx_time:now_secs() of
|
||||||
|
NowTimestamp when NowTimestamp =< Timestamp,
|
||||||
|
CheckTimes > TimesThreshold ->
|
||||||
|
ets:delete(?FLAPPING_TAB, ClientId),
|
||||||
|
flapping;
|
||||||
|
NowTimestamp when NowTimestamp > Timestamp,
|
||||||
|
Action =:= disconnect ->
|
||||||
|
ets:delete(?FLAPPING_TAB, ClientId),
|
||||||
|
ok;
|
||||||
|
NowTimestamp ->
|
||||||
|
NewFlapping = Flapping#flapping{timestamp = NowTimestamp + TimeInterval},
|
||||||
|
ets:insert(?FLAPPING_TAB, NewFlapping),
|
||||||
|
ok
|
||||||
|
end.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% gen_server callbacks
|
%% gen_statem callbacks
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
-spec(start_link(TimerInterval :: integer()) -> startlink_ret()).
|
||||||
|
start_link(TimerInterval) ->
|
||||||
|
gen_statem:start_link({local, ?MODULE}, ?MODULE, [TimerInterval], []).
|
||||||
|
|
||||||
init([]) ->
|
init([TimerInterval]) ->
|
||||||
%% ets:new(banned, [public, ordered_set, named_table]),
|
TabOpts = [ public
|
||||||
{ok, #state{}}.
|
, set
|
||||||
|
, {keypos, 2}
|
||||||
|
, {write_concurrency, true}
|
||||||
|
, {read_concurrency, true}],
|
||||||
|
ok = emqx_tables:new(?FLAPPING_TAB, TabOpts),
|
||||||
|
{ok, initialized, #{timer_interval => TimerInterval}}.
|
||||||
|
|
||||||
handle_call(_Request, _From, State) ->
|
callback_mode() -> [state_functions, state_enter].
|
||||||
Reply = ok,
|
|
||||||
{reply, Reply, State}.
|
|
||||||
|
|
||||||
handle_cast(_Msg, State) ->
|
initialized(enter, _OldState, #{timer_interval := Time}) ->
|
||||||
{noreply, State}.
|
Action = {state_timeout, Time, clean_expired_records},
|
||||||
|
{keep_state_and_data, Action};
|
||||||
|
initialized(state_timeout, clean_expired_records, #{}) ->
|
||||||
|
clean_expired_records(),
|
||||||
|
repeat_state_and_data.
|
||||||
|
|
||||||
handle_info(_Info, State) ->
|
code_change(_Vsn, State, Data, _Extra) ->
|
||||||
{noreply, State}.
|
{ok, State, Data}.
|
||||||
|
|
||||||
terminate(_Reason, _State) ->
|
terminate(_Reason, _StateName, _State) ->
|
||||||
|
emqx_tables:delete(?FLAPPING_TAB),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
code_change(_OldVsn, State, _Extra) ->
|
|
||||||
{ok, State}.
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Internal functions
|
%% Internal functions
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
%% @doc clean expired records in ets
|
||||||
|
clean_expired_records() ->
|
||||||
|
Records = ets:tab2list(?FLAPPING_TAB),
|
||||||
|
traverse_records(Records).
|
||||||
|
|
||||||
|
traverse_records([]) ->
|
||||||
|
ok;
|
||||||
|
traverse_records([#flapping{client_id = ClientId,
|
||||||
|
timestamp = Timestamp} | LeftRecords]) ->
|
||||||
|
case emqx_time:now_secs() > Timestamp of
|
||||||
|
true ->
|
||||||
|
ets:delete(?FLAPPING_TAB, ClientId);
|
||||||
|
false ->
|
||||||
|
true
|
||||||
|
end,
|
||||||
|
traverse_records(LeftRecords).
|
||||||
|
|
|
@ -141,16 +141,16 @@ parse_packet(#mqtt_packet_header{type = ?CONNECT}, FrameBin, _Options) ->
|
||||||
|
|
||||||
{Properties, Rest3} = parse_properties(Rest2, ProtoVer),
|
{Properties, Rest3} = parse_properties(Rest2, ProtoVer),
|
||||||
{ClientId, Rest4} = parse_utf8_string(Rest3),
|
{ClientId, Rest4} = parse_utf8_string(Rest3),
|
||||||
ConnPacket = #mqtt_packet_connect{proto_name = ProtoName,
|
ConnPacket = #mqtt_packet_connect{proto_name = ProtoName,
|
||||||
proto_ver = ProtoVer,
|
proto_ver = ProtoVer,
|
||||||
is_bridge = (BridgeTag =:= 8),
|
is_bridge = (BridgeTag =:= 8),
|
||||||
clean_start = bool(CleanStart),
|
clean_start = bool(CleanStart),
|
||||||
will_flag = bool(WillFlag),
|
will_flag = bool(WillFlag),
|
||||||
will_qos = WillQoS,
|
will_qos = WillQoS,
|
||||||
will_retain = bool(WillRetain),
|
will_retain = bool(WillRetain),
|
||||||
keepalive = KeepAlive,
|
keepalive = KeepAlive,
|
||||||
properties = Properties,
|
properties = Properties,
|
||||||
client_id = ClientId},
|
client_id = ClientId},
|
||||||
{ConnPacket1, Rest5} = parse_will_message(ConnPacket, Rest4),
|
{ConnPacket1, Rest5} = parse_will_message(ConnPacket, Rest4),
|
||||||
{Username, Rest6} = parse_utf8_string(Rest5, bool(UsernameFlag)),
|
{Username, Rest6} = parse_utf8_string(Rest5, bool(UsernameFlag)),
|
||||||
{Passsword, <<>>} = parse_utf8_string(Rest6, bool(PasswordFlag)),
|
{Passsword, <<>>} = parse_utf8_string(Rest6, bool(PasswordFlag)),
|
||||||
|
|
|
@ -60,6 +60,7 @@
|
||||||
is_bridge,
|
is_bridge,
|
||||||
enable_ban,
|
enable_ban,
|
||||||
enable_acl,
|
enable_acl,
|
||||||
|
enable_flapping_detect,
|
||||||
acl_deny_action,
|
acl_deny_action,
|
||||||
recv_stats,
|
recv_stats,
|
||||||
send_stats,
|
send_stats,
|
||||||
|
@ -90,31 +91,32 @@ init(SocketOpts = #{ peername := Peername
|
||||||
, peercert := Peercert
|
, peercert := Peercert
|
||||||
, sendfun := SendFun}, Options) ->
|
, sendfun := SendFun}, Options) ->
|
||||||
Zone = proplists:get_value(zone, Options),
|
Zone = proplists:get_value(zone, Options),
|
||||||
#pstate{zone = Zone,
|
#pstate{zone = Zone,
|
||||||
sendfun = SendFun,
|
sendfun = SendFun,
|
||||||
peername = Peername,
|
peername = Peername,
|
||||||
peercert = Peercert,
|
peercert = Peercert,
|
||||||
proto_ver = ?MQTT_PROTO_V4,
|
proto_ver = ?MQTT_PROTO_V4,
|
||||||
proto_name = <<"MQTT">>,
|
proto_name = <<"MQTT">>,
|
||||||
client_id = <<>>,
|
client_id = <<>>,
|
||||||
is_assigned = false,
|
is_assigned = false,
|
||||||
conn_pid = self(),
|
conn_pid = self(),
|
||||||
username = init_username(Peercert, Options),
|
username = init_username(Peercert, Options),
|
||||||
clean_start = false,
|
clean_start = false,
|
||||||
topic_aliases = #{},
|
topic_aliases = #{},
|
||||||
packet_size = emqx_zone:get_env(Zone, max_packet_size),
|
packet_size = emqx_zone:get_env(Zone, max_packet_size),
|
||||||
is_bridge = false,
|
is_bridge = false,
|
||||||
enable_ban = emqx_zone:get_env(Zone, enable_ban, false),
|
enable_ban = emqx_zone:get_env(Zone, enable_ban, false),
|
||||||
enable_acl = emqx_zone:get_env(Zone, enable_acl),
|
enable_acl = emqx_zone:get_env(Zone, enable_acl),
|
||||||
acl_deny_action = emqx_zone:get_env(Zone, acl_deny_action, ignore),
|
enable_flapping_detect = emqx_zone:get_env(Zone, enable_flapping_detect, false),
|
||||||
recv_stats = #{msg => 0, pkt => 0},
|
acl_deny_action = emqx_zone:get_env(Zone, acl_deny_action, ignore),
|
||||||
send_stats = #{msg => 0, pkt => 0},
|
recv_stats = #{msg => 0, pkt => 0},
|
||||||
connected = false,
|
send_stats = #{msg => 0, pkt => 0},
|
||||||
ignore_loop = emqx_config:get_env(mqtt_ignore_loop_deliver, false),
|
connected = false,
|
||||||
topic_alias_maximum = #{to_client => 0, from_client => 0},
|
ignore_loop = emqx_config:get_env(mqtt_ignore_loop_deliver, false),
|
||||||
conn_mod = maps:get(conn_mod, SocketOpts, undefined),
|
topic_alias_maximum = #{to_client => 0, from_client => 0},
|
||||||
credentials = #{},
|
conn_mod = maps:get(conn_mod, SocketOpts, undefined),
|
||||||
ws_cookie = maps:get(ws_cookie, SocketOpts, undefined)}.
|
credentials = #{},
|
||||||
|
ws_cookie = maps:get(ws_cookie, SocketOpts, undefined)}.
|
||||||
|
|
||||||
init_username(Peercert, Options) ->
|
init_username(Peercert, Options) ->
|
||||||
case proplists:get_value(peer_cert_as_username, Options) of
|
case proplists:get_value(peer_cert_as_username, Options) of
|
||||||
|
@ -766,6 +768,7 @@ make_will_msg(#mqtt_packet_connect{proto_ver = ProtoVer,
|
||||||
check_connect(Packet, PState) ->
|
check_connect(Packet, PState) ->
|
||||||
run_check_steps([fun check_proto_ver/2,
|
run_check_steps([fun check_proto_ver/2,
|
||||||
fun check_client_id/2,
|
fun check_client_id/2,
|
||||||
|
fun check_flapping/2,
|
||||||
fun check_banned/2,
|
fun check_banned/2,
|
||||||
fun check_will_topic/2], Packet, PState).
|
fun check_will_topic/2], Packet, PState).
|
||||||
|
|
||||||
|
@ -798,6 +801,9 @@ check_client_id(#mqtt_packet_connect{client_id = ClientId}, #pstate{zone = Zone}
|
||||||
false -> {error, ?RC_CLIENT_IDENTIFIER_NOT_VALID}
|
false -> {error, ?RC_CLIENT_IDENTIFIER_NOT_VALID}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
check_flapping(#mqtt_packet_connect{}, PState) ->
|
||||||
|
do_flapping_detect(connect, PState).
|
||||||
|
|
||||||
check_banned(_ConnPkt, #pstate{enable_ban = false}) ->
|
check_banned(_ConnPkt, #pstate{enable_ban = false}) ->
|
||||||
ok;
|
ok;
|
||||||
check_banned(#mqtt_packet_connect{client_id = ClientId, username = Username},
|
check_banned(#mqtt_packet_connect{client_id = ClientId, username = Username},
|
||||||
|
@ -896,14 +902,16 @@ inc_stats(Type, Stats = #{pkt := PktCnt, msg := MsgCnt}) ->
|
||||||
|
|
||||||
terminate(_Reason, #pstate{client_id = undefined}) ->
|
terminate(_Reason, #pstate{client_id = undefined}) ->
|
||||||
ok;
|
ok;
|
||||||
terminate(_Reason, #pstate{connected = false}) ->
|
terminate(_Reason, PState = #pstate{connected = false}) ->
|
||||||
|
do_flapping_detect(disconnect, PState),
|
||||||
ok;
|
ok;
|
||||||
terminate(conflict, _PState) ->
|
terminate(Reason, PState) when Reason =:= conflict;
|
||||||
ok;
|
Reason =:= discard ->
|
||||||
terminate(discard, _PState) ->
|
do_flapping_detect(disconnect, PState),
|
||||||
ok;
|
ok;
|
||||||
|
|
||||||
terminate(Reason, #pstate{credentials = Credentials}) ->
|
terminate(Reason, PState = #pstate{credentials = Credentials}) ->
|
||||||
|
do_flapping_detect(disconnect, PState),
|
||||||
?LOG(info, "[Protocol] Shutdown for ~p", [Reason]),
|
?LOG(info, "[Protocol] Shutdown for ~p", [Reason]),
|
||||||
ok = emqx_hooks:run('client.disconnected', [Credentials, Reason]).
|
ok = emqx_hooks:run('client.disconnected', [Credentials, Reason]).
|
||||||
|
|
||||||
|
@ -932,6 +940,26 @@ flag(true) -> 1.
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Execute actions in case acl deny
|
%% Execute actions in case acl deny
|
||||||
|
|
||||||
|
do_flapping_detect(Action, #pstate{zone = Zone,
|
||||||
|
client_id = ClientId,
|
||||||
|
enable_flapping_detect = true}) ->
|
||||||
|
ExpiryInterval = emqx_zone:get_env(Zone, flapping_expiry_interval, 3600000),
|
||||||
|
Threshold = emqx_zone:get_env(Zone, flapping_threshold, 20),
|
||||||
|
Until = erlang:system_time(second) + ExpiryInterval,
|
||||||
|
case emqx_flapping:check(Action, ClientId, Threshold) of
|
||||||
|
flapping ->
|
||||||
|
emqx_banned:add(#banned{who = {client_id, ClientId},
|
||||||
|
reason = <<"flapping">>,
|
||||||
|
by = <<"flapping_checker">>,
|
||||||
|
until = Until
|
||||||
|
}),
|
||||||
|
ok;
|
||||||
|
_Other ->
|
||||||
|
ok
|
||||||
|
end;
|
||||||
|
do_flapping_detect(_Action, _PState) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
do_acl_deny_action(?PUBLISH_PACKET(?QOS_0, _Topic, _PacketId, _Payload),
|
do_acl_deny_action(?PUBLISH_PACKET(?QOS_0, _Topic, _PacketId, _Payload),
|
||||||
?RC_NOT_AUTHORIZED, PState = #pstate{proto_ver = ProtoVer,
|
?RC_NOT_AUTHORIZED, PState = #pstate{proto_ver = ProtoVer,
|
||||||
acl_deny_action = disconnect}) ->
|
acl_deny_action = disconnect}) ->
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
-module(emqx_tables).
|
-module(emqx_tables).
|
||||||
|
|
||||||
-export([new/2]).
|
-export([new/2, delete/1]).
|
||||||
|
|
||||||
-export([ lookup_value/2
|
-export([ lookup_value/2
|
||||||
, lookup_value/3
|
, lookup_value/3
|
||||||
|
@ -30,6 +30,16 @@ new(Tab, Opts) ->
|
||||||
Tab -> ok
|
Tab -> ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
-spec(delete(atom()) -> ok).
|
||||||
|
delete(Tab) ->
|
||||||
|
case ets:info(Tab, name) of
|
||||||
|
undefined ->
|
||||||
|
ok;
|
||||||
|
Tab ->
|
||||||
|
ets:delete(Tab),
|
||||||
|
ok
|
||||||
|
end.
|
||||||
|
|
||||||
%% KV lookup
|
%% KV lookup
|
||||||
-spec(lookup_value(atom(), term()) -> any()).
|
-spec(lookup_value(atom(), term()) -> any()).
|
||||||
lookup_value(Tab, Key) ->
|
lookup_value(Tab, Key) ->
|
||||||
|
@ -42,4 +52,3 @@ lookup_value(Tab, Key, Def) ->
|
||||||
catch
|
catch
|
||||||
error:badarg -> Def
|
error:badarg -> Def
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
|
|
||||||
-export_type([ alarm/0
|
-export_type([ alarm/0
|
||||||
, plugin/0
|
, plugin/0
|
||||||
|
, banned/0
|
||||||
, command/0
|
, command/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
@ -91,6 +92,7 @@
|
||||||
-type(topic_table() :: [{topic(), subopts()}]).
|
-type(topic_table() :: [{topic(), subopts()}]).
|
||||||
-type(payload() :: binary() | iodata()).
|
-type(payload() :: binary() | iodata()).
|
||||||
-type(message() :: #message{}).
|
-type(message() :: #message{}).
|
||||||
|
-type(banned() :: #banned{}).
|
||||||
-type(delivery() :: #delivery{}).
|
-type(delivery() :: #delivery{}).
|
||||||
-type(deliver_results() :: [{route, node(), topic()} |
|
-type(deliver_results() :: [{route, node(), topic()} |
|
||||||
{dispatch, topic(), pos_integer()}]).
|
{dispatch, topic(), pos_integer()}]).
|
||||||
|
@ -98,4 +100,3 @@
|
||||||
-type(alarm() :: #alarm{}).
|
-type(alarm() :: #alarm{}).
|
||||||
-type(plugin() :: #plugin{}).
|
-type(plugin() :: #plugin{}).
|
||||||
-type(command() :: #command{}).
|
-type(command() :: #command{}).
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,13 @@ groups() ->
|
||||||
[compile_rule,
|
[compile_rule,
|
||||||
match_rule]}].
|
match_rule]}].
|
||||||
|
|
||||||
|
init_per_suite(Config) ->
|
||||||
|
emqx_ct_broker_helpers:run_setup_steps(),
|
||||||
|
Config.
|
||||||
|
|
||||||
|
end_per_suite(_Config) ->
|
||||||
|
emqx_ct_broker_helpers:run_teadown_steps().
|
||||||
|
|
||||||
init_per_group(Group, Config) when Group =:= access_control;
|
init_per_group(Group, Config) when Group =:= access_control;
|
||||||
Group =:= access_control_cache_mode ->
|
Group =:= access_control_cache_mode ->
|
||||||
prepare_config(Group),
|
prepare_config(Group),
|
||||||
|
|
|
@ -62,6 +62,7 @@ run_setup_steps(Config) ->
|
||||||
NewConfig = generate_config(),
|
NewConfig = generate_config(),
|
||||||
lists:foreach(fun set_app_env/1, NewConfig),
|
lists:foreach(fun set_app_env/1, NewConfig),
|
||||||
set_bridge_env(),
|
set_bridge_env(),
|
||||||
|
|
||||||
{ok, _} = application:ensure_all_started(?APP),
|
{ok, _} = application:ensure_all_started(?APP),
|
||||||
set_log_level(Config),
|
set_log_level(Config),
|
||||||
Config.
|
Config.
|
||||||
|
@ -109,32 +110,32 @@ set_bridge_env() ->
|
||||||
change_opts(SslType) ->
|
change_opts(SslType) ->
|
||||||
{ok, Listeners} = application:get_env(?APP, listeners),
|
{ok, Listeners} = application:get_env(?APP, listeners),
|
||||||
NewListeners =
|
NewListeners =
|
||||||
lists:foldl(fun({Protocol, Port, Opts} = Listener, Acc) ->
|
lists:foldl(fun({Protocol, Port, Opts} = Listener, Acc) ->
|
||||||
case Protocol of
|
case Protocol of
|
||||||
ssl ->
|
ssl ->
|
||||||
SslOpts = proplists:get_value(ssl_options, Opts),
|
SslOpts = proplists:get_value(ssl_options, Opts),
|
||||||
Keyfile = local_path(["etc/certs", "key.pem"]),
|
Keyfile = local_path(["etc/certs", "key.pem"]),
|
||||||
Certfile = local_path(["etc/certs", "cert.pem"]),
|
Certfile = local_path(["etc/certs", "cert.pem"]),
|
||||||
TupleList1 = lists:keyreplace(keyfile, 1, SslOpts, {keyfile, Keyfile}),
|
TupleList1 = lists:keyreplace(keyfile, 1, SslOpts, {keyfile, Keyfile}),
|
||||||
TupleList2 = lists:keyreplace(certfile, 1, TupleList1, {certfile, Certfile}),
|
TupleList2 = lists:keyreplace(certfile, 1, TupleList1, {certfile, Certfile}),
|
||||||
TupleList3 =
|
TupleList3 =
|
||||||
case SslType of
|
case SslType of
|
||||||
ssl_twoway->
|
ssl_twoway->
|
||||||
CAfile = local_path(["etc", proplists:get_value(cacertfile, ?MQTT_SSL_TWOWAY)]),
|
CAfile = local_path(["etc", proplists:get_value(cacertfile, ?MQTT_SSL_TWOWAY)]),
|
||||||
MutSslList = lists:keyreplace(cacertfile, 1, ?MQTT_SSL_TWOWAY, {cacertfile, CAfile}),
|
MutSslList = lists:keyreplace(cacertfile, 1, ?MQTT_SSL_TWOWAY, {cacertfile, CAfile}),
|
||||||
lists:merge(TupleList2, MutSslList);
|
lists:merge(TupleList2, MutSslList);
|
||||||
_ ->
|
_ ->
|
||||||
lists:filter(fun ({cacertfile, _}) -> false;
|
lists:filter(fun ({cacertfile, _}) -> false;
|
||||||
({verify, _}) -> false;
|
({verify, _}) -> false;
|
||||||
({fail_if_no_peer_cert, _}) -> false;
|
({fail_if_no_peer_cert, _}) -> false;
|
||||||
(_) -> true
|
(_) -> true
|
||||||
end, TupleList2)
|
end, TupleList2)
|
||||||
end,
|
end,
|
||||||
[{Protocol, Port, lists:keyreplace(ssl_options, 1, Opts, {ssl_options, TupleList3})} | Acc];
|
[{Protocol, Port, lists:keyreplace(ssl_options, 1, Opts, {ssl_options, TupleList3})} | Acc];
|
||||||
_ ->
|
_ ->
|
||||||
[Listener | Acc]
|
[Listener | Acc]
|
||||||
end
|
end
|
||||||
end, [], Listeners),
|
end, [], Listeners),
|
||||||
application:set_env(?APP, listeners, NewListeners).
|
application:set_env(?APP, listeners, NewListeners).
|
||||||
|
|
||||||
client_ssl_twoway() ->
|
client_ssl_twoway() ->
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
%% Copyright (c) 2013-2019 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_flapping_SUITE).
|
||||||
|
|
||||||
|
-compile(export_all).
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
|
-include("emqx.hrl").
|
||||||
|
|
||||||
|
-include_lib("common_test/include/ct.hrl").
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
all() ->
|
||||||
|
[t_flapping].
|
||||||
|
|
||||||
|
init_per_suite(Config) ->
|
||||||
|
emqx_ct_broker_helpers:run_setup_steps(),
|
||||||
|
prepare_for_test(),
|
||||||
|
Config.
|
||||||
|
|
||||||
|
end_per_suite(_Config) ->
|
||||||
|
emqx_ct_broker_helpers:run_teardown_steps().
|
||||||
|
|
||||||
|
t_flapping(_Config) ->
|
||||||
|
process_flag(trap_exit, true),
|
||||||
|
flapping_connect(5),
|
||||||
|
{ok, C} = emqx_client:start_link([{client_id, <<"Client">>}]),
|
||||||
|
{error, _} = emqx_client:connect(C),
|
||||||
|
receive
|
||||||
|
{'EXIT', Client, _Reason} ->
|
||||||
|
ct:log("receive exit signal, Client: ~p", [Client])
|
||||||
|
after 1000 ->
|
||||||
|
ct:log("timeout")
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
flapping_connect(Times) ->
|
||||||
|
[flapping_connect() || _ <- lists:seq(1, Times)].
|
||||||
|
|
||||||
|
flapping_connect() ->
|
||||||
|
{ok, C} = emqx_client:start_link([{client_id, <<"Client">>}]),
|
||||||
|
{ok, _} = emqx_client:connect(C),
|
||||||
|
ok = emqx_client:disconnect(C).
|
||||||
|
|
||||||
|
prepare_for_test() ->
|
||||||
|
emqx_zone:set_env(external, enable_flapping_detect, true),
|
||||||
|
emqx_zone:set_env(external, flapping_threshold, {10, 60}),
|
||||||
|
emqx_zone:set_env(external, flapping_expiry_interval, 3600).
|
|
@ -23,4 +23,6 @@ t_new(_) ->
|
||||||
ok = emqx_tables:new(test_table, [{read_concurrency, true}]),
|
ok = emqx_tables:new(test_table, [{read_concurrency, true}]),
|
||||||
ets:insert(test_table, {key, 100}),
|
ets:insert(test_table, {key, 100}),
|
||||||
ok = emqx_tables:new(test_table, [{read_concurrency, true}]),
|
ok = emqx_tables:new(test_table, [{read_concurrency, true}]),
|
||||||
100 = ets:lookup_element(test_table, key, 2).
|
100 = ets:lookup_element(test_table, key, 2),
|
||||||
|
ok = emqx_tables:delete(test_table),
|
||||||
|
ok = emqx_tables:delete(test_table).
|
||||||
|
|
|
@ -35,4 +35,3 @@ t_set_get_env(_) ->
|
||||||
emqx_zone:force_reload(),
|
emqx_zone:force_reload(),
|
||||||
?assertEqual(val, emqx_zone:get_env(zone1, key)),
|
?assertEqual(val, emqx_zone:get_env(zone1, key)),
|
||||||
emqx_zone:stop().
|
emqx_zone:stop().
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue