]> granicus.if.org Git - ejabberd/commitdiff
TURN support (EJAB-1017)
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>
Thu, 8 May 2014 12:08:07 +0000 (16:08 +0400)
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>
Thu, 8 May 2014 12:14:21 +0000 (16:14 +0400)
doc/guide.tex
src/ejabberd_listener.erl
src/ejabberd_stun.erl [new file with mode: 0644]
src/shaper.erl

index 5f8d13de97fcc68968d4168bb7a6a8706cea428d..0919c689a9f558e9f037f764fbe8721d4c56881b 100644 (file)
@@ -398,7 +398,7 @@ Some options that you may be interested in modifying:
         Enable Stream Compression (XEP-0138) using zlib.
 
         \titem{--enable-stun}
-        Enable STUN support (see section \ref{stun}).
+        Enable STUN/TURN support (see section \ref{stun}).
 
         \titem{--enable-iconv}
         Enable iconv support. This is needed for \term{mod\_irc} (see seciont \ref{modirc}).
@@ -891,9 +891,13 @@ The available modules, their purpose and the options allowed by each one are:
     \footahref{http://tools.ietf.org/html/rfc3261}{RFC 3261}.\\
     Options: \texttt{certfile}, \texttt{tls}
   \titem{\texttt{ejabberd\_stun}}
-    Handles STUN Binding requests as defined in
-    \footahref{http://tools.ietf.org/html/rfc5389}{RFC 5389}.\\
-    Options: \texttt{certfile}
+    Handles STUN/TURN requests as defined in
+    \footahref{http://tools.ietf.org/html/rfc5389}{RFC 5389} and
+    \footahref{http://tools.ietf.org/html/rfc5766}{RFC 5766}.\\
+    Options: \texttt{certfile}, \texttt{tls}, \texttt{use\_turn}, \texttt{turn\_ip},
+    \texttt{turn\_port\_range}, \texttt{turn\_max\_allocations},
+    \texttt{turn\_max\_permissions}, \texttt{shaper}, \texttt{server\_name},
+    \texttt{auth\_realm}, \texttt{auth\_type}
   \titem{\texttt{ejabberd\_http}}
     Handles incoming HTTP connections.\\
     Options: \texttt{captcha}, \texttt{certfile}, \texttt{default\_host}, \texttt{http\_bind}, \texttt{http\_poll},
@@ -1991,22 +1995,57 @@ listen:
   ...
 \end{verbatim}
 
-\makesubsection{stun}{STUN}
+\makesubsection{stun}{STUN and TURN}
 \ind{options!stun}\ind{stun}
 
-\ejabberd{} is able to act as a stand-alone STUN server
-(\footahref{http://tools.ietf.org/html/rfc5389}{RFC 5389}). Currently only Binding usage
-is supported. In that role \ejabberd{} helps clients with ICE (\footahref{http://tools.ietf.org/html/rfc5245}{RFC 5245}) or Jingle ICE (\xepref{0176}) support to discover their external addresses and ports.
+\ejabberd{} is able to act as a stand-alone STUN/TURN server
+(\footahref{http://tools.ietf.org/html/rfc5389}{RFC 5389}/\footahref{http://tools.ietf.org/html/rfc5766}{RFC 5766}). In that role \ejabberd{} helps clients with ICE (\footahref{http://tools.ietf.org/html/rfc5245}{RFC 5245}) or Jingle ICE (\xepref{0176}) support to discover their external addresses and ports and to relay media traffic when it is impossible to establish direct
+peer-to-peer connection.
 
 You should configure \term{ejabberd\_stun} listening module as described in \ref{listened} section.
-If \option{certfile} option is defined, \ejabberd{} multiplexes TCP and
-TLS over TCP connections on the same port. Obviously, \option{certfile} option
-is defined for \term{tcp} only. Note however that TCP or TLS over TCP
-support is not required for Binding usage and is reserved for
-\footahref{http://tools.ietf.org/html/draft-ietf-behave-turn-16}{TURN}
-functionality. Feel free to configure \term{udp} transport only.
+The specific configurable options are:
+\begin{description}
+  \titem{tls: true|false}
+  If enabled, \option{certfile} option must be set, otherwise \ejabberd{}
+  will not be able to accept TLS connections. Obviously, this option
+  makes sense for \term{tcp} transport only. The default is \term{false}.
+  \titem{certfile: Path}
+  Path to the certificate file. Only makes sense when \option{tls} is set.
+  \titem{use\_turn: true|false}
+  Enables/disables TURN (media relay) functionality. The default is \term{false}.
+  \titem{turn\_ip: String}
+  The IPv4 address advertised by your TURN server. The address should not be NAT'ed
+  or firewalled. There is not default, so you should set this option explicitly.
+  Implies \term{use\_turn}.
+  \titem{turn\_min\_port: Integer}
+  Together with \option{turn\_max\_port} forms port range to allocate from.
+  The default is 49152. Implies \term{use\_turn}.
+  \titem{turn\_max\_port: Integer}
+  Together with \option{turn\_min\_port} forms port range to allocate from.
+  The default is 65535. Implies \term{use\_turn}.
+  \titem{turn\_max\_allocations: Integer|unlimited}
+  Maximum number of TURN allocations available from the particular IP address.
+  The default value is 10. Implies \term{use\_turn}.
+  \titem{turn\_max\_permissions: Integer|unlimited}
+  Maximum number of TURN permissions available from the particular IP address.
+  The default value is 10. Implies \term{use\_turn}.
+  \titem{auth\_type: user|anonymous}
+  Which authentication type to use for TURN allocation requests. When type \term{user}
+  is set, ejabberd authentication backend is used. For \term{anonymous} type
+  no authentication is performed (not recommended for public services).
+  The default is \term{user}. Implies \term{use\_turn}.
+  \titem{auth\_realm: String}
+  When \option{auth\_type} is set to \term{user} and you have several virtual
+  hosts configured you should set this option explicitly to the virtual host
+  you want to serve on this particular listening port. Implies \term{use\_turn}.
+  \titem{shaper: Atom}
+  For \term{tcp} transports defines shaper to use. The default is \term{none}.
+  \titem{server\_name: String}
+  Defines software version to return with every response. The default is the
+  STUN library version.
+\end{description}
 
-Example configuration:
+Example configuration with disabled TURN functionality (STUN only):
 \begin{verbatim}
 listen:
   ...
@@ -2024,18 +2063,41 @@ listen:
   ...
 \end{verbatim}
 
+Example configuration with TURN functionality. Note that STUN is always
+enabled if TURN is enabled. Here, only UDP section is shown:
+\begin{verbatim}
+listen:
+  ...
+  - 
+    port: 3478
+    transport: udp
+    use_turn: true
+    turn_ip: 10.20.30.1
+    module: ejabberd_stun
+  ...
+\end{verbatim}
+
 You also need to configure DNS SRV records properly so clients can easily discover a
-STUN server serving your XMPP domain. Refer to section
+STUN/TURN server serving your XMPP domain. Refer to section
 \footahref{http://tools.ietf.org/html/rfc5389\#section-9}{DNS Discovery of a Server}
-of \footahref{http://tools.ietf.org/html/rfc5389}{RFC 5389} for details.
+of \footahref{http://tools.ietf.org/html/rfc5389}{RFC 5389} and section
+\footahref{http://tools.ietf.org/html/rfc5766\#section-6}{Creating an Allocation}
+of \footahref{http://tools.ietf.org/html/rfc5766}{RFC 5766} for details.
 
-Example DNS SRV configuration:
+Example DNS SRV configuration for STUN only:
 \begin{verbatim}
 _stun._udp   IN SRV  0 0 3478 stun.example.com.
 _stun._tcp   IN SRV  0 0 3478 stun.example.com.
 _stuns._tcp  IN SRV  0 0 5349 stun.example.com.
 \end{verbatim}
 
+And you should also add these in the case if TURN is enabled:
+\begin{verbatim}
+_turn._udp   IN SRV  0 0 3478 turn.example.com.
+_turn._tcp   IN SRV  0 0 3478 turn.example.com.
+_turns._tcp  IN SRV  0 0 5349 turn.example.com.
+\end{verbatim}
+
 \makesubsection{sip}{SIP}
 \ind{options!sip}\ind{sip}
 
index 844080a0488ebcee5aa1383406ba04b210b84ea9..02a2f3fbd6b731cfdf35aea907261448e66773bc 100644 (file)
@@ -366,7 +366,6 @@ start_listener2(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()}}
-    maybe_start_stun(Module),
     maybe_start_sip(Module),
     start_module_sup(Port, Module),
     start_listener_sup(Port, Module, Opts).
@@ -482,13 +481,6 @@ is_frontend(_) -> false.
 strip_frontend({frontend, Module}) -> Module;
 strip_frontend(Module) when is_atom(Module) -> Module.
 
--spec maybe_start_stun(module()) -> any().
-
-maybe_start_stun(ejabberd_stun) ->
-    ejabberd:start_app(p1_stun);
-maybe_start_stun(_) ->
-    ok.
-
 maybe_start_sip(esip_socket) ->
     ejabberd:start_app(esip);
 maybe_start_sip(_) ->
@@ -671,12 +663,8 @@ prepare_ip(IP) when is_list(IP) ->
 prepare_ip(IP) when is_binary(IP) ->
     prepare_ip(binary_to_list(IP)).
 
-prepare_mod(ejabberd_stun) ->
-    prepare_mod(stun);
 prepare_mod(ejabberd_sip) ->
     prepare_mod(sip);
-prepare_mod(stun) ->
-    stun;
 prepare_mod(sip) ->
     esip_socket;
 prepare_mod(Mod) when is_atom(Mod) ->
diff --git a/src/ejabberd_stun.erl b/src/ejabberd_stun.erl
new file mode 100644 (file)
index 0000000..89cbebf
--- /dev/null
@@ -0,0 +1,83 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2014, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created :  8 May 2014 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(ejabberd_stun).
+
+%% API
+-export([tcp_init/2, udp_init/2, udp_recv/5, start/2, socket_type/0]).
+
+-include("ejabberd.hrl").
+-include("logger.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+tcp_init(Socket, Opts) ->
+    ejabberd:start_app(p1_stun),
+    stun:tcp_init(Socket, prepare_turn_opts(Opts)).
+
+udp_init(Socket, Opts) ->
+    ejabberd:start_app(p1_stun),
+    stun:udp_init(Socket, prepare_turn_opts(Opts)).
+
+udp_recv(Socket, Addr, Port, Packet, Opts) ->
+    stun:udp_recv(Socket, Addr, Port, Packet, Opts).
+
+start(Opaque, Opts) ->
+    stun:start(Opaque, Opts).
+
+socket_type() ->
+    raw.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+prepare_turn_opts(Opts) ->
+    UseTurn = proplists:get_bool(use_turn, Opts),
+    prepare_turn_opts(Opts, UseTurn).
+
+prepare_turn_opts(Opts, _UseTurn = false) ->
+    Opts;
+prepare_turn_opts(Opts, _UseTurn = true) ->
+    NumberOfMyHosts = length(?MYHOSTS),
+    case proplists:get_value(turn_ip, Opts) of
+       undefined ->
+           ?WARNING_MSG("option 'turn_ip' is undefined, "
+                        "more likely the TURN relay won't be working "
+                        "properly", []);
+       _ ->
+           ok
+    end,
+    AuthFun = fun ejabberd_auth:get_password_s/2,
+    Shaper = gen_mod:get_opt(shaper, Opts,
+                            fun(S) when is_atom(S) -> S end,
+                            none),
+    AuthType = gen_mod:get_opt(auth_type, Opts,
+                              fun(anonymous) -> anonymous;
+                                 (user) -> user
+                              end, user),
+    Realm = case gen_mod:get_opt(auth_realm, Opts, fun iolist_to_binary/1) of
+               undefined when AuthType == user ->
+                   if NumberOfMyHosts > 1 ->
+                           ?WARNING_MSG("you have several virtual "
+                                        "hosts configured, but option "
+                                        "'auth_realm' is undefined and "
+                                        "'auth_type' is set to 'user', "
+                                        "more likely the TURN relay won't "
+                                        "be working properly. Using ~s as "
+                                        "a fallback", [?MYNAME]);
+                      true ->
+                           ok
+                   end,
+                   [{auth_realm, ?MYNAME}];
+               _ ->
+                   []
+           end,
+    MaxRate = shaper:get_max_rate(Shaper),
+    Realm ++ [{auth_fun, AuthFun},{shaper, MaxRate} |
+             lists:keydelete(shaper, 1, Opts)].
index a31ec55605c2db218929919d191e945975801a23..4904d391e3a47ff279a12cd6af9cff638768bafb 100644 (file)
@@ -27,7 +27,7 @@
 
 -author('alexey@process-one.net').
 
--export([start/0, new/1, new1/1, update/2,
+-export([start/0, new/1, new1/1, update/2, get_max_rate/1,
          transform_options/1, load_from_config/0]).
 
 -include("ejabberd.hrl").
@@ -74,6 +74,18 @@ load_from_config() ->
             {error, Err}
     end.
 
+-spec get_max_rate(atom()) -> none | non_neg_integer().
+
+get_max_rate(none) ->
+    none;
+get_max_rate(Name) ->
+    case ets:lookup(shaper, {Name, global}) of
+       [#shaper{maxrate = R}] ->
+           R;
+       [] ->
+           none
+    end.
+
 -spec new(atom()) -> shaper().
 
 new(none) ->