./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 ?DEFAULT_PING_REQ_TIMEOUT),
48 12 IQ = #iq{type = get,
49 sub_el = [#xmlel{name = <<"ping">>,
50 attrs = [{<<"xmlns">>, ?NS_PING}]}]},
51 12 Pid = self(),
52 12 T0 = erlang:monotonic_time(millisecond),
53 12 F = fun(_From, _To, Acc, timeout) ->
54 2 ejabberd_c2s:run_remote_hook(Pid, mod_ping, timeout),
55 2 NewAcc = mongoose_hooks:user_ping_response(HostType,
56 Acc, JID, timeout, 0),
57 2 NewAcc;
58 (_From, _To, Acc, Response) ->
59 % received a pong from client
60 10 TDelta = erlang:monotonic_time(millisecond) - T0,
61 10 NewAcc = mongoose_hooks:user_ping_response(HostType,
62 Acc, JID, Response, TDelta),
63 10 NewAcc
64 end,
65 12 From = jid:make_noprep(<<"">>, Server, <<"">>),
66 12 Acc = mongoose_acc:new(#{ location => ?LOCATION,
67 lserver => Server,
68 host_type => HostType,
69 from_jid => From,
70 to_jid => JID,
71 element => jlib:iq_to_xml(IQ) }),
72 12 ejabberd_local:route_iq(From, JID, Acc, IQ, F, PingReqTimeout).
73
74 %%====================================================================
75 %% utility
76 %%====================================================================
77
78 hooks(HostType) ->
79 5 [{sm_register_connection_hook, HostType, ?MODULE, user_online, 100},
80 {sm_remove_connection_hook, HostType, ?MODULE, user_offline, 100},
81 {user_send_packet, HostType, ?MODULE, user_send, 100},
82 {user_sent_keep_alive, HostType, ?MODULE, user_keep_alive, 100},
83 {user_ping_response, HostType, ?MODULE, user_ping_response, 100},
84 {c2s_remote_hook, HostType, ?MODULE, handle_remote_hook, 100}].
85
86 ensure_metrics(HostType) ->
87 3 mongoose_metrics:ensure_metric(HostType, [mod_ping, ping_response], spiral),
88 3 mongoose_metrics:ensure_metric(HostType, [mod_ping, ping_response_timeout], spiral),
89 3 mongoose_metrics:ensure_metric(HostType, [mod_ping, ping_response_time], histogram).
90
91 %%====================================================================
92 %% gen_mod callbacks
93 %%====================================================================
94
95 start(HostType, Opts) ->
96 3 ensure_metrics(HostType),
97 3 SendPings = gen_mod:get_opt(send_pings, Opts, ?DEFAULT_SEND_PINGS),
98 3 IQDisc = gen_mod:get_opt(iqdisc, Opts, no_queue),
99 3 gen_iq_handler:add_iq_handler_for_domain(HostType, ?NS_PING, ejabberd_sm,
100 fun ?MODULE:iq_ping/5, #{}, IQDisc),
101 3 gen_iq_handler:add_iq_handler_for_domain(HostType, ?NS_PING, ejabberd_local,
102 fun ?MODULE:iq_ping/5, #{}, IQDisc),
103 3 maybe_add_hooks_handlers(HostType, SendPings).
104
105 maybe_add_hooks_handlers(Host, true) ->
106 2 ejabberd_hooks:add(hooks(Host));
107 maybe_add_hooks_handlers(_, _) ->
108 1 ok.
109
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 160 #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 }.
133
134
:-(
supported_features() -> [dynamic_domains].
135
136 %%====================================================================
137 %% IQ handlers
138 %%====================================================================
139 iq_ping(Acc, _From, _To, #iq{type = get, sub_el = #xmlel{name = <<"ping">>}} = IQ, _) ->
140 5 {Acc, IQ#iq{type = result, sub_el = []}};
141 iq_ping(Acc, _From, _To, #iq{sub_el = SubEl} = IQ, _) ->
142 2 NewSubEl = [SubEl, mongoose_xmpp_errors:feature_not_implemented()],
143 2 {Acc, IQ#iq{type = error, sub_el = NewSubEl}}.
144
145 %%====================================================================
146 %% Hook callbacks
147 %%====================================================================
148
149 handle_remote_hook(HandlerState, mod_ping, Args, C2SState) ->
150 78 handle_remote_call(Args,
151 ejabberd_c2s_state:jid(C2SState),
152 ejabberd_c2s_state:server(C2SState),
153 ejabberd_c2s_state:host_type(C2SState),
154 HandlerState);
155 handle_remote_hook(HandlerState, _, _, _) ->
156
:-(
HandlerState.
157
158 user_online(Acc, _HostType, {_, Pid} = _SID, _Jid, _Info) ->
159 22 ejabberd_c2s:run_remote_hook(Pid, mod_ping, init),
160 22 Acc.
161
162 user_offline(Acc, {_, Pid} = _SID, _JID, _Info, _Reason) ->
163 22 ejabberd_c2s:run_remote_hook(Pid, mod_ping, remove_timer),
164 22 Acc.
165
166 user_send(Acc, _JID, _From, _Packet) ->
167 40 ejabberd_c2s:run_remote_hook(self(), mod_ping, init),
168 40 Acc.
169
170 user_keep_alive(Acc, _JID) ->
171 2 ejabberd_c2s:run_remote_hook(self(), mod_ping, init),
172 2 Acc.
173
174 -spec user_ping_response(Acc :: mongoose_acc:t(),
175 HostType :: mongooseim:host_type(),
176 JID :: jid:jid(),
177 Response :: timeout | jlib:iq(),
178 TDelta :: pos_integer()) -> mongoose_acc:t().
179 user_ping_response(Acc, HostType, _JID, timeout, _TDelta) ->
180 2 mongoose_metrics:update(HostType, [mod_ping, ping_response_timeout], 1),
181 2 Acc;
182 user_ping_response(Acc, HostType, _JID, _Response, TDelta) ->
183 10 mongoose_metrics:update(HostType, [mod_ping, ping_response_time], TDelta),
184 10 mongoose_metrics:update(HostType, [mod_ping, ping_response], 1),
185 10 Acc.
186
187 %%====================================================================
188 %% Implementation
189 %%====================================================================
190
191 handle_remote_call(init, _JID, _Server, HostType, HandlerState) ->
192 64 start_ping_timer(HandlerState, HostType);
193 handle_remote_call(send_ping, JID, Server, HostType, HandlerState) ->
194 12 route_ping_iq(JID, Server, HostType),
195 12 start_ping_timer(HandlerState, Server);
196 handle_remote_call(timeout, JID, _Server, HostType, HandlerState) ->
197 2 mongoose_hooks:user_ping_timeout(HostType, JID),
198 2 case gen_mod:get_module_opt(HostType, ?MODULE, timeout_action, none) of
199 1 kill -> ejabberd_c2s:stop(self());
200 1 _ -> ok
201 end,
202 2 HandlerState;
203 handle_remote_call(remove_timer, _JID, _Server, _HostType, HandlerState) ->
204
:-(
cancel_timer(HandlerState),
205
:-(
empty_state.
206
207 -spec start_ping_timer(term(), jid:server()) -> reference().
208 start_ping_timer(HandlerState, HostType) ->
209 76 cancel_timer(HandlerState),
210 76 PingInterval = gen_mod:get_module_opt(HostType, ?MODULE, ping_interval,
211 ?DEFAULT_PING_INTERVAL),
212 76 ejabberd_c2s:run_remote_hook_after(PingInterval, self(), mod_ping, send_ping).
213
214 cancel_timer(empty_state) ->
215 22 do_nothing;
216 cancel_timer(TRef) ->
217 54 erlang:cancel_timer(TRef).
218
Line Hits Source