./ct_report/coverage/mod_ping.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : mod_ping.erl
3 %%% Author : Piotr Nosek <piotr.nosek@erlang-solutions.com>
4 %%% Purpose : XEP-0199 XMPP Ping implementation
5 %%% Created : 14 Nov 2019 by Piotr Nosek <piotr.nosek@erlang-solutions.com>
6 %%%----------------------------------------------------------------------
7
8 -module(mod_ping).
9 -author('piotr.nosek@erlang-solutions.com').
10
11 -behavior(gen_mod).
12 -xep([{xep, 199}, {version, "2.0"}]).
13 -include("mongoose.hrl").
14 -include("jlib.hrl").
15 -include("mongoose_config_spec.hrl").
16
17 -define(DEFAULT_SEND_PINGS, false). % bool()
18 -define(DEFAULT_PING_INTERVAL, (60*1000)). % 60 seconds
19 -define(DEFAULT_PING_REQ_TIMEOUT, (32*1000)).% 32 seconds
20
21 %% gen_mod callbacks
22 -export([start/2,
23 stop/1,
24 config_spec/0,
25 supported_features/0]).
26
27 %% Hook callbacks
28 -export([iq_ping/5,
29 user_online/5,
30 user_offline/5,
31 user_send/4,
32 user_ping_response/5,
33 user_keep_alive/2]).
34
35 %% Remote hook callback
36 -export([handle_remote_hook/4]).
37
38 -ignore_xref([handle_remote_hook/4, user_keep_alive/2, user_offline/5, user_online/5,
39 user_ping_response/5, user_ping_response/5, user_send/4]).
40
41 %%====================================================================
42 %% Info Handler
43 %%====================================================================
44
45 route_ping_iq(JID, Server, HostType) ->
46 12 PingReqTimeout = gen_mod:get_module_opt(HostType, ?MODULE, ping_req_timeout),
47 12 IQ = #iq{type = get,
48 sub_el = [#xmlel{name = <<"ping">>,
49 attrs = [{<<"xmlns">>, ?NS_PING}]}]},
50 12 Pid = self(),
51 12 T0 = erlang:monotonic_time(millisecond),
52 12 F = fun(_From, _To, Acc, timeout) ->
53 2 ejabberd_c2s:run_remote_hook(Pid, mod_ping, timeout),
54 2 NewAcc = mongoose_hooks:user_ping_response(HostType,
55 Acc, JID, timeout, 0),
56 2 NewAcc;
57 (_From, _To, Acc, Response) ->
58 % received a pong from client
59 10 TDelta = erlang:monotonic_time(millisecond) - T0,
60 10 NewAcc = mongoose_hooks:user_ping_response(HostType,
61 Acc, JID, Response, TDelta),
62 10 NewAcc
63 end,
64 12 From = jid:make_noprep(<<"">>, Server, <<"">>),
65 12 Acc = mongoose_acc:new(#{ location => ?LOCATION,
66 lserver => Server,
67 host_type => HostType,
68 from_jid => From,
69 to_jid => JID,
70 element => jlib:iq_to_xml(IQ) }),
71 12 ejabberd_local:route_iq(From, JID, Acc, IQ, F, PingReqTimeout).
72
73 %%====================================================================
74 %% utility
75 %%====================================================================
76
77 hooks(HostType) ->
78 5 [{sm_register_connection_hook, HostType, ?MODULE, user_online, 100},
79 {sm_remove_connection_hook, HostType, ?MODULE, user_offline, 100},
80 {user_send_packet, HostType, ?MODULE, user_send, 100},
81 {user_sent_keep_alive, HostType, ?MODULE, user_keep_alive, 100},
82 {user_ping_response, HostType, ?MODULE, user_ping_response, 100},
83 {c2s_remote_hook, HostType, ?MODULE, handle_remote_hook, 100}].
84
85 ensure_metrics(HostType) ->
86 3 mongoose_metrics:ensure_metric(HostType, [mod_ping, ping_response], spiral),
87 3 mongoose_metrics:ensure_metric(HostType, [mod_ping, ping_response_timeout], spiral),
88 3 mongoose_metrics:ensure_metric(HostType, [mod_ping, ping_response_time], histogram).
89
90 %%====================================================================
91 %% gen_mod callbacks
92 %%====================================================================
93
94 -spec start(mongooseim:host_type(), gen_mod:module_opts()) -> ok.
95 start(HostType, #{send_pings := SendPings, iqdisc := IQDisc}) ->
96 3 ensure_metrics(HostType),
97 3 gen_iq_handler:add_iq_handler_for_domain(HostType, ?NS_PING, ejabberd_sm,
98 fun ?MODULE:iq_ping/5, #{}, IQDisc),
99 3 gen_iq_handler:add_iq_handler_for_domain(HostType, ?NS_PING, ejabberd_local,
100 fun ?MODULE:iq_ping/5, #{}, IQDisc),
101 3 maybe_add_hooks_handlers(HostType, SendPings).
102
103 -spec maybe_add_hooks_handlers(mongooseim:host_type(), boolean()) -> ok.
104 maybe_add_hooks_handlers(Host, true) ->
105 2 ejabberd_hooks:add(hooks(Host));
106 maybe_add_hooks_handlers(_, _) ->
107 1 ok.
108
109 -spec stop(mongooseim:host_type()) -> ok.
110 stop(HostType) ->
111 %% a word of warning: timers are installed in c2s processes, so stopping mod_ping
112 %% won't stop currently running timers. They'll run one more time, and then stop.
113 3 ejabberd_hooks:delete(hooks(HostType)),
114 3 gen_iq_handler:remove_iq_handler_for_domain(HostType, ?NS_PING, ejabberd_local),
115 3 gen_iq_handler:remove_iq_handler_for_domain(HostType, ?NS_PING, ejabberd_sm),
116 3 ok.
117
118 -spec config_spec() -> mongoose_config_spec:config_section().
119 config_spec() ->
120 164 #section{
121 items = #{<<"send_pings">> => #option{type = boolean},
122 <<"ping_interval">> => #option{type = integer,
123 validate = positive,
124 process = fun timer:seconds/1},
125 <<"timeout_action">> => #option{type = atom,
126 validate = {enum, [none, kill]}},
127 <<"ping_req_timeout">> => #option{type = integer,
128 validate = positive,
129 process = fun timer:seconds/1},
130 <<"iqdisc">> => mongoose_config_spec:iqdisc()
131 },
132 defaults = #{<<"send_pings">> => ?DEFAULT_SEND_PINGS,
133 <<"ping_interval">> => ?DEFAULT_PING_INTERVAL,
134 <<"timeout_action">> => none,
135 <<"ping_req_timeout">> => ?DEFAULT_PING_REQ_TIMEOUT,
136 <<"iqdisc">> => no_queue
137 },
138 format_items = map
139 }.
140
141
:-(
supported_features() -> [dynamic_domains].
142
143 %%====================================================================
144 %% IQ handlers
145 %%====================================================================
146 iq_ping(Acc, _From, _To, #iq{type = get, sub_el = #xmlel{name = <<"ping">>}} = IQ, _) ->
147 5 {Acc, IQ#iq{type = result, sub_el = []}};
148 iq_ping(Acc, _From, _To, #iq{sub_el = SubEl} = IQ, _) ->
149 2 NewSubEl = [SubEl, mongoose_xmpp_errors:feature_not_implemented()],
150 2 {Acc, IQ#iq{type = error, sub_el = NewSubEl}}.
151
152 %%====================================================================
153 %% Hook callbacks
154 %%====================================================================
155
156 handle_remote_hook(HandlerState, mod_ping, Args, C2SState) ->
157 78 handle_remote_call(Args,
158 ejabberd_c2s_state:jid(C2SState),
159 ejabberd_c2s_state:server(C2SState),
160 ejabberd_c2s_state:host_type(C2SState),
161 HandlerState);
162 handle_remote_hook(HandlerState, _, _, _) ->
163
:-(
HandlerState.
164
165 user_online(Acc, _HostType, {_, Pid} = _SID, _Jid, _Info) ->
166 22 ejabberd_c2s:run_remote_hook(Pid, mod_ping, init),
167 22 Acc.
168
169 user_offline(Acc, {_, Pid} = _SID, _JID, _Info, _Reason) ->
170 22 ejabberd_c2s:run_remote_hook(Pid, mod_ping, remove_timer),
171 22 Acc.
172
173 user_send(Acc, _JID, _From, _Packet) ->
174 40 ejabberd_c2s:run_remote_hook(self(), mod_ping, init),
175 40 Acc.
176
177 user_keep_alive(Acc, _JID) ->
178 2 ejabberd_c2s:run_remote_hook(self(), mod_ping, init),
179 2 Acc.
180
181 -spec user_ping_response(Acc :: mongoose_acc:t(),
182 HostType :: mongooseim:host_type(),
183 JID :: jid:jid(),
184 Response :: timeout | jlib:iq(),
185 TDelta :: pos_integer()) -> mongoose_acc:t().
186 user_ping_response(Acc, HostType, _JID, timeout, _TDelta) ->
187 2 mongoose_metrics:update(HostType, [mod_ping, ping_response_timeout], 1),
188 2 Acc;
189 user_ping_response(Acc, HostType, _JID, _Response, TDelta) ->
190 10 mongoose_metrics:update(HostType, [mod_ping, ping_response_time], TDelta),
191 10 mongoose_metrics:update(HostType, [mod_ping, ping_response], 1),
192 10 Acc.
193
194 %%====================================================================
195 %% Implementation
196 %%====================================================================
197
198 handle_remote_call(init, _JID, _Server, HostType, HandlerState) ->
199 64 start_ping_timer(HandlerState, HostType);
200 handle_remote_call(send_ping, JID, Server, HostType, HandlerState) ->
201 12 route_ping_iq(JID, Server, HostType),
202 12 start_ping_timer(HandlerState, Server);
203 handle_remote_call(timeout, JID, _Server, HostType, HandlerState) ->
204 2 mongoose_hooks:user_ping_timeout(HostType, JID),
205 2 case gen_mod:get_module_opt(HostType, ?MODULE, timeout_action) of
206 1 kill -> ejabberd_c2s:stop(self());
207 1 _ -> ok
208 end,
209 2 HandlerState;
210 handle_remote_call(remove_timer, _JID, _Server, _HostType, HandlerState) ->
211
:-(
cancel_timer(HandlerState),
212
:-(
empty_state.
213
214 -spec start_ping_timer(term(), jid:server()) -> reference().
215 start_ping_timer(HandlerState, HostType) ->
216 76 cancel_timer(HandlerState),
217 76 PingInterval = gen_mod:get_module_opt(HostType, ?MODULE, ping_interval),
218 76 ejabberd_c2s:run_remote_hook_after(PingInterval, self(), mod_ping, send_ping).
219
220 cancel_timer(empty_state) ->
221 22 do_nothing;
222 cancel_timer(TRef) ->
223 54 erlang:cancel_timer(TRef).
224
Line Hits Source