diff --git a/ChangeLog b/ChangeLog index 4688874c9..fe1367479 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +2008-12-26 Badlop + + * src/ejabberd_config.erl: Option outgoing_s2s_options to define + s2s outgoing behaviour: IPv4, IPv6 and timeout (thanks to Stephan + Maka)(EJAB-665) + * src/ejabberd_s2s_out.erl: Likewise + * src/ejabberd_socket.erl: Likewise + * src/ejabberd.cfg.example: Likewise + * doc/guide.tex: Likewise + * doc/guide.html: Likewise + 2008-12-26 Evgeniy Khramtsov * src/odbc/ejabberd_odbc.erl: get rid of SERIALIZABLE isolation @@ -10,7 +21,7 @@ 2008-12-24 Badlop * src/aclocal.m4: Fixes in configure script: fix - disable-disable_zlib and disable-pam; in case of problems, PAM + disable-ejabberd_zlib and disable-pam; in case of problems, PAM verification aborts with error instead of warning. (EJAB-787) * src/configure.ac: Likewise * src/configure: Likewise diff --git a/doc/guide.html b/doc/guide.html index 2982f355d..6d143c335 100644 --- a/doc/guide.html +++ b/doc/guide.html @@ -745,6 +745,10 @@ use STARTTLS for s2s connections. file containing a SSL certificate.
{domain_certfile, Domain, Path}
Full path to the file containing the SSL certificate for a specific domain. +
{outgoing_s2s_options, Methods, Timeout}
+Specify which address families to try, in what order, and connect timeout in milliseconds. +By default it first tries connecting with IPv4, if that fails it tries using IPv6, +with a timeout of 10000 milliseconds.
{s2s_default_policy, allow|deny}
The default policy for incoming and outgoing s2s connections to other Jabber servers. The default value is allow. @@ -1032,6 +1036,10 @@ declarations of ACLs in the configuration file have the following syntax:
{resource, <resource>}
Matches any JID with a resource <resource>. Example:
{acl, mucklres, {resource, "muckl"}}.
+
{shared_group, <groupname>}
Matches any member of a Shared Roster Group with name <groupname> in the virtual host. Example: +
{acl, techgroupmembers, {shared_group, "techteam"}}.
+
{shared_group, <groupname>, <server>}
Matches any member of a Shared Roster Group with name <groupname> in the virtual host <server>. Example: +
{acl, techgroupmembers, {shared_group, "techteam", "example.org"}}.
 
{user_regexp, <regexp>}
Matches any local user with a name that matches <regexp> on local virtual hosts. Example:
{acl, tests, {user_regexp, "^test[0-9]*$"}}.
diff --git a/doc/guide.tex b/doc/guide.tex
index 9501c0cc5..afc63a3ff 100644
--- a/doc/guide.tex
+++ b/doc/guide.tex
@@ -870,6 +870,10 @@ There are some additional global options:
   file containing a SSL certificate.
   \titem{\{domain\_certfile, Domain, Path\}} \ind{options!domain\_certfile}
   Full path to the file containing the SSL certificate for a specific domain.
+  \titem{\{outgoing\_s2s\_options, Methods, Timeout\}} \ind{options!outgoing\_s2s\_options}
+  Specify which address families to try, in what order, and connect timeout in milliseconds.
+  By default it first tries connecting with IPv4, if that fails it tries using IPv6,
+  with a timeout of 10000 milliseconds.
   \titem{\{s2s\_default\_policy, allow|deny\}}
   The default policy for incoming and outgoing s2s connections to other Jabber servers.
   The default value is \term{allow}.
diff --git a/src/ejabberd.cfg.example b/src/ejabberd.cfg.example
index 6a6b52494..046db942b 100644
--- a/src/ejabberd.cfg.example
+++ b/src/ejabberd.cfg.example
@@ -186,6 +186,13 @@
 %%{{s2s_host, "goodhost.org"}, allow}.
 %%{{s2s_host, "badhost.org"}, deny}.
 
