./ct_report/coverage/ejabberd_router.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : ejabberd_router.erl
3 %%% Author : Alexey Shchepin <alexey@process-one.net>
4 %%% Purpose : Main router
5 %%% Created : 27 Nov 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_router).
27 -author('alexey@process-one.net').
28
29 -behaviour(gen_server).
30 %% API
31 -export([route/3,
32 route/4,
33 route_error/4,
34 route_error_reply/4,
35 is_component_dirty/1,
36 dirty_get_all_components/1,
37 register_components/2,
38 register_components/3,
39 register_components/4,
40 register_component/2,
41 register_component/3,
42 register_component/4,
43 lookup_component/1,
44 lookup_component/2,
45 unregister_component/1,
46 unregister_component/2,
47 unregister_components/1,
48 unregister_components/2
49 ]).
50
51 -export([start_link/0]).
52 -export([routes_cleanup_on_nodedown/2]).
53
54 %% gen_server callbacks
55 -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
56
57 %% debug exports for tests
58 -export([update_tables/0]).
59
60 -ignore_xref([register_component/2, register_component/3, register_component/4,
61 register_components/2, register_components/3,
62 route_error/4, routes_cleanup_on_nodedown/2, start_link/0,
63 unregister_component/1, unregister_component/2, unregister_components/2,
64 unregister_routes/1, update_tables/0]).
65
66 -include("mongoose.hrl").
67 -include("jlib.hrl").
68 -include("external_component.hrl").
69
70 -record(state, {}).
71
72 -type domain() :: binary().
73
74 -type external_component() :: #external_component{domain :: domain(),
75 handler :: mongoose_packet_handler:t(),
76 is_hidden :: boolean()}.
77
78 % Not simple boolean() because is probably going to support third value in the future: only_hidden.
79 % Besides, it increases readability.
80 -type return_hidden() :: only_public | all.
81
82 %%====================================================================
83 %% API
84 %%====================================================================
85
86 -spec start_link() -> 'ignore' | {'error', _} | {'ok', pid()}.
87 start_link() ->
88 83 gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
89
90 %% @doc The main routing function. It puts the message through a chain
91 %% of filtering/routing modules, as defined in config 'routing_modules'
92 %% setting (default is hardcoded in default_routing_modules function
93 %% of this module). Each of those modules should use xmpp_router behaviour
94 %% and implement two functions:
95 %% filter/3 - should return either 'drop' atom or its args
96 %% route/3, which should either:
97 %% - deliver the message locally by calling mongoose_local_delivery:do_route/5
98 %% and return 'done'
99 %% - deliver the message it its own way and return 'done'
100 %% - return its args
101 %% - return a tuple of {From, To, Packet} which might be modified
102 %% For both functions, returning a 'drop' or 'done' atom terminates the procedure,
103 %% while returning a tuple means 'proceed' and the tuple is passed to
104 %% the next module in sequence.
105 -spec route(From :: jid:jid(),
106 To :: jid:jid(),
107 Packet :: mongoose_acc:t()|exml:element()) -> mongoose_acc:t().
108 route(From, To, #xmlel{} = Packet) ->
109 % ?LOG_ERROR("Deprecated - it should be Acc: ~p", [Packet]),
110 2593 Acc = mongoose_acc:new(#{ location => ?LOCATION,
111 lserver => From#jid.lserver,
112 element => Packet,
113 from_jid => From,
114 to_jid => To }),
115 % (called by broadcasting)
116 2593 route(From, To, Acc);
117 route(From, To, Acc) ->
118 5826 ?LOG_DEBUG(#{what => route, acc => Acc}),
119 5826 El = mongoose_acc:element(Acc),
120 5826 RoutingModules = routing_modules_list(),
121 5826 NewAcc = route(From, To, Acc, El, RoutingModules),
122 5824 ?LOG_DEBUG(#{what => routing_result,
123 routing_result => mongoose_acc:get(router, result, {drop, undefined}, NewAcc),
124 5824 routing_modules => RoutingModules, acc => Acc}),
125 5824 NewAcc.
126
127 -spec route(From :: jid:jid(),
128 To :: jid:jid(),
129 Acc :: mongoose_acc:t(),
130 El :: exml:element() | {error, term()}) -> mongoose_acc:t().
131 route(_From, _To, Acc, {error, Reason} = Err) ->
132
:-(
?LOG_INFO(#{what => cannot_route_stanza, acc => Acc, reason => Reason}),
133
:-(
mongoose_acc:append(router, result, Err, Acc);
134 route(From, To, Acc, El) ->
135 12305 Acc1 = mongoose_acc:update_stanza(#{ from_jid => From,
136 to_jid => To,
137 element => El }, Acc),
138 12305 ?LOG_DEBUG(#{what => route, acc => Acc1}),
139 12305 RoutingModules = routing_modules_list(),
140 12305 NewAcc = route(From, To, Acc1, El, RoutingModules),
141 12305 ?LOG_DEBUG(#{what => routing_result,
142 routing_result => mongoose_acc:get(router, result, {drop, undefined}, NewAcc),
143 12305 routing_modules => RoutingModules, acc => Acc}),
144 12305 NewAcc.
145
146 %% Route the error packet only if the originating packet is not an error itself.
147 %% RFC3920 9.3.1
148 -spec route_error(From :: jid:jid(),
149 To :: jid:jid(),
150 Acc :: mongoose_acc:t(),
151 ErrPacket :: exml:element()) -> mongoose_acc:t().
152 route_error(From, To, Acc, ErrPacket) ->
153
:-(
case mongoose_acc:stanza_type(Acc) of
154 <<"error">> ->
155
:-(
Acc;
156 _ ->
157
:-(
route(From, To, Acc, ErrPacket)
158 end.
159
160 -spec route_error_reply(jid:jid(), jid:jid(), mongoose_acc:t(), exml:element()) ->
161 mongoose_acc:t().
162 route_error_reply(From, To, Acc, Error) ->
163
:-(
{Acc1, ErrorReply} = jlib:make_error_reply(Acc, Error),
164
:-(
route_error(From, To, Acc1, ErrorReply).
165
166
167 -spec register_components([Domain :: domain()],
168 Handler :: mongoose_packet_handler:t()) -> ok | {error, any()}.
169 register_components(Domains, Handler) ->
170
:-(
register_components(Domains, node(), Handler).
171
172 -spec register_components([Domain :: domain()],
173 Node :: node(),
174 Handler :: mongoose_packet_handler:t()) -> ok | {error, any()}.
175 register_components(Domains, Node, Handler) ->
176
:-(
register_components(Domains, Node, Handler, false).
177
178 -spec register_components([Domain :: domain()],
179 Node :: node(),
180 Handler :: mongoose_packet_handler:t(),
181 AreHidden :: boolean()) -> ok | {error, any()}.
182 register_components(Domains, Node, Handler, AreHidden) ->
183 42 LDomains = [{jid:nameprep(Domain), Domain} || Domain <- Domains],
184 42 F = fun() ->
185 42 [do_register_component(LDomain, Handler, Node, AreHidden) || LDomain <- LDomains],
186 36 ok
187 end,
188 42 case mnesia:transaction(F) of
189 36 {atomic, ok} -> ok;
190 6 {aborted, Reason} -> {error, Reason}
191 end.
192
193 %% @doc
194 %% components are registered in two places: external_components table as local components
195 %% and external_components_global as globals. Registration should be done by register_component/1
196 %% or register_components/1, which registers them for current node; the arity 2 funcs are
197 %% here for testing.
198 -spec register_component(Domain :: domain(),
199 Handler :: mongoose_packet_handler:t()) -> ok | {error, any()}.
200 register_component(Domain, Handler) ->
201
:-(
register_component(Domain, node(), Handler).
202
203 -spec register_component(Domain :: domain(),
204 Node :: node(),
205 Handler :: mongoose_packet_handler:t()) -> ok | {error, any()}.
206 register_component(Domain, Node, Handler) ->
207
:-(
register_component(Domain, Node, Handler, false).
208
209 -spec register_component(Domain :: domain(),
210 Node :: node(),
211 Handler :: mongoose_packet_handler:t(),
212 IsHidden :: boolean()) -> ok | {error, any()}.
213 register_component(Domain, Node, Handler, IsHidden) ->
214
:-(
register_components([Domain], Node, Handler, IsHidden).
215
216 do_register_component({error, Domain}, _Handler, _Node, _IsHidden) ->
217
:-(
error({invalid_domain, Domain});
218 do_register_component({LDomain, _}, Handler, Node, IsHidden) ->
219 43 case check_component(LDomain, Node) of
220 ok ->
221 37 ComponentGlobal = #external_component{domain = LDomain, handler = Handler,
222 node = Node, is_hidden = IsHidden},
223 37 mnesia:write(external_component_global, ComponentGlobal, write),
224 37 NDomain = {LDomain, Node},
225 37 Component = #external_component{domain = NDomain, handler = Handler,
226 node = Node, is_hidden = IsHidden},
227 37 mnesia:write(Component),
228 37 mongoose_hooks:register_subhost(LDomain, IsHidden);
229 6 _ -> mnesia:abort(route_already_exists)
230 end.
231
232 %% @doc Check if the component/route is already registered somewhere; ok means it is not, so we are
233 %% ok to proceed, anything else means the domain/node pair is already serviced.
234 %% true and false are there because that's how orelse works.
235 -spec check_component(binary(), Node :: node()) -> ok | error.
236 check_component(LDomain, Node) ->
237 43 case check_dynamic_domains(LDomain)
238 41 orelse check_component_route(LDomain)
239 41 orelse check_component_local(LDomain, Node)
240 37 orelse check_component_global(LDomain, Node) of
241 6 true -> error;
242 37 false -> ok
243 end.
244
245 check_dynamic_domains(LDomain)->
246 43 {error, not_found} =/= mongoose_domain_api:get_host_type(LDomain).
247
248 %% check that route for this domain is not already registered
249 check_component_route(LDomain) ->
250 41 no_route =/= mongoose_router:lookup_route(LDomain).
251
252 %% check that there is no local component for domain:node pair
253 check_component_local(LDomain, Node) ->
254 41 NDomain = {LDomain, Node},
255 41 [] =/= mnesia:read(external_component, NDomain).
256
257 %% check that there is no component registered globally for this node
258 check_component_global(LDomain, Node) ->
259 37 undefined =/= get_global_component(LDomain, Node).
260
261 %% Find a component registered globally for this node (internal use)
262 get_global_component([], _) ->
263 37 undefined;
264 get_global_component([Comp|Tail], Node) ->
265 40 case Comp of
266 #external_component{node = Node} ->
267 38 Comp;
268 _ ->
269 2 get_global_component(Tail, Node)
270 end;
271 get_global_component(LDomain, Node) ->
272 75 get_global_component(mnesia:read(external_component_global, LDomain), Node).
273
274
275 -spec unregister_components([Domains :: domain()]) -> {atomic, ok}.
276 unregister_components(Domains) ->
277 36 unregister_components(Domains, node()).
278 -spec unregister_components([Domains :: domain()], Node :: node()) -> {atomic, ok}.
279 unregister_components(Domains, Node) ->
280 36 LDomains = [{jid:nameprep(Domain), Domain} || Domain <- Domains],
281 36 F = fun() ->
282 37 [do_unregister_component(LDomain, Node) || LDomain <- LDomains],
283 36 ok
284 end,
285 36 {atomic, ok} = mnesia:transaction(F).
286
287 do_unregister_component({error, Domain}, _Node) ->
288
:-(
error({invalid_domain, Domain});
289 do_unregister_component({LDomain, _}, Node) ->
290 38 case get_global_component(LDomain, Node) of
291 undefined ->
292
:-(
ok;
293 Comp ->
294 38 ok = mnesia:delete_object(external_component_global, Comp, write)
295 end,
296 37 ok = mnesia:delete({external_component, {LDomain, Node}}),
297 37 mongoose_hooks:unregister_subhost(LDomain),
298 37 ok.
299
300 -spec unregister_component(Domain :: domain()) -> {atomic, ok}.
301 unregister_component(Domain) ->
302
:-(
unregister_components([Domain]).
303
304 -spec unregister_component(Domain :: domain(), Node :: node()) -> {atomic, ok}.
305 unregister_component(Domain, Node) ->
306
:-(
unregister_components([Domain], Node).
307
308 %% @doc Returns a list of components registered for this domain by any node,
309 %% the choice is yours.
310 -spec lookup_component(Domain :: jid:lserver()) -> [external_component()].
311 lookup_component(Domain) ->
312 278 mnesia:dirty_read(external_component_global, Domain).
313
314 %% @doc Returns a list of components registered for this domain at the given node.
315 %% (must be only one, or nothing)
316 -spec lookup_component(Domain :: jid:lserver(), Node :: node()) -> [external_component()].
317 lookup_component(Domain, Node) ->
318 256 mnesia:dirty_read(external_component, {Domain, Node}).
319
320 -spec dirty_get_all_components(return_hidden()) -> [jid:lserver()].
321 dirty_get_all_components(all) ->
322 39 mnesia:dirty_all_keys(external_component_global);
323 dirty_get_all_components(only_public) ->
324 27 MatchNonHidden = {#external_component{ domain = '$1', is_hidden = false, _ = '_' }, [], ['$1']},
325 27 mnesia:dirty_select(external_component_global, [MatchNonHidden]).
326
327 -spec is_component_dirty(jid:lserver()) -> boolean().
328 is_component_dirty(Domain) ->
329
:-(
[] =/= lookup_component(Domain).
330
331 %%====================================================================
332 %% gen_server callbacks
333 %%====================================================================
334
335 init([]) ->
336 83 update_tables(),
337
338 %% add distributed service_component routes
339 83 mnesia:create_table(external_component,
340 [{attributes, record_info(fields, external_component)},
341 {local_content, true}]),
342 83 mnesia:add_table_copy(external_component, node(), ram_copies),
343 83 mnesia:create_table(external_component_global,
344 [{attributes, record_info(fields, external_component)},
345 {type, bag},
346 {record_name, external_component}]),
347 83 mnesia:add_table_copy(external_component_global, node(), ram_copies),
348 83 mongoose_metrics:ensure_metric(global, routingErrors, spiral),
349 83 ejabberd_hooks:add(node_cleanup, global, ?MODULE, routes_cleanup_on_nodedown, 90),
350
351 83 {ok, #state{}}.
352
353 handle_call(_Request, _From, State) ->
354
:-(
Reply = ok,
355
:-(
{reply, Reply, State}.
356
357 handle_cast(_Msg, State) ->
358
:-(
{noreply, State}.
359
360 handle_info(_Info, State) ->
361
:-(
{noreply, State}.
362
363 terminate(_Reason, _State) ->
364
:-(
ejabberd_hooks:delete(node_cleanup, global, ?MODULE, routes_cleanup_on_nodedown, 90),
365
:-(
ok.
366
367 code_change(_OldVsn, State, _Extra) ->
368
:-(
{ok, State}.
369
370 %%--------------------------------------------------------------------
371 %%% Internal functions
372 %%--------------------------------------------------------------------
373 routing_modules_list() ->
374 18131 mongoose_config:get_opt(routing_modules).
375
376 -spec route(From :: jid:jid(),
377 To :: jid:jid(),
378 Acc :: mongoose_acc:t(),
379 Packet :: exml:element(),
380 [atom()]) -> mongoose_acc:t().
381 route(_From, _To, Acc, _Packet, []) ->
382
:-(
?LOG_ERROR(#{what => no_more_routing_modules, acc => Acc}),
383
:-(
mongoose_metrics:update(global, routingErrors, 1),
384
:-(
mongoose_acc:append(router, result, {error, out_of_modules}, Acc);
385 route(OrigFrom, OrigTo, Acc0, OrigPacket, [M|Tail]) ->
386 36683 try xmpp_router:call_filter(M, OrigFrom, OrigTo, Acc0, OrigPacket) of
387 drop ->
388 371 mongoose_acc:append(router, result, {drop, M}, Acc0);
389 {OrigFrom, OrigTo, Acc1, OrigPacketFiltered} ->
390 36310 try xmpp_router:call_route(M, OrigFrom, OrigTo, Acc1, OrigPacketFiltered) of
391 {done, Acc2} ->
392 17753 mongoose_acc:append(router, result, {done, M}, Acc2);
393 {From, To, NAcc1, Packet} ->
394 18552 route(From, To, NAcc1, Packet, Tail)
395 catch Class:Reason:Stacktrace ->
396 5 ?LOG_WARNING(#{what => routing_failed,
397 router_module => M, acc => Acc1,
398
:-(
class => Class, reason => Reason, stacktrace => Stacktrace}),
399 5 mongoose_acc:append(router, result, {error, {M, Reason}}, Acc1)
400 end
401 catch Class:Reason:Stacktrace ->
402
:-(
?LOG_WARNING(#{what => route_filter_failed,
403 text => <<"Error when filtering packet in router">>,
404 router_module => M, acc => Acc0,
405
:-(
class => Class, reason => Reason, stacktrace => Stacktrace}),
406
:-(
mongoose_acc:append(router, result, {error, {M, Reason}}, Acc0)
407 end.
408
409 update_tables() ->
410 83 case catch mnesia:table_info(external_component, attributes) of
411 [domain, handler, node] ->
412
:-(
mnesia:delete_table(external_component);
413 [domain, handler, node, is_hidden] ->
414 56 ok;
415 {'EXIT', _} ->
416 27 ok
417 end,
418 83 case catch mnesia:table_info(external_component_global, attributes) of
419 [domain, handler, node] ->
420
:-(
UpdateFun = fun({external_component, Domain, Handler, Node}) ->
421
:-(
{external_component, Domain, Handler, Node, false}
422 end,
423
:-(
mnesia:transform_table(external_component_global, UpdateFun,
424 [domain, handler, node, is_hidden]);
425 [domain, handler, node, is_hidden] ->
426 56 ok;
427 {'EXIT', _} ->
428 27 ok
429 end.
430
431 -spec routes_cleanup_on_nodedown(map(), node()) -> map().
432 routes_cleanup_on_nodedown(Acc, Node) ->
433
:-(
Entries = mnesia:dirty_match_object(external_component_global,
434 #external_component{node = Node, _ = '_'}),
435
:-(
[mnesia:dirty_delete_object(external_component_global, Entry) || Entry <- Entries],
436
:-(
maps:put(?MODULE, ok, Acc).
Line Hits Source