./ct_report/coverage/ejabberd_s2s.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : ejabberd_s2s.erl
3 %%% Author : Alexey Shchepin <alexey@process-one.net>
4 %%% Purpose : S2S connections manager
5 %%% Created : 7 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
6 %%%
7 %%%
8 %%% ejabberd, Copyright (C) 2002-2011 ProcessOne
9 %%%
10 %%% This program is free software; you can redistribute it and/or
11 %%% modify it under the terms of the GNU General Public License as
12 %%% published by the Free Software Foundation; either version 2 of the
13 %%% License, or (at your option) any later version.
14 %%%
15 %%% This program is distributed in the hope that it will be useful,
16 %%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17 %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 %%% General Public License for more details.
19 %%%
20 %%% You should have received a copy of the GNU General Public License
21 %%% along with this program; if not, write to the Free Software
22 %%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 %%%
24 %%%----------------------------------------------------------------------
25
26 -module(ejabberd_s2s).
27 -author('alexey@process-one.net').
28
29 -behaviour(gen_server).
30 -behaviour(xmpp_router).
31
32 %% API functions
33 -export([start_link/0,
34 filter/4,
35 route/4,
36 key/3,
37 try_register/1,
38 get_s2s_out_pids/1,
39 remove_connection/2]).
40
41 %% Hooks callbacks
42 -export([node_cleanup/3]).
43
44 %% gen_server callbacks
45 -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
46 terminate/2, code_change/3]).
47
48 -ignore_xref([start_link/0]).
49
50 -include("mongoose.hrl").
51 -include("jlib.hrl").
52
53 %% Pair of hosts {FromServer, ToServer}.
54 %% FromServer is the local server.
55 %% ToServer is the remote server.
56 %% Used in a lot of API and backend functions.
57 -type fromto() :: {jid:lserver(), jid:lserver()}.
58
59 %% Pids for ejabberd_s2s_out servers
60 -type s2s_pids() :: [pid()].
61
62 -record(state, {}).
63
64 -type base16_secret() :: binary().
65 -type stream_id() :: binary().
66 -type s2s_dialback_key() :: binary().
67
68 -export_type([fromto/0, s2s_pids/0, base16_secret/0, stream_id/0, s2s_dialback_key/0]).
69
70 %% API functions
71
72 %% Starts the server
73 -spec start_link() -> ignore | {error, _} | {ok, pid()}.
74 start_link() ->
75 100 gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
76
77 filter(From, To, Acc, Packet) ->
78 113 {From, To, Acc, Packet}.
79
80 route(From, To, Acc, Packet) ->
81 113 do_route(From, To, Acc, Packet).
82
83 %% Called by ejabberd_s2s_out process.
84 -spec try_register(fromto()) -> IsRegistered :: boolean().
85 try_register(FromTo) ->
86 27 Pid = self(),
87 27 IsRegistered = call_try_register(Pid, FromTo),
88 27 case IsRegistered of
89 false ->
90 %% This usually happens when a ejabberd_s2s_out connection is established during dialback
91 %% procedure to check the key.
92 %% We still are fine, we just would not use that s2s connection to route
93 %% any stanzas to the remote server.
94 %% Could be a sign of abuse or a bug though, so use logging here.
95 14 ?LOG_INFO(#{what => s2s_register_failed, from_to => FromTo, pid => self()});
96 _ ->
97 13 ok
98 end,
99 27 IsRegistered.
100
101 -spec key(mongooseim:host_type(), fromto(), stream_id()) -> s2s_dialback_key().
102 key(HostType, FromTo, StreamID) ->
103 53 {ok, Secret} = get_shared_secret(HostType),
104 53 mongoose_s2s_dialback:make_key(FromTo, StreamID, Secret).
105
106 %% Hooks callbacks
107
108 -spec node_cleanup(map(), map(), map()) -> {ok, map()}.
109 node_cleanup(Acc, #{node := Node}, _) ->
110 8 Res = call_node_cleanup(Node),
111 8 {ok, maps:put(?MODULE, Res, Acc)}.
112
113 %% gen_server callbacks
114
115 init([]) ->
116 100 internal_database_init(),
117 100 set_shared_secret(),
118 100 gen_hook:add_handlers(hooks()),
119 100 {ok, #state{}}.
120
121 handle_call(Request, From, State) ->
122
:-(
?UNEXPECTED_CALL(Request, From),
123
:-(
{reply, {error, unexpected_call}, State}.
124
125 handle_cast(Msg, State) ->
126
:-(
?UNEXPECTED_CAST(Msg),
127
:-(
{noreply, State}.
128
129 handle_info(Msg, State) ->
130
:-(
?UNEXPECTED_INFO(Msg),
131
:-(
{noreply, State}.
132
133 terminate(_Reason, _State) ->
134
:-(
gen_hook:delete_handlers(hooks()),
135
:-(
ok.
136
137 code_change(_OldVsn, State, _Extra) ->
138
:-(
{ok, State}.
139
140 %%% Internal functions
141 -spec hooks() -> [gen_hook:hook_tuple()].
142 hooks() ->
143 100 [{node_cleanup, global, fun ?MODULE:node_cleanup/3, #{}, 50}].
144
145 -spec do_route(From :: jid:jid(),
146 To :: jid:jid(),
147 Acc :: mongoose_acc:t(),
148 Packet :: exml:element()) ->
149 {done, mongoose_acc:t()}. % this is the 'last resort' router, it always returns 'done'.
150 do_route(From, To, Acc, Packet) ->
151 113 ?LOG_DEBUG(#{what => s2s_route, acc => Acc}),
152 113 case find_connection(From, To) of
153 {ok, Pid} when is_pid(Pid) ->
154 106 ?LOG_DEBUG(#{what => s2s_found_connection,
155 text => <<"Send packet to s2s connection">>,
156 106 s2s_pid => Pid, acc => Acc}),
157 106 NewPacket = jlib:replace_from_to(From, To, Packet),
158 106 Acc1 = mongoose_hooks:s2s_send_packet(Acc, From, To, Packet),
159 106 send_element(Pid, Acc1, NewPacket),
160 106 {done, Acc1};
161 {error, _Reason} ->
162 7 case mongoose_acc:stanza_type(Acc) of
163 <<"error">> ->
164 2 {done, Acc};
165 <<"result">> ->
166
:-(
{done, Acc};
167 _ ->
168 5 ?LOG_DEBUG(#{what => s2s_connection_not_found, acc => Acc}),
169 5 {Acc1, Err} = jlib:make_error_reply(
170 Acc, Packet, mongoose_xmpp_errors:service_unavailable()),
171 5 Acc2 = ejabberd_router:route(To, From, Acc1, Err),
172 5 {done, Acc2}
173 end
174 end.
175
176 -spec send_element(pid(), mongoose_acc:t(), exml:element()) -> ok.
177 send_element(Pid, Acc, El) ->
178 106 Pid ! {send_element, Acc, El},
179 106 ok.
180
181 -spec find_connection(From :: jid:jid(), To :: jid:jid()) ->
182 {ok, pid()} | {error, not_allowed}.
183 find_connection(From, To) ->
184 113 FromTo = mongoose_s2s_lib:make_from_to(From, To),
185 113 ?LOG_DEBUG(#{what => s2s_find_connection, from_to => FromTo}),
186 113 OldCons = get_s2s_out_pids(FromTo),
187 113 NewCons = ensure_enough_connections(FromTo, OldCons),
188 113 case NewCons of
189 [] ->
190 7 {error, not_allowed};
191 [_|_] ->
192 106 {ok, mongoose_s2s_lib:choose_pid(From, NewCons)}
193 end.
194
195 %% Opens more connections if needed and allowed.
196 %% Returns an updated list of connections.
197 -spec ensure_enough_connections(fromto(), s2s_pids()) -> s2s_pids().
198 ensure_enough_connections(FromTo, OldCons) ->
199 113 NeededConnections =
200 mongoose_s2s_lib:needed_extra_connections_number_if_allowed(FromTo, OldCons),
201 %% Could be negative, if we have too many connections
202 113 case NeededConnections > 0 of
203 true ->
204 29 open_new_connections(NeededConnections, FromTo),
205 %% Query for s2s pids one more time
206 29 get_s2s_out_pids(FromTo);
207 false ->
208 84 OldCons
209 end.
210
211 -spec open_new_connections(N :: pos_integer(), FromTo :: fromto()) -> ok.
212 open_new_connections(N, FromTo) ->
213 29 [open_new_connection(FromTo) || _N <- lists:seq(1, N)],
214 29 ok.
215
216 -spec open_new_connection(FromTo :: fromto()) -> ok.
217 open_new_connection(FromTo) ->
218 %% Start a process, but do not connect to the server yet.
219 29 {ok, Pid} = ejabberd_s2s_out:start(FromTo, new),
220 %% Try to write the Pid into Mnesia/CETS
221 29 IsRegistered = call_try_register(Pid, FromTo),
222 29 maybe_start_connection(Pid, FromTo, IsRegistered),
223 29 ok.
224
225 %% If registration is successful, create an actual network connection.
226 %% If not successful, remove the process.
227 -spec maybe_start_connection(Pid :: pid(), FromTo :: fromto(), IsRegistered :: boolean()) -> ok.
228 maybe_start_connection(Pid, FromTo, true) ->
229 24 ?LOG_INFO(#{what => s2s_new_connection,
230 text => <<"New s2s connection started">>,
231 24 from_to => FromTo, s2s_pid => Pid}),
232 24 ejabberd_s2s_out:start_connection(Pid);
233 maybe_start_connection(Pid, _FromTo, false) ->
234 5 ejabberd_s2s_out:stop_connection(Pid).
235
236 -spec set_shared_secret() -> ok.
237 set_shared_secret() ->
238 100 [set_shared_secret(HostType) || HostType <- ?ALL_HOST_TYPES],
239 100 ok.
240
241 %% Updates the secret across the cluster if needed
242 -spec set_shared_secret(mongooseim:host_type()) -> ok.
243 set_shared_secret(HostType) ->
244 536 case mongoose_s2s_lib:check_shared_secret(HostType, get_shared_secret(HostType)) of
245 {update, NewSecret} ->
246 236 register_secret(HostType, NewSecret);
247 ok ->
248 300 ok
249 end.
250
251 %% Backend logic functions
252
253 -spec internal_database_init() -> ok.
254 internal_database_init() ->
255 100 Backend = mongoose_config:get_opt(s2s_backend),
256 100 mongoose_s2s_backend:init(#{backend => Backend}).
257
258 %% Get ejabberd_s2s_out pids
259 -spec get_s2s_out_pids(FromTo :: fromto()) -> s2s_pids().
260 get_s2s_out_pids(FromTo) ->
261 168 mongoose_s2s_backend:get_s2s_out_pids(FromTo).
262
263 %% Returns true if the connection is registered
264 -spec call_try_register(Pid :: pid(), FromTo :: fromto()) -> IsRegistered :: boolean().
265 call_try_register(Pid, FromTo) ->
266 56 mongoose_s2s_backend:try_register(Pid, FromTo).
267
268 -spec call_node_cleanup(Node :: node()) -> ok.
269 call_node_cleanup(Node) ->
270 8 mongoose_s2s_backend:node_cleanup(Node).
271
272 -spec remove_connection(fromto(), pid()) -> ok.
273 remove_connection(FromTo, Pid) ->
274 42 mongoose_s2s_backend:remove_connection(FromTo, Pid).
275
276 -spec get_shared_secret(mongooseim:host_type()) -> {ok, base16_secret()} | {error, not_found}.
277 get_shared_secret(HostType) ->
278 589 mongoose_s2s_backend:get_shared_secret(HostType).
279
280 -spec register_secret(mongooseim:host_type(), base16_secret()) -> ok.
281 register_secret(HostType, Secret) ->
282 236 mongoose_s2s_backend:register_secret(HostType, Secret).
Line Hits Source