1 |
|
%%%---------------------------------------------------------------------- |
2 |
|
%%% File : ejabberd_local.erl |
3 |
|
%%% Author : Alexey Shchepin <alexey@process-one.net> |
4 |
|
%%% Purpose : Route local packets |
5 |
|
%%% Created : 30 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 |
|
%%%---------------------------------------------------------------------- |
27 |
|
%%% FIXME: the code in this module uses Host term to identify domain |
28 |
|
%%% name, not a host type. |
29 |
|
%%%---------------------------------------------------------------------- |
30 |
|
|
31 |
|
-module(ejabberd_local). |
32 |
|
-author('alexey@process-one.net'). |
33 |
|
|
34 |
|
-behaviour(gen_server). |
35 |
|
-behaviour(mongoose_packet_handler). |
36 |
|
-behaviour(gen_iq_component). |
37 |
|
|
38 |
|
%% API |
39 |
|
-export([start_link/0]). |
40 |
|
|
41 |
|
-export([process_packet/5, |
42 |
|
route_iq/5, |
43 |
|
route_iq/6, |
44 |
|
process_iq_reply/4, |
45 |
|
register_iq_handler/3, |
46 |
|
register_host/1, |
47 |
|
register_iq_response_handler/4, |
48 |
|
register_iq_response_handler/5, |
49 |
|
unregister_iq_handler/2, |
50 |
|
unregister_host/1, |
51 |
|
unregister_iq_response_handler/2, |
52 |
|
sync/0 |
53 |
|
]). |
54 |
|
|
55 |
|
%% Hooks callbacks |
56 |
|
|
57 |
|
-export([node_cleanup/2, |
58 |
|
disco_local_features/1]). |
59 |
|
|
60 |
|
%% gen_server callbacks |
61 |
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, |
62 |
|
terminate/2, code_change/3]). |
63 |
|
|
64 |
|
-export([do_route/4]). |
65 |
|
|
66 |
|
%% For testing only |
67 |
|
-export([get_iq_callback/1]). |
68 |
|
|
69 |
|
-ignore_xref([disco_local_features/1, do_route/4, get_iq_callback/1, |
70 |
|
node_cleanup/2, process_iq_reply/4, register_iq_response_handler/4, |
71 |
|
register_iq_response_handler/5, start_link/0, unregister_iq_response_handler/2]). |
72 |
|
|
73 |
|
-include("mongoose.hrl"). |
74 |
|
-include("jlib.hrl"). |
75 |
|
-include("session.hrl"). |
76 |
|
|
77 |
|
-record(state, {}). |
78 |
|
|
79 |
|
-type id() :: any(). |
80 |
|
-record(iq_response, {id :: id(), |
81 |
|
module, |
82 |
|
function, |
83 |
|
timer}). |
84 |
|
|
85 |
|
-define(IQTABLE, local_iqtable). |
86 |
|
-define(NSTABLE, local_nstable). |
87 |
|
|
88 |
|
%% This value is used in SIP and Megaco for a transaction lifetime. |
89 |
|
-define(IQ_TIMEOUT, 32000). |
90 |
|
|
91 |
|
%%==================================================================== |
92 |
|
%% API |
93 |
|
%%==================================================================== |
94 |
|
%%-------------------------------------------------------------------- |
95 |
|
%% Function: start_link() -> {ok, Pid} | ignore | {error, Error} |
96 |
|
%% Description: Starts the server |
97 |
|
%%-------------------------------------------------------------------- |
98 |
|
-spec start_link() -> 'ignore' | {'error', _} | {'ok', pid()}. |
99 |
|
start_link() -> |
100 |
80 |
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). |
101 |
|
|
102 |
|
-spec process_iq(Acc :: mongoose_acc:t(), |
103 |
|
From :: jid:jid(), |
104 |
|
To :: jid:jid(), |
105 |
|
El :: exml:element() |
106 |
|
) -> mongoose_acc:t(). |
107 |
|
process_iq(Acc0, From, To, El) -> |
108 |
165 |
{IQ, Acc} = mongoose_iq:info(Acc0), |
109 |
165 |
process_iq(IQ, Acc, From, To, El). |
110 |
|
|
111 |
|
process_iq(#iq{ type = Type } = IQReply, Acc, From, To, _El) |
112 |
|
when Type == result; Type == error -> |
113 |
50 |
process_iq_reply(From, To, Acc, IQReply); |
114 |
|
process_iq(#iq{ xmlns = XMLNS } = IQ, Acc, From, To, _El) -> |
115 |
115 |
Host = To#jid.lserver, |
116 |
115 |
case ets:lookup(?IQTABLE, {XMLNS, Host}) of |
117 |
|
[{_, IQHandler}] -> |
118 |
115 |
gen_iq_component:handle(IQHandler, Acc, From, To, IQ); |
119 |
|
[] -> |
120 |
:-( |
T = <<"Local server does not implement this feature">>, |
121 |
:-( |
ejabberd_router:route_error_reply(To, From, Acc, |
122 |
|
mongoose_xmpp_errors:feature_not_implemented(<<"en">>, T)) |
123 |
|
end; |
124 |
|
process_iq(_, Acc, From, To, El) -> |
125 |
:-( |
{Acc1, Err} = jlib:make_error_reply(Acc, El, mongoose_xmpp_errors:bad_request()), |
126 |
:-( |
ejabberd_router:route(To, From, Acc1, Err). |
127 |
|
|
128 |
|
-spec process_iq_reply(From :: jid:jid(), |
129 |
|
To :: jid:jid(), |
130 |
|
mongoose_acc:t(), |
131 |
|
IQ :: jlib:iq() ) -> mongoose_acc:t(). |
132 |
|
process_iq_reply(From, To, Acc, #iq{id = ID} = IQ) -> |
133 |
50 |
case get_iq_callback(ID) of |
134 |
|
{ok, undefined, Function} -> |
135 |
50 |
Function(From, To, Acc, IQ); |
136 |
|
{ok, Module, Function} -> |
137 |
:-( |
Module:Function(From, To, Acc, IQ); |
138 |
|
_ -> |
139 |
:-( |
Acc |
140 |
|
end. |
141 |
|
|
142 |
|
|
143 |
|
-spec process_packet(Acc :: mongoose_acc:t(), |
144 |
|
From :: jid:jid(), |
145 |
|
To ::jid:jid(), |
146 |
|
El :: exml:element(), |
147 |
|
Extra :: map()) -> mongoose_acc:t(). |
148 |
|
process_packet(Acc, From, To, El, _Extra) -> |
149 |
14511 |
try |
150 |
14511 |
do_route(Acc, From, To, El) |
151 |
|
catch |
152 |
|
Class:Reason:Stacktrace -> |
153 |
:-( |
?LOG_ERROR(#{what => routing_error, acc => Acc, |
154 |
:-( |
class => Class, reason => Reason, stacktrace => Stacktrace}) |
155 |
|
end. |
156 |
|
|
157 |
|
-spec route_iq(From :: jid:jid(), |
158 |
|
To :: jid:jid(), |
159 |
|
Acc :: mongoose_acc:t(), |
160 |
|
IQ :: jlib:iq(), |
161 |
|
F :: fun()) -> mongoose_acc:t(). |
162 |
|
route_iq(From, To, Acc, IQ, F) -> |
163 |
44 |
route_iq(From, To, Acc, IQ, F, undefined). |
164 |
|
|
165 |
|
|
166 |
|
-spec route_iq(From :: jid:jid(), |
167 |
|
To :: jid:jid(), |
168 |
|
Acc :: mongoose_acc:t(), |
169 |
|
IQ :: jlib:iq(), |
170 |
|
F :: fun(), |
171 |
|
Timeout :: undefined | integer()) -> mongoose_acc:t(). |
172 |
|
route_iq(From, To, Acc, #iq{type = Type} = IQ, F, Timeout) when is_function(F) -> |
173 |
56 |
Packet = case Type == set orelse Type == get of |
174 |
|
true -> |
175 |
56 |
ID = mongoose_bin:gen_from_crypto(), |
176 |
56 |
Host = From#jid.lserver, |
177 |
56 |
register_iq_response_handler(Host, ID, undefined, F, Timeout), |
178 |
56 |
jlib:iq_to_xml(IQ#iq{id = ID}); |
179 |
|
false -> |
180 |
:-( |
jlib:iq_to_xml(IQ) |
181 |
|
end, |
182 |
56 |
ejabberd_router:route(From, To, Acc, Packet). |
183 |
|
|
184 |
|
register_iq_response_handler(Host, ID, Module, Function) -> |
185 |
:-( |
register_iq_response_handler(Host, ID, Module, Function, undefined). |
186 |
|
|
187 |
|
-spec register_iq_response_handler(_Host :: jid:server(), |
188 |
|
ID :: id(), |
189 |
|
Module :: atom(), |
190 |
|
Function :: fun(), |
191 |
|
Timeout :: 'undefined' | pos_integer()) -> any(). |
192 |
|
register_iq_response_handler(_Host, ID, Module, Function, Timeout0) -> |
193 |
56 |
Timeout = case Timeout0 of |
194 |
|
undefined -> |
195 |
44 |
?IQ_TIMEOUT; |
196 |
|
N when is_integer(N), N > 0 -> |
197 |
12 |
N |
198 |
|
end, |
199 |
56 |
TRef = erlang:start_timer(Timeout, ejabberd_local, ID), |
200 |
56 |
mnesia:dirty_write(#iq_response{id = ID, |
201 |
|
module = Module, |
202 |
|
function = Function, |
203 |
|
timer = TRef}). |
204 |
|
|
205 |
|
-spec register_iq_handler(Domain :: jid:server(), Namespace :: binary(), |
206 |
|
IQHandler :: mongoose_iq_handler:t()) -> ok. |
207 |
|
register_iq_handler(Domain, XMLNS, IQHandler) -> |
208 |
175 |
ejabberd_local ! {register_iq_handler, Domain, XMLNS, IQHandler}, |
209 |
175 |
ok. |
210 |
|
|
211 |
|
-spec sync() -> ok. |
212 |
|
sync() -> |
213 |
155 |
gen_server:call(ejabberd_local, sync). |
214 |
|
|
215 |
|
-spec unregister_iq_response_handler(_Host :: jid:server(), |
216 |
|
ID :: id()) -> 'ok'. |
217 |
|
unregister_iq_response_handler(_Host, ID) -> |
218 |
:-( |
catch get_iq_callback(ID), |
219 |
:-( |
ok. |
220 |
|
|
221 |
|
-spec unregister_iq_handler(Domain :: jid:server(), Namespace :: binary()) -> ok. |
222 |
|
unregister_iq_handler(Domain, XMLNS) -> |
223 |
139 |
ejabberd_local ! {unregister_iq_handler, Domain, XMLNS}, |
224 |
139 |
ok. |
225 |
|
|
226 |
|
-spec bounce_resource_packet(Acc :: mongoose_acc:t(), |
227 |
|
From :: jid:jid(), |
228 |
|
To :: jid:jid(), |
229 |
|
El :: exml:element()) -> mongoose_acc:t(). |
230 |
|
bounce_resource_packet(Acc, From, To, El) -> |
231 |
2 |
{Acc1, Err} = jlib:make_error_reply(Acc, El, mongoose_xmpp_errors:item_not_found()), |
232 |
2 |
ejabberd_router:route(To, From, Acc1, Err), |
233 |
2 |
Acc. |
234 |
|
|
235 |
|
-spec register_host(Host :: jid:server()) -> ok. |
236 |
|
register_host(Host) -> |
237 |
23 |
gen_server:call(?MODULE, {register_host, Host}). |
238 |
|
|
239 |
|
-spec unregister_host(Host :: jid:server()) -> ok. |
240 |
|
unregister_host(Host) -> |
241 |
7 |
gen_server:call(?MODULE, {unregister_host, Host}). |
242 |
|
|
243 |
|
-spec disco_local_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). |
244 |
|
disco_local_features(Acc = #{to_jid := #jid{lserver = LServer}, node := <<>>}) -> |
245 |
156 |
Features = [Feature || {_, Feature} <- ets:lookup(?NSTABLE, LServer)], |
246 |
156 |
mongoose_disco:add_features(Features, Acc); |
247 |
|
disco_local_features(Acc) -> |
248 |
5 |
Acc. |
249 |
|
|
250 |
|
%%==================================================================== |
251 |
|
%% API |
252 |
|
%%==================================================================== |
253 |
|
|
254 |
|
node_cleanup(Acc, Node) -> |
255 |
:-( |
F = fun() -> |
256 |
:-( |
Keys = mnesia:select( |
257 |
|
iq_response, |
258 |
|
[{#iq_response{timer = '$1', id = '$2', _ = '_'}, |
259 |
|
[{'==', {node, '$1'}, Node}], |
260 |
|
['$2']}]), |
261 |
:-( |
lists:foreach(fun(Key) -> |
262 |
:-( |
mnesia:delete({iq_response, Key}) |
263 |
|
end, Keys) |
264 |
|
end, |
265 |
:-( |
Res = mnesia:async_dirty(F), |
266 |
:-( |
maps:put(?MODULE, Res, Acc). |
267 |
|
|
268 |
|
%%==================================================================== |
269 |
|
%% gen_server callbacks |
270 |
|
%%==================================================================== |
271 |
|
|
272 |
|
%%-------------------------------------------------------------------- |
273 |
|
%% Function: init(Args) -> {ok, State} | |
274 |
|
%% {ok, State, Timeout} | |
275 |
|
%% ignore | |
276 |
|
%% {stop, Reason} |
277 |
|
%% Description: Initiates the server |
278 |
|
%%-------------------------------------------------------------------- |
279 |
|
init([]) -> |
280 |
80 |
catch ets:new(?IQTABLE, [named_table, protected, {read_concurrency, true}]), |
281 |
80 |
catch ets:new(?NSTABLE, [named_table, bag, protected, {read_concurrency, true}]), |
282 |
80 |
update_table(), |
283 |
80 |
mnesia:create_table(iq_response, |
284 |
|
[{ram_copies, [node()]}, |
285 |
|
{attributes, record_info(fields, iq_response)}]), |
286 |
80 |
mnesia:add_table_copy(iq_response, node(), ram_copies), |
287 |
80 |
ejabberd_hooks:add(hooks()), |
288 |
80 |
{ok, #state{}}. |
289 |
|
|
290 |
|
%%-------------------------------------------------------------------- |
291 |
|
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | |
292 |
|
%% {reply, Reply, State, Timeout} | |
293 |
|
%% {noreply, State} | |
294 |
|
%% {noreply, State, Timeout} | |
295 |
|
%% {stop, Reason, Reply, State} | |
296 |
|
%% {stop, Reason, State} |
297 |
|
%% Description: Handling call messages |
298 |
|
%%-------------------------------------------------------------------- |
299 |
|
handle_call({unregister_host, Host}, _From, State) -> |
300 |
7 |
Node = node(), |
301 |
7 |
[ejabberd_c2s:stop(Pid) |
302 |
7 |
|| #session{sid = {_, Pid}} <- ejabberd_sm:get_vh_session_list(Host), |
303 |
3 |
node(Pid) =:= Node], |
304 |
7 |
do_unregister_host(Host), |
305 |
7 |
{reply, ok, State}; |
306 |
|
handle_call({register_host, Host}, _From, State) -> |
307 |
23 |
do_register_host(Host), |
308 |
23 |
{reply, ok, State}; |
309 |
|
handle_call(sync, _From, State) -> |
310 |
155 |
{reply, ok, State}; |
311 |
|
handle_call(_Request, _From, State) -> |
312 |
:-( |
Reply = ok, |
313 |
:-( |
{reply, Reply, State}. |
314 |
|
|
315 |
|
%%-------------------------------------------------------------------- |
316 |
|
%% Function: handle_cast(Msg, State) -> {noreply, State} | |
317 |
|
%% {noreply, State, Timeout} | |
318 |
|
%% {stop, Reason, State} |
319 |
|
%% Description: Handling cast messages |
320 |
|
%%-------------------------------------------------------------------- |
321 |
|
handle_cast(_Msg, State) -> |
322 |
:-( |
{noreply, State}. |
323 |
|
|
324 |
|
%%-------------------------------------------------------------------- |
325 |
|
%% Function: handle_info(Info, State) -> {noreply, State} | |
326 |
|
%% {noreply, State, Timeout} | |
327 |
|
%% {stop, Reason, State} |
328 |
|
%% Description: Handling all non call/cast messages |
329 |
|
%%-------------------------------------------------------------------- |
330 |
|
handle_info({route, Acc, From, To, El}, State) -> |
331 |
:-( |
process_packet(Acc, From, To, El, #{}), |
332 |
:-( |
{noreply, State}; |
333 |
|
handle_info({register_iq_handler, Host, XMLNS, IQHandler}, State) -> |
334 |
175 |
ets:insert(?NSTABLE, {Host, XMLNS}), |
335 |
175 |
ets:insert(?IQTABLE, {{XMLNS, Host}, IQHandler}), |
336 |
175 |
{noreply, State}; |
337 |
|
handle_info({unregister_iq_handler, Host, XMLNS}, State) -> |
338 |
139 |
case ets:lookup(?IQTABLE, {XMLNS, Host}) of |
339 |
|
[{_, IQHandler}] -> |
340 |
139 |
gen_iq_component:stop_iq_handler(IQHandler), |
341 |
139 |
ets:delete_object(?NSTABLE, {Host, XMLNS}), |
342 |
139 |
ets:delete(?IQTABLE, {XMLNS, Host}); |
343 |
|
_ -> |
344 |
:-( |
ok |
345 |
|
end, |
346 |
139 |
{noreply, State}; |
347 |
|
handle_info({timeout, _TRef, ID}, State) -> |
348 |
6 |
process_iq_timeout(ID), |
349 |
6 |
{noreply, State}; |
350 |
|
handle_info(_Info, State) -> |
351 |
:-( |
{noreply, State}. |
352 |
|
|
353 |
|
%%-------------------------------------------------------------------- |
354 |
|
%% Function: terminate(Reason, State) -> void() |
355 |
|
%% Description: This function is called by a gen_server when it is about to |
356 |
|
%% terminate. It should be the opposite of Module:init/1 and do any necessary |
357 |
|
%% cleaning up. When it returns, the gen_server terminates with Reason. |
358 |
|
%% The return value is ignored. |
359 |
|
%%-------------------------------------------------------------------- |
360 |
|
terminate(_Reason, _State) -> |
361 |
:-( |
ejabberd_hooks:delete(hooks()). |
362 |
|
|
363 |
|
%%-------------------------------------------------------------------- |
364 |
|
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} |
365 |
|
%% Description: Convert process state when code is changed |
366 |
|
%%-------------------------------------------------------------------- |
367 |
|
code_change(_OldVsn, State, _Extra) -> |
368 |
:-( |
{ok, State}. |
369 |
|
|
370 |
|
%%-------------------------------------------------------------------- |
371 |
|
%%% Internal functions |
372 |
|
%%-------------------------------------------------------------------- |
373 |
|
|
374 |
|
hooks() -> |
375 |
80 |
[{node_cleanup, global, ?MODULE, node_cleanup, 50} | |
376 |
409 |
[{disco_local_features, HostType, ?MODULE, disco_local_features, 99} || |
377 |
80 |
HostType <- ?ALL_HOST_TYPES]]. |
378 |
|
|
379 |
|
-spec do_route(Acc :: mongoose_acc:t(), |
380 |
|
From :: jid:jid(), |
381 |
|
To :: jid:jid(), |
382 |
|
El :: exml:element()) -> mongoose_acc:t(). |
383 |
|
do_route(Acc, From, To, El) -> |
384 |
14511 |
?LOG_DEBUG(#{what => local_routing, acc => Acc}), |
385 |
14511 |
case directed_to(To) of |
386 |
|
user -> |
387 |
14344 |
ejabberd_sm:route(From, To, Acc, El); |
388 |
|
server -> |
389 |
165 |
case El#xmlel.name of |
390 |
|
<<"iq">> -> |
391 |
165 |
process_iq(Acc, From, To, El); |
392 |
|
_ -> |
393 |
:-( |
Acc |
394 |
|
end; |
395 |
|
local_resource -> |
396 |
2 |
case mongoose_acc:stanza_type(Acc) of |
397 |
:-( |
<<"error">> -> Acc; |
398 |
:-( |
<<"result">> -> Acc; |
399 |
2 |
_ -> bounce_resource_packet(Acc, From, To, El) |
400 |
|
end |
401 |
|
end. |
402 |
|
|
403 |
|
-spec directed_to(jid:jid()) -> user | server | local_resource. |
404 |
|
directed_to(To) -> |
405 |
14511 |
directed_to(To#jid.luser, To#jid.lresource). |
406 |
|
|
407 |
|
directed_to(<<>>, <<>>) -> |
408 |
165 |
server; |
409 |
|
directed_to(<<>>, _) -> |
410 |
2 |
local_resource; |
411 |
|
directed_to(_, _) -> |
412 |
14344 |
user. |
413 |
|
|
414 |
|
-spec update_table() -> ok | {atomic|aborted, _}. |
415 |
|
update_table() -> |
416 |
80 |
case catch mnesia:table_info(iq_response, attributes) of |
417 |
|
[id, module, function] -> |
418 |
:-( |
mnesia:delete_table(iq_response); |
419 |
|
[id, module, function, timer] -> |
420 |
53 |
ok; |
421 |
|
{'EXIT', _} -> |
422 |
27 |
ok |
423 |
|
end. |
424 |
|
|
425 |
|
-spec get_iq_callback(ID :: id()) -> 'error' | {'ok', Mod :: atom(), fun() | atom()}. |
426 |
|
get_iq_callback(ID) -> |
427 |
56 |
case mnesia:dirty_read(iq_response, ID) of |
428 |
|
[#iq_response{module = Module, timer = TRef, |
429 |
|
function = Function}] -> |
430 |
56 |
cancel_timer(TRef), |
431 |
56 |
mnesia:dirty_delete(iq_response, ID), |
432 |
56 |
{ok, Module, Function}; |
433 |
|
_ -> |
434 |
:-( |
error |
435 |
|
end. |
436 |
|
|
437 |
|
-spec process_iq_timeout(id()) -> id(). |
438 |
|
process_iq_timeout(ID) -> |
439 |
6 |
spawn(fun process_iq_timeout/0) ! ID. |
440 |
|
|
441 |
|
-spec process_iq_timeout() -> ok | any(). |
442 |
|
process_iq_timeout() -> |
443 |
6 |
receive |
444 |
|
ID -> |
445 |
6 |
case get_iq_callback(ID) of |
446 |
|
{ok, undefined, Function} -> |
447 |
6 |
Function(undefined, undefined, undefined, timeout); |
448 |
|
_ -> |
449 |
:-( |
ok |
450 |
|
end |
451 |
|
after 5000 -> |
452 |
:-( |
ok |
453 |
|
end. |
454 |
|
|
455 |
|
-spec cancel_timer(reference()) -> 'ok'. |
456 |
|
cancel_timer(TRef) -> |
457 |
56 |
case erlang:cancel_timer(TRef) of |
458 |
|
false -> |
459 |
6 |
receive |
460 |
|
{timeout, TRef, _} -> |
461 |
:-( |
ok |
462 |
|
after 0 -> |
463 |
6 |
ok |
464 |
|
end; |
465 |
|
_ -> |
466 |
50 |
ok |
467 |
|
end. |
468 |
|
|
469 |
|
do_register_host(Host) -> |
470 |
23 |
mongoose_router:register_route(Host, mongoose_packet_handler:new(?MODULE)). |
471 |
|
|
472 |
|
do_unregister_host(Host) -> |
473 |
7 |
mongoose_router:unregister_route(Host). |