+%%
+%% Outgoing S2S options
+%%
+%% Preferred address families (which to try first) and connect timeout
+%% in milliseconds.
+%%
+%%{outgoing_s2s_options, [ipv4, ipv6], 10000}.
 
 %%%   ==============
 %%%   AUTHENTICATION
diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl
index 6ee0fa528..a5083424b 100644
--- a/src/ejabberd_config.erl
+++ b/src/ejabberd_config.erl
@@ -330,6 +330,8 @@ process_term(Term, State) ->
 	    add_option(language, Val, State);
 	{outgoing_s2s_port, Port} ->
 	    add_option(outgoing_s2s_port, Port, State);
+	{outgoing_s2s_options, Methods, Timeout} ->
+	    add_option(outgoing_s2s_options, {Methods, Timeout}, State);
 	{s2s_use_starttls, Port} ->
 	    add_option(s2s_use_starttls, Port, State);
 	{s2s_certfile, CertFile} ->
diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl
index aa4c7ed96..5f5d905f8 100644
--- a/src/ejabberd_s2s_out.erl
+++ b/src/ejabberd_s2s_out.erl
@@ -119,6 +119,8 @@
 -define(INVALID_XML_ERR,
 	xml:element_to_string(?SERR_XML_NOT_WELL_FORMED)).
 
+-define(SOCKET_DEFAULT_RESULT, {error, badarg}).
+
 %%%----------------------------------------------------------------------
 %%% API
 %%%----------------------------------------------------------------------
@@ -206,7 +208,7 @@ open_socket(init, StateData) ->
 				 _ ->
 				     open_socket1(Addr, Port)
 			     end
-		     end, {error, badarg}, AddrList) of
+		     end, ?SOCKET_DEFAULT_RESULT, AddrList) of
 	{ok, Socket} ->
 	    Version = if
 			  StateData#state.use_v10 ->
@@ -239,34 +241,40 @@ open_socket(_, StateData) ->
     {next_state, open_socket, StateData}.
 
 %%----------------------------------------------------------------------
-open_socket1(Addr, Port) ->
-    ?DEBUG("s2s_out: connecting to ~s:~p~n", [Addr, Port]),
-    Res = case catch ejabberd_socket:connect(
-		       Addr, Port,
-		       [binary, {packet, 0},
-			{active, false}]) of
-	      {ok, _Socket} = R -> R;
-	      {error, Reason1} ->
-		  ?DEBUG("s2s_out: connect return ~p~n", [Reason1]),
-		  catch ejabberd_socket:connect(
-			  Addr, Port,
-			  [binary, {packet, 0},
-			   {active, false}, inet6]);
-	      {'EXIT', Reason1} ->
-		  ?DEBUG("s2s_out: connect crashed ~p~n", [Reason1]),
-		  catch ejabberd_socket:connect(
-			  Addr, Port,
-			  [binary, {packet, 0},
-			   {active, false}, inet6])
-	  end,
-    case Res of
-	{ok, Socket} ->
-	    {ok, Socket};
-	{error, Reason} ->
-	    ?DEBUG("s2s_out: inet6 connect return ~p~n", [Reason]),
-	    {error, Reason};
+%% IPv4
+open_socket1({_,_,_,_} = Addr, Port) ->
+    open_socket2(inet, Addr, Port);
+
+%% IPv6
+open_socket1({_,_,_,_,_,_,_,_} = Addr, Port) ->
+    open_socket2(inet6, Addr, Port);
+
+%% Hostname
+open_socket1(Host, Port) ->
+    lists:foldl(fun(_Family, {ok, _Socket} = R) ->
+			R;
+		   (Family, _) ->
+			Addrs = get_addrs(Host, Family),
+			lists:foldl(fun(_Addr, {ok, _Socket} = R) ->
+					    R;
+				       (Addr, _) ->
+					    open_socket1(Addr, Port)
+				    end, ?SOCKET_DEFAULT_RESULT, Addrs)
+		end, ?SOCKET_DEFAULT_RESULT, outgoing_s2s_families()).
+
+open_socket2(Type, Addr, Port) ->
+    ?DEBUG("s2s_out: connecting to ~p:~p~n", [Addr, Port]),
+    Timeout = outgoing_s2s_timeout(),
+    case (catch ejabberd_socket:connect(Addr, Port,
+					[binary, {packet, 0},
+					 {active, false}, Type],
+					Timeout)) of
+	{ok, _Socket} = R -> R;
+	{error, Reason} = R ->
+	    ?DEBUG("s2s_out: connect return ~p~n", [Reason]),
+	    R;
 	{'EXIT', Reason} ->
-	    ?DEBUG("s2s_out: inet6 connect crashed ~p~n", [Reason]),
+	    ?DEBUG("s2s_out: connect crashed ~p~n", [Reason]),
 	    {error, Reason}
     end.
 
@@ -1000,6 +1008,23 @@ test_get_addr_port(Server) ->
 	      end
       end, [], lists:seq(1, 100000)).
 
+get_addrs(Host, Family) ->
+    Type = case Family of
+	       inet4 -> a;
+	       ipv4 -> a;
+	       inet6 -> aaaa;
+	       ipv6 -> aaaa
+	   end,
+    case inet_res:getbyname(Host, Type) of
+	{ok, #hostent{h_addr_list = Addrs}} ->
+	    ?DEBUG("~s of ~s resolved to: ~p~n", [Type, Host, Addrs]),
+	    Addrs;
+	{error, Reason} ->
+	    ?DEBUG("~s lookup of '~s' failed: ~p~n", [Type, Host, Reason]),
+	    []
+    end.
+
+
 outgoing_s2s_port() ->
     case ejabberd_config:get_local_option(outgoing_s2s_port) of
 	Port when is_integer(Port) ->
@@ -1008,6 +1033,36 @@ outgoing_s2s_port() ->
 	    5269
     end.
 
+outgoing_s2s_families() ->
+    case ejabberd_config:get_local_option(outgoing_s2s_options) of
+	{Families, _} when is_list(Families) ->
+	    Families;
+	undefined ->
+	    %% DISCUSSION: Why prefer IPv4 first?
+	    %%
+	    %% IPv4 connectivity will be available for everyone for
+	    %% many years to come. So, there's absolutely no benefit
+	    %% in preferring IPv6 connections which are flaky at best
+	    %% nowadays.
+	    %%
+	    %% On the other hand content providers hesitate putting up
+	    %% AAAA records for their sites due to the mentioned
+	    %% quality of current IPv6 connectivity. Making IPv6 the a
+	    %% `fallback' may avoid these problems elegantly.
+	    [ipv4, ipv6]
+    end.
+
+outgoing_s2s_timeout() ->
+    case ejabberd_config:get_local_option(outgoing_s2s_options) of
+	{_, Timeout} when is_integer(Timeout) ->
+	    Timeout;
+	{_, infinity} ->
+	    infinity;
+	undefined ->
+	    %% 10 seconds
+	    10000
+    end.
+
 %% Human readable S2S logging: Log only new outgoing connections as INFO
 %% Do not log dialback
 log_s2s_out(false, _, _) -> ok;
diff --git a/src/ejabberd_socket.erl b/src/ejabberd_socket.erl
index ba706e4e8..01e1eddcb 100644
--- a/src/ejabberd_socket.erl
+++ b/src/ejabberd_socket.erl
@@ -30,6 +30,7 @@
 %% API
 -export([start/4,
 	 connect/3,
+	 connect/4,
 	 starttls/2,
 	 starttls/3,
 	 compress/1,
@@ -94,7 +95,10 @@ start(Module, SockMod, Socket, Opts) ->
     end.
 
 connect(Addr, Port, Opts) ->
-    case gen_tcp:connect(Addr, Port, Opts) of
+    connect(Addr, Port, Opts, infinity).
+
+connect(Addr, Port, Opts, Timeout) ->
+    case gen_tcp:connect(Addr, Port, Opts, Timeout) of
 	{ok, Socket} ->
 	    Receiver = ejabberd_receiver:start(Socket, gen_tcp, none),
 	    SocketData = #socket_state{sockmod = gen_tcp,