diff options
author | Badlop <badlop@process-one.net> | 2009-01-12 20:03:02 +0000 |
---|---|---|
committer | Badlop <badlop@process-one.net> | 2009-01-12 20:03:02 +0000 |
commit | ba2eb355909b474284c4ab68226fcddf60c742d3 (patch) | |
tree | 9b5b7a23d56b3c775c4ad4671e89cf89819e727e /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.erl | 12 | ||||
-rw-r--r-- | src/ejabberd_listener.erl | 169 | ||||
-rw-r--r-- | src/mod_proxy65/mod_proxy65_service.erl | 22 | ||||
-rw-r--r-- | src/mod_proxy65/mod_proxy65_stream.erl | 3 | ||||
-rw-r--r-- | src/web/ejabberd_web_admin.erl | 84 |
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. |