summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBadlop <badlop@process-one.net>2009-01-12 20:03:02 +0000
committerBadlop <badlop@process-one.net>2009-01-12 20:03:02 +0000
commitba2eb355909b474284c4ab68226fcddf60c742d3 (patch)
tree9b5b7a23d56b3c775c4ad4671e89cf89819e727e /src
parent* src/web/ejabberd_web_admin.erl: New appearance of WebAdmin logo, (diff)
* src/ejabberd_listener.erl: New way to configure IP address and
IP version of listener. Support for definition of IP address in string format, and implicit definition of IP version (EJAB-388). Support for defining several listeners: all with same port number but different IP addresses (EJAB-389)(thanks to Fabrice Colliot and Sergei Golovan). Better report in WebAdmin of problem when starting a listener. The old configuration method of ip tuple and inet6 is fully supported for backwards compatibility, but is not documented in the Guide anymore. * src/ejabberd_config.erl: Likewise * src/mod_proxy65/mod_proxy65_stream.erl: Likewise * src/mod_proxy65/mod_proxy65_service.erl: Likewise * src/web/ejabberd_web_admin.erl: Likewise * doc/guide.tex: Document the new way to configure IP address and IP version of listener, undocument options ip and inet6 * doc/guide.html: Likewise SVN Revision: 1812
Diffstat (limited to 'src')
-rw-r--r--src/ejabberd_config.erl12
-rw-r--r--src/ejabberd_listener.erl169
-rw-r--r--src/mod_proxy65/mod_proxy65_service.erl22
-rw-r--r--src/mod_proxy65/mod_proxy65_stream.erl3
-rw-r--r--src/web/ejabberd_web_admin.erl84
5 files changed, 224 insertions, 66 deletions
diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl
index c8005529..ecf8ec30 100644
--- a/src/ejabberd_config.erl
+++ b/src/ejabberd_config.erl
@@ -324,8 +324,16 @@ process_term(Term, State) ->
{host_config, Host, Terms} ->
lists:foldl(fun(T, S) -> process_host_term(T, Host, S) end,
State, Terms);
- {listen, Val} ->
- add_option(listen, Val, State);
+ {listen, Listeners} ->
+ Listeners2 =
+ lists:map(
+ fun({PortIP, Module, Opts}) ->
+ {Port, IPT, _, _, OptsClean} =
+ ejabberd_listener:parse_listener_portip(PortIP, Opts),
+ {{Port, IPT}, Module, OptsClean}
+ end,
+ Listeners),
+ add_option(listen, Listeners2, State);
{language, Val} ->
add_option(language, Val, State);
{outgoing_s2s_port, Port} ->
diff --git a/src/ejabberd_listener.erl b/src/ejabberd_listener.erl
index 93ae78a7..cd5ca089 100644
--- a/src/ejabberd_listener.erl
+++ b/src/ejabberd_listener.erl
@@ -32,6 +32,7 @@
start_listeners/0,
start_listener/3,
stop_listener/2,
+ parse_listener_portip/2,
add_listener/3,
delete_listener/2
]).
@@ -53,12 +54,25 @@ start_listeners() ->
undefined ->
ignore;
Ls ->
- lists:map(
- fun({Port, Module, Opts}) ->
- start_listener(Port, Module, Opts)
- end, Ls)
+ Ls2 = lists:map(
+ fun({Port, Module, Opts}) ->
+ start_listener(Port, Module, Opts)
+ end, Ls),
+ report_duplicated_portips(Ls),
+ {ok, {{one_for_one, 10, 1}, Ls2}}
end.
+report_duplicated_portips(L) ->
+ LKeys = [Port || {Port, _, _} <- L],
+ LNoDupsKeys = proplists:get_keys(L),
+ case LKeys -- LNoDupsKeys of
+ [] -> ok;
+ Dups ->
+ ?CRITICAL_MSG("In the ejabberd configuration there are duplicated "
+ "Port number + IP address:~n ~p",
+ [Dups])
+ end.
+
start(Port, Module, Opts) ->
%% Check if the module is an ejabberd listener or an independent listener
ModuleRaw = strip_frontend(Module),
@@ -67,11 +81,11 @@ start(Port, Module, Opts) ->
_ -> start_dependent(Port, Module, Opts)
end.
+%% -> {ok, Pid} | {error, ErrorMessage}
start_dependent(Port, Module, Opts) ->
case includes_deprecated_ssl_option(Opts) of
false ->
- {ok, proc_lib:spawn_link(?MODULE, init,
- [Port, Module, Opts])};
+ proc_lib:start_link(?MODULE, init, [Port, Module, Opts]);
true ->
SSLErr="There is a problem with your ejabberd configuration file: "
"the option 'ssl' for listening sockets is no longer available."
@@ -91,13 +105,16 @@ includes_deprecated_ssl_option(Opts) ->
lists:member(ssl, Opts)
end.
-init(Port, Module, Opts) ->
+init(PortIP, Module, Opts1) ->
+ {Port, IPT, IPS, IPV, OptsClean} = parse_listener_portip(PortIP, Opts1),
+ %% The first inet|inet6 and the last {ip, _} work,
+ %% so overriding those in Opts
+ Opts = [IPV | OptsClean] ++ [{ip, IPT}],
SockOpts = lists:filter(fun({ip, _}) -> true;
(inet6) -> true;
(inet) -> true;
(_) -> false
end, Opts),
-
Res = gen_tcp:listen(Port, [binary,
{packet, 0},
{active, false},
@@ -108,13 +125,82 @@ init(Port, Module, Opts) ->
SockOpts]),
case Res of
{ok, ListenSocket} ->
+ %% Inform my parent that this port was opened succesfully
+ proc_lib:init_ack({ok, self()}),
+ %% And now start accepting connection attempts
accept(ListenSocket, Module, Opts);
{error, Reason} ->
- ?ERROR_MSG("Failed to open socket for ~p: ~p",
- [{Port, Module, Opts}, Reason]),
- error
+ ReasonT = case Reason of
+ eaddrnotavail -> "IP address not available: " ++ IPS;
+ _ -> atom_to_list(Reason)
+ end,
+ ?ERROR_MSG("Failed to open socket:~n ~p~nReason: ~s",
+ [{Port, Module, SockOpts}, ReasonT]),
+ throw(ReasonT)
+ end.
+
+%% @spec (PortIP, Opts) -> {Port, IPT, IPS, IPV, OptsClean}
+%% where
+%% PortIP = Port | {Port, IPT | IPS}
+%% Port = integer()
+%% IPT = tuple()
+%% IPS = string()
+%% IPV = inet | inet6
+%% Opts = [IPV | {ip, IPT} | atom() | tuple()]
+%% OptsClean = [atom() | tuple()]
+%% @doc Parse any kind of ejabberd listener specification.
+%% The parsed options are returned in several formats.
+%% OptsClean does not include inet/inet6 or ip options.
+%% Opts can include the options inet6 and {ip, Tuple},
+%% but they are only used when no IP address was specified in the PortIP.
+%% The IP version (either IPv4 or IPv6) is inferred from the IP address type,
+%% so the option inet/inet6 is only used when no IP is specified at all.
+parse_listener_portip(PortIP, Opts) ->
+ {IPOpt, Opts2} = strip_ip_option(Opts),
+ {IPVOpt, OptsClean} = case lists:member(inet6, Opts2) of
+ true -> {inet6, Opts2 -- [inet6]};
+ false -> {inet, Opts2}
+ end,
+ {Port, IPT, IPS} = case PortIP of
+ P when is_integer(P) ->
+ T = get_ip_tuple(IPOpt, IPVOpt),
+ S = inet_parse:ntoa(T),
+ {P, T, S};
+ {P, T} when is_integer(P) and is_tuple(T) ->
+ S = inet_parse:ntoa(T),
+ {P, T, S};
+ {P, S} when is_integer(P) and is_list(S) ->
+ [S | _] = string:tokens(S, "/"),
+ {ok, T} = inet_parse:address(S),
+ {P, T, S}
+ end,
+ IPV = case size(IPT) of
+ 4 -> inet;
+ 8 -> inet6
+ end,
+ {Port, IPT, IPS, IPV, OptsClean}.
+
+strip_ip_option(Opts) ->
+ {IPL, OptsNoIP} = lists:partition(
+ fun({ip, _}) -> true;
+ (_) -> false
+ end,
+ Opts),
+ case IPL of
+ %% Only the first ip option is considered
+ [{ip, T1} | _] when is_tuple(T1) ->
+ {T1, OptsNoIP};
+ [] ->
+ {no_ip_option, OptsNoIP}
end.
+get_ip_tuple(no_ip_option, inet) ->
+ {0, 0, 0, 0};
+get_ip_tuple(no_ip_option, inet6) ->
+ {0, 0, 0, 0, 0, 0, 0, 0};
+get_ip_tuple(IPOpt, _IPVOpt) ->
+ IPOpt.
+
accept(ListenSocket, Module, Opts) ->
case gen_tcp:accept(ListenSocket) of
{ok, Socket} ->
@@ -137,11 +223,14 @@ accept(ListenSocket, Module, Opts) ->
accept(ListenSocket, Module, Opts)
end.
+%% @spec (Port, Module, Opts) -> {ok, Pid} | {error, Error}
start_listener(Port, Module, Opts) ->
+ %% It is only required to start the supervisor in some cases.
+ %% But it doesn't hurt to attempt to start it for any listener.
+ %% So, it's normal (and harmless) that in most cases this call returns: {error, {already_started, pid()}}
start_module_sup(Port, Module),
start_listener_sup(Port, Module, Opts).
-%% Only required for some listeners, but doing for all doesn't hurt
start_module_sup(_Port, Module) ->
Proc1 = gen_mod:get_module_proc("sup", Module),
ChildSpec1 =
@@ -151,7 +240,7 @@ start_module_sup(_Port, Module) ->
infinity,
supervisor,
[ejabberd_tmp_sup]},
- catch supervisor:start_child(ejabberd_sup, ChildSpec1).
+ supervisor:start_child(ejabberd_sup, ChildSpec1).
start_listener_sup(Port, Module, Opts) ->
ChildSpec = {Port,
@@ -165,33 +254,55 @@ start_listener_sup(Port, Module, Opts) ->
stop_listener(Port, Module) ->
supervisor:terminate_child(ejabberd_listeners, Port),
supervisor:delete_child(ejabberd_listeners, Port),
-
Proc1 = gen_mod:get_module_proc("sup", Module),
supervisor:terminate_child(ejabberd_sup, Proc1),
supervisor:delete_child(ejabberd_sup, Proc1).
-add_listener(Port, Module, Opts) ->
- Ports = case ejabberd_config:get_local_option(listen) of
- undefined ->
- [];
- Ls ->
- Ls
- end,
- Ports1 = lists:keydelete(Port, 1, Ports),
- Ports2 = [{Port, Module, Opts} | Ports1],
- ejabberd_config:add_local_option(listen, Ports2),
- start_listener(Port, Module, Opts).
-
-delete_listener(Port, Module) ->
+%% @spec (PortIP, Module, Opts) -> {ok, Pid} | {error, Error}
+%% where
+%% PortIP = {Port, IPT | IPS}
+%% Port = integer()
+%% IPT = tuple()
+%% IPS = string()
+%% IPV = inet | inet6
+%% Module = atom()
+%% Opts = [IPV | {ip, IPT} | atom() | tuple()]
+%% @doc Add a listener and store in config if success
+add_listener(PortIP, Module, Opts) ->
+ case start_listener(PortIP, Module, Opts) of
+ {ok, _Pid} ->
+ Ports = case ejabberd_config:get_local_option(listen) of
+ undefined ->
+ [];
+ Ls ->
+ Ls
+ end,
+ Ports1 = lists:keydelete(PortIP, 1, Ports),
+ Ports2 = [{PortIP, Module, Opts} | Ports1],
+ ejabberd_config:add_local_option(listen, Ports2),
+ ok;
+ {error, {already_started, _Pid}} ->
+ {error, {already_started, PortIP}};
+ {error, Error} ->
+ {error, Error}
+ end.
+
+%% @spec (PortIP) -> ok
+%% where
+%% PortIP = {Port, IPT | IPS}
+%% Port = integer()
+%% IPT = tuple()
+%% IPS = string()
+delete_listener(PortIP, Module) ->
Ports = case ejabberd_config:get_local_option(listen) of
undefined ->
[];
Ls ->
Ls
end,
- Ports1 = lists:keydelete(Port, 1, Ports),
+ Ports1 = lists:keydelete(PortIP, 1, Ports),
ejabberd_config:add_local_option(listen, Ports1),
- stop_listener(Port, Module).
+ stop_listener(PortIP, Module).
is_frontend({frontend, _Module}) -> true;
is_frontend(_) -> false.
diff --git a/src/mod_proxy65/mod_proxy65_service.erl b/src/mod_proxy65/mod_proxy65_service.erl
index ba95ad4a..ee11e3db 100644
--- a/src/mod_proxy65/mod_proxy65_service.erl
+++ b/src/mod_proxy65/mod_proxy65_service.erl
@@ -52,6 +52,7 @@
name,
stream_addr,
port,
+ ip,
acl
}).
@@ -65,7 +66,7 @@ start_link(Host, Opts) ->
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
init([Host, Opts]) ->
- {_IP, State} = parse_options(Host, Opts),
+ State = parse_options(Host, Opts),
ejabberd_router:register_route(State#state.myhost),
{ok, State}.
@@ -89,8 +90,8 @@ handle_info({route, From, To, {xmlelement, "iq", _, _} = Packet}, State) ->
handle_info(_Info, State) ->
{noreply, State}.
-handle_call(get_port, _From, State) ->
- {reply, {port, State#state.port}, State};
+handle_call(get_port_ip, _From, State) ->
+ {reply, {port_ip, State#state.port, State#state.ip}, State};
handle_call(_Request, _From, State) ->
{reply, ok, State}.
@@ -105,14 +106,14 @@ code_change(_OldVsn, State, _Extra) ->
%%%------------------------
add_listener(Host, Opts) ->
- {IP, State} = parse_options(Host, Opts),
- NewOpts = [Host, {ip, IP} | Opts],
- ejabberd_listener:add_listener(State#state.port,mod_proxy65_stream,NewOpts).
+ State = parse_options(Host, Opts),
+ NewOpts = [Host | Opts],
+ ejabberd_listener:add_listener({State#state.port, State#state.ip}, mod_proxy65_stream, NewOpts).
delete_listener(Host) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- {port, Port} = gen_server:call(Proc, get_port),
- catch ejabberd_listener:delete_listener(Port, mod_proxy65_stream).
+ {port_ip, Port, IP} = gen_server:call(Proc, get_port_ip),
+ catch ejabberd_listener:delete_listener({Port, IP}, mod_proxy65_stream).
%%%------------------------
%%% IQ Processing
@@ -219,12 +220,13 @@ parse_options(ServerHost, Opts) ->
end,
StrIP = inet_parse:ntoa(IP),
StreamAddr = [{"jid", MyHost}, {"host", StrIP}, {"port", integer_to_list(Port)}],
- {IP, #state{myhost = MyHost,
+ #state{myhost = MyHost,
serverhost = ServerHost,
name = Name,
port = Port,
+ ip = IP,
stream_addr = StreamAddr,
- acl = ACL}}.
+ acl = ACL}.
%% Return the IP of the proxy host, or if not found, the ip of the xmpp domain
get_proxy_or_domainip(ServerHost, MyHost) ->
diff --git a/src/mod_proxy65/mod_proxy65_stream.erl b/src/mod_proxy65/mod_proxy65_stream.erl
index 40505041..c6d0103a 100644
--- a/src/mod_proxy65/mod_proxy65_stream.erl
+++ b/src/mod_proxy65/mod_proxy65_stream.erl
@@ -78,7 +78,8 @@ code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
%%-------------------------------
-start({gen_tcp, Socket}, [Host | Opts]) ->
+start({gen_tcp, Socket}, Opts1) ->
+ {[Host], Opts} = lists:partition(fun(O) -> is_list(O) end, Opts1),
Supervisor = gen_mod:get_module_proc(Host, ejabberd_mod_proxy65_sup),
supervisor:start_child(Supervisor, [Socket, Host, Opts]).
diff --git a/src/web/ejabberd_web_admin.erl b/src/web/ejabberd_web_admin.erl
index 1cbb20dc..cf3150b9 100644
--- a/src/web/ejabberd_web_admin.erl
+++ b/src/web/ejabberd_web_admin.erl
@@ -1809,9 +1809,14 @@ get_node(global, Node, ["ports"], Query, Lang) ->
ok;
{'EXIT', _Reason} ->
error;
+ {is_added, ok} ->
+ ok;
+ {is_added, {error, Reason}} ->
+ {error, io_lib:format("~p", [Reason])};
_ ->
nothing
end,
+ %% TODO: This sorting does not work when [{{Port, IP}, Module, Opts}]
NewPorts = lists:sort(
rpc:call(Node, ejabberd_config, get_local_option, [listen])),
H1String = ?T("Listened Ports at ") ++ atom_to_list(Node),
@@ -1819,6 +1824,7 @@ get_node(global, Node, ["ports"], Query, Lang) ->
case Res of
ok -> [?CT("Submitted"), ?P];
error -> [?CT("Bad format"), ?P];
+ {error, ReasonT} -> [?CT("Problem: "), ?C(ReasonT), ?P];
nothing -> []
end ++
[?XAE("form", [{"action", ""}, {"method", "post"}],
@@ -2057,58 +2063,77 @@ node_ports_to_xhtml(Ports, Lang) ->
[?XE("thead",
[?XE("tr",
[?XCT("td", "Port"),
+ ?XCT("td", "IP"),
?XCT("td", "Module"),
?XCT("td", "Options")
])]),
?XE("tbody",
lists:map(
- fun({Port, Module, Opts} = _E) ->
- SPort = integer_to_list(Port),
+ fun({PortIP, Module, Opts} = _E) ->
+ {_Port, SPort, _TIP, SIP, SSPort, OptsClean} =
+ get_port_data(PortIP, Opts),
SModule = atom_to_list(Module),
%%ID = term_to_id(E),
?XE("tr",
- [?XC("td", SPort),
- ?XE("td", [?INPUT("text", "module" ++ SPort,
- SModule)]),
- ?XE("td", [?INPUTS("text", "opts" ++ SPort,
- term_to_string(Opts), "40")]),
- ?XE("td", [?INPUTT("submit", "add" ++ SPort,
+ [?XAE("td", [{"size", "6"}], [?C(SPort)]),
+ ?XAE("td", [{"size", "15"}], [?C(SIP)]),
+ ?XE("td", [?INPUTS("text", "module" ++ SSPort,
+ SModule, "15")]),
+ ?XE("td", [?INPUTS("text", "opts" ++ SSPort,
+ term_to_string(OptsClean), "40")]),
+ ?XE("td", [?INPUTT("submit", "add" ++ SSPort,
"Update")]),
- ?XE("td", [?INPUTT("submit", "delete" ++ SPort,
+ ?XE("td", [?INPUTT("submit", "delete" ++ SSPort,
"Delete")])
]
)
end, Ports) ++
[?XE("tr",
[?XE("td", [?INPUTS("text", "portnew", "", "6")]),
- ?XE("td", [?INPUT("text", "modulenew", "")]),
- ?XE("td", [?INPUTS("text", "optsnew", "", "40")]),
+ ?XE("td", [?INPUTS("text", "ipnew", "0.0.0.0", "15")]),
+ ?XE("td", [?INPUTS("text", "modulenew", "", "17")]),
+ ?XE("td", [?INPUTS("text", "optsnew", "[]", "40")]),
?XAE("td", [{"colspan", "2"}],
[?INPUTT("submit", "addnew", "Add New")])
]
)]
)]).
+get_port_data(PortIP, Opts) ->
+ {Port, IPT, IPS, _IPV, OptsClean} = ejabberd_listener:parse_listener_portip(PortIP, Opts),
+ SPort = io_lib:format("~p", [Port]),
+
+ SSPort = lists:flatten(
+ lists:map(
+ fun(N) -> io_lib:format("~.16b", [N]) end,
+ binary_to_list(crypto:md5(SPort++IPS)))),
+ {Port, SPort, IPT, IPS, SSPort, OptsClean}.
+
node_ports_parse_query(Node, Ports, Query) ->
lists:foreach(
- fun({Port, Module1, _Opts1}) ->
- SPort = integer_to_list(Port),
- case lists:keysearch("add" ++ SPort, 1, Query) of
+ fun({PortIP, Module1, Opts1}) ->
+ {Port, _SPort, TIP, _SIP, SSPort, _OptsClean} =
+ get_port_data(PortIP, Opts1),
+ case lists:keysearch("add" ++ SSPort, 1, Query) of
{value, _} ->
+ PortIP2 = {Port, TIP},
{{value, {_, SModule}}, {value, {_, SOpts}}} =
- {lists:keysearch("module" ++ SPort, 1, Query),
- lists:keysearch("opts" ++ SPort, 1, Query)},
+ {lists:keysearch("module" ++ SSPort, 1, Query),
+ lists:keysearch("opts" ++ SSPort, 1, Query)},
Module = list_to_atom(SModule),
{ok, Tokens, _} = erl_scan:string(SOpts ++ "."),
{ok, Opts} = erl_parse:parse_term(Tokens),
- rpc:call(Node, ejabberd_listener, delete_listener, [Port, Module]),
- rpc:call(Node, ejabberd_listener, add_listener, [Port, Module, Opts]),
- throw(submitted);
+ rpc:call(Node, ejabberd_listener, delete_listener,
+ [PortIP2, Module1]),
+ R=rpc:call(Node, ejabberd_listener, add_listener,
+ [PortIP2, Module, Opts]),
+ throw({is_added, R});
_ ->
- case lists:keysearch("delete" ++ SPort, 1, Query) of
+ case lists:keysearch("delete" ++ SSPort, 1, Query) of
{value, _} ->
- rpc:call(Node, ejabberd_listener, delete_listener, [Port, Module1]),
+ rpc:call(Node, ejabberd_listener, delete_listener,
+ [PortIP, Module1]),
throw(submitted);
_ ->
ok
@@ -2118,17 +2143,28 @@ node_ports_parse_query(Node, Ports, Query) ->
case lists:keysearch("addnew", 1, Query) of
{value, _} ->
{{value, {_, SPort}},
+ {value, {_, STIP}}, %% It is a string that may represent a tuple
{value, {_, SModule}},
{value, {_, SOpts}}} =
{lists:keysearch("portnew", 1, Query),
+ lists:keysearch("ipnew", 1, Query),
lists:keysearch("modulenew", 1, Query),
lists:keysearch("optsnew", 1, Query)},
- Port = list_to_integer(SPort),
+ {ok, Toks, _} = erl_scan:string(SPort ++ "."),
+ {ok, Port2} = erl_parse:parse_term(Toks),
+ {ok, ToksIP, _} = erl_scan:string(STIP ++ "."),
+ STIP2 = case erl_parse:parse_term(ToksIP) of
+ {ok, IPTParsed} -> IPTParsed;
+ {error, _} -> STIP
+ end,
Module = list_to_atom(SModule),
{ok, Tokens, _} = erl_scan:string(SOpts ++ "."),
{ok, Opts} = erl_parse:parse_term(Tokens),
- rpc:call(Node, ejabberd_listener, add_listener, [Port, Module, Opts]),
- throw(submitted);
+ {Port2, _SPort, IP2, _SIP, _SSPort, OptsClean} =
+ get_port_data({Port2, STIP2}, Opts),
+ R=rpc:call(Node, ejabberd_listener, add_listener,
+ [{Port2, IP2}, Module, OptsClean]),
+ throw({is_added, R});
_ ->
ok
end.