$NetBSD: patch-ag,v 1.2 2009/02/03 12:07:26 martti Exp $ Modified to use IPv6/v4 patch (https://support.process-one.net/browse/EJAB-389) --- src/ejabberd_listener.erl.orig 2009-01-14 11:54:15.000000000 +0200 +++ src/ejabberd_listener.erl 2009-02-03 08:21:35.000000000 +0200 @@ -32,6 +32,7 @@ init_ssl/4, start_listener/3, stop_listener/1, + parse_listener_portip/2, add_listener/3, delete_listener/1 ]). @@ -50,8 +51,7 @@ undefined -> ignore; Ls -> - {ok, {{one_for_one, 10, 1}, - lists:map( + Ls2 = lists:map( fun({Port, Module, Opts}) -> {Port, {?MODULE, start, [Port, Module, Opts]}, @@ -59,7 +59,20 @@ brutal_kill, worker, [?MODULE]} - end, Ls)}} + 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. @@ -84,7 +97,11 @@ end 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; @@ -103,11 +120,77 @@ {ok, ListenSocket} -> 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]), + {error, 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} -> @@ -182,6 +265,7 @@ end. +%% @spec (Port, Module, Opts) -> {ok, Pid} | {error, Error} start_listener(Port, Module, Opts) -> ChildSpec = {Port, {?MODULE, start, [Port, Module, Opts]}, @@ -195,26 +279,49 @@ supervisor:terminate_child(ejabberd_listeners, Port), supervisor:delete_child(ejabberd_listeners, Port). -add_listener(Port, Module, Opts) -> +%% @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(Port, 1, Ports), - Ports2 = [{Port, Module, Opts} | Ports1], + Ports1 = lists:keydelete(PortIP, 1, Ports), + Ports2 = [{PortIP, Module, Opts} | Ports1], ejabberd_config:add_local_option(listen, Ports2), - start_listener(Port, Module, Opts). + ok; + {error, {already_started, _Pid}} -> + {error, {already_started, PortIP}}; + {error, Error} -> + {error, Error} + end. -delete_listener(Port) -> +%% @spec (PortIP) -> ok +%% where +%% PortIP = {Port, IPT | IPS} +%% Port = integer() +%% IPT = tuple() +%% IPS = string() +delete_listener(PortIP) -> 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). + stop_listener(PortIP).