1 |
|
%%%---------------------------------------------------------------------- |
2 |
|
%%% File : ejabberd_listener.erl |
3 |
|
%%% Author : Alexey Shchepin <alexey@process-one.net> |
4 |
|
%%% Purpose : Manage socket listener |
5 |
|
%%% Created : 16 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_listener). |
27 |
|
-author('alexey@process-one.net'). |
28 |
|
|
29 |
|
-export([start_link/0, |
30 |
|
init/1, |
31 |
|
start_listeners/0, |
32 |
|
start_listener/1, |
33 |
|
stop_listeners/0, |
34 |
|
stop_listener/1 |
35 |
|
]). |
36 |
|
|
37 |
|
%% Internal |
38 |
|
-export([format_error/1, socket_error/6]). |
39 |
|
|
40 |
|
-ignore_xref([start_link/0, init/1, start_listener/1, stop_listener/1]). |
41 |
|
-export_type([opts/0, mod/0]). |
42 |
|
|
43 |
|
-type opts() :: list(). |
44 |
|
-type mod() :: module(). |
45 |
|
|
46 |
|
-include("mongoose.hrl"). |
47 |
|
|
48 |
|
-spec start_link() -> 'ignore' | {'error', _} | {'ok', pid()}. |
49 |
|
start_link() -> |
50 |
82 |
supervisor:start_link({local, ejabberd_listeners}, ?MODULE, []). |
51 |
|
|
52 |
|
|
53 |
|
init(_) -> |
54 |
|
%bind_tcp_ports(), |
55 |
82 |
{ok, {{one_for_one, 10, 1}, []}}. |
56 |
|
|
57 |
|
|
58 |
|
-spec start_listeners() -> {'ok', {{_, _, _}, [any()]}}. |
59 |
|
start_listeners() -> |
60 |
82 |
Ls = mongoose_config:get_opt(listen), |
61 |
82 |
Ls2 = lists:map( |
62 |
|
fun(Listener) -> |
63 |
957 |
case start_listener(Listener) of |
64 |
957 |
{ok, _Pid} = R -> R; |
65 |
|
{error, Error} -> |
66 |
:-( |
throw(Error) |
67 |
|
end |
68 |
|
end, Ls), |
69 |
82 |
{ok, {{one_for_one, 10, 1}, Ls2}}. |
70 |
|
|
71 |
|
-spec start_listener(mongoose_listenet_config:listener()) -> {'error', pid()} | {'ok', _}. |
72 |
|
start_listener(Opts = #{module := Module}) -> |
73 |
1006 |
try |
74 |
|
%% It is only required to start the supervisor in some cases. |
75 |
|
%% But it doesn't hurt to attempt to start it for any listener. |
76 |
|
%% So, it's normal (and harmless) that in most cases this call returns: |
77 |
|
%% {error, {already_started, pid()}} |
78 |
1006 |
start_module_sup(Module), |
79 |
1006 |
start_listener_sup(Module, Opts) |
80 |
|
of |
81 |
1006 |
{ok, _Pid} = R -> R; |
82 |
|
{error, {already_started, Pid}} -> |
83 |
:-( |
{ok, Pid}; |
84 |
|
{error, Reason} = R -> |
85 |
:-( |
?LOG_CRITICAL(#{what => listener_failed_to_start, reason => Reason, |
86 |
|
text => <<"Failed to start a listener">>, |
87 |
:-( |
module => Module, opts => Opts}), |
88 |
:-( |
R |
89 |
|
catch Class:Reason:Stacktrace -> |
90 |
:-( |
?LOG_CRITICAL(#{what => listener_failed_to_start, |
91 |
|
text => <<"Failed to start a listener">>, |
92 |
|
module => Module, opts => Opts, |
93 |
:-( |
class => Class, reason => Reason, stacktrace => Stacktrace}), |
94 |
:-( |
{error, Reason} |
95 |
|
end. |
96 |
|
|
97 |
|
-spec start_module_sup(Module :: module()) |
98 |
|
-> {'error', _} | {'ok', 'undefined' | pid()} | {'ok', 'undefined' | pid(), _}. |
99 |
|
start_module_sup(Module) -> |
100 |
1006 |
Proc = gen_mod:get_module_proc("sup", Module), |
101 |
1006 |
ChildSpec = |
102 |
|
{Proc, |
103 |
|
{ejabberd_tmp_sup, start_link, [Proc, Module]}, |
104 |
|
permanent, |
105 |
|
infinity, |
106 |
|
supervisor, |
107 |
|
[ejabberd_tmp_sup]}, |
108 |
|
%% TODO Rewrite using ejabberd_sup:start_child |
109 |
|
%% This function is called more than once |
110 |
1006 |
supervisor:start_child(ejabberd_sup, ChildSpec). |
111 |
|
|
112 |
|
-spec start_listener_sup(module(), mongoose_listener_config:listener()) |
113 |
|
-> {'error', _} | {'ok', 'undefined' | pid()} | {'ok', 'undefined' | pid(), _}. |
114 |
|
start_listener_sup(Module, Listener = #{ip_address := IPAddr, port := Port, proto := Proto}) -> |
115 |
1006 |
ListenerId = mongoose_listener_config:listener_id(Listener), |
116 |
1006 |
Opts = mongoose_listener_config:prepare_opts(Listener), |
117 |
1006 |
case Module:socket_type() of |
118 |
|
independent -> |
119 |
578 |
Module:start_listener(ListenerId, Opts); |
120 |
|
_ -> |
121 |
428 |
SockOpts = mongoose_listener_config:filter_socket_opts(Opts), |
122 |
428 |
{SupModule, Kill, Type} = |
123 |
|
case Proto of |
124 |
:-( |
udp -> {mongoose_udp_listener, brutal_kill, worker}; |
125 |
428 |
_ -> {mongoose_tcp_listener, 1000, supervisor} |
126 |
|
end, |
127 |
428 |
ChildSpec = {ListenerId, |
128 |
|
{SupModule, start_link, |
129 |
|
[ListenerId, Module, Opts, SockOpts, Port, IPAddr]}, |
130 |
|
transient, Kill, Type, [SupModule]}, |
131 |
428 |
supervisor:start_child(ejabberd_listeners, ChildSpec) |
132 |
|
end. |
133 |
|
|
134 |
|
-spec stop_listeners() -> 'ok'. |
135 |
|
stop_listeners() -> |
136 |
82 |
Listeners = mongoose_config:get_opt(listen), |
137 |
82 |
lists:foreach(fun stop_listener/1, Listeners). |
138 |
|
|
139 |
|
-spec stop_listener(mongoose_listener_config:listener()) |
140 |
|
-> 'ok' | {'error', 'not_found' | 'restarting' | 'running' | 'simple_one_for_one'}. |
141 |
|
stop_listener(Listener) -> |
142 |
1006 |
ListenerId = mongoose_listener_config:listener_id(Listener), |
143 |
1006 |
supervisor:terminate_child(ejabberd_listeners, ListenerId), |
144 |
1006 |
supervisor:delete_child(ejabberd_listeners, ListenerId). |
145 |
|
|
146 |
|
%%% |
147 |
|
%%% Check options |
148 |
|
%%% |
149 |
|
|
150 |
|
socket_error(Reason, PortIP, Module, SockOpts, Port, IPS) -> |
151 |
:-( |
ReasonT = case Reason of |
152 |
|
eaddrnotavail -> |
153 |
:-( |
"IP address not available: " ++ IPS; |
154 |
|
eaddrinuse -> |
155 |
|
"IP address and port number already used: " |
156 |
:-( |
++IPS++" "++integer_to_list(Port); |
157 |
|
_ -> |
158 |
:-( |
format_error(Reason) |
159 |
|
end, |
160 |
:-( |
?LOG_ERROR(#{what => failed_to_open_socket, reason => ReasonT, |
161 |
:-( |
port => Port, module => Module, socket_option => SockOpts}), |
162 |
:-( |
throw({Reason, PortIP}). |
163 |
|
|
164 |
|
-spec format_error(atom()) -> string(). |
165 |
|
format_error(Reason) -> |
166 |
:-( |
case inet:format_error(Reason) of |
167 |
|
"unknown POSIX error" -> |
168 |
:-( |
atom_to_list(Reason); |
169 |
|
ReasonStr -> |
170 |
:-( |
ReasonStr |
171 |
|
end. |