aboutsummaryrefslogtreecommitdiff
path: root/src/stun/ejabberd_stun.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/stun/ejabberd_stun.erl')
-rw-r--r--src/stun/ejabberd_stun.erl261
1 files changed, 261 insertions, 0 deletions
diff --git a/src/stun/ejabberd_stun.erl b/src/stun/ejabberd_stun.erl
new file mode 100644
index 000000000..ca880c175
--- /dev/null
+++ b/src/stun/ejabberd_stun.erl
@@ -0,0 +1,261 @@
+%%%-------------------------------------------------------------------
+%%% File : ejabberd_stun.erl
+%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
+%%% Description : RFC5389 implementation.
+%%% Currently only Binding usage is supported.
+%%%
+%%% Created : 8 Aug 2009 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
+%%%
+%%% This program is free software; you can redistribute it and/or
+%%% modify it under the terms of the GNU General Public License as
+%%% published by the Free Software Foundation; either version 2 of the
+%%% License, or (at your option) any later version.
+%%%
+%%% This program is distributed in the hope that it will be useful,
+%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%%% General Public License for more details.
+%%%
+%%% You should have received a copy of the GNU General Public License
+%%% along with this program; if not, write to the Free Software
+%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+%%% 02111-1307 USA
+%%%
+%%%-------------------------------------------------------------------
+-module(ejabberd_stun).
+
+-behaviour(gen_fsm).
+
+%% API
+-export([start_link/2,
+ start/2,
+ socket_type/0,
+ udp_recv/5]).
+
+%% gen_fsm callbacks
+-export([init/1,
+ handle_event/3,
+ handle_sync_event/4,
+ handle_info/3,
+ terminate/3,
+ code_change/4]).
+
+%% gen_fsm states
+-export([wait_for_tls/2,
+ session_established/2]).
+
+-include("ejabberd.hrl").
+-include("stun.hrl").
+
+-define(MAX_BUF_SIZE, 64*1024). %% 64kb
+-define(TIMEOUT, 10000). %% 10 sec
+
+-record(state, {sock,
+ sock_mod = gen_tcp,
+ certfile,
+ peer,
+ tref,
+ buf = <<>>}).
+
+%%====================================================================
+%% API
+%%====================================================================
+start({gen_tcp, Sock}, Opts) ->
+ supervisor:start_child(ejabberd_stun_sup, [Sock, Opts]).
+
+start_link(Sock, Opts) ->
+ gen_fsm:start_link(?MODULE, [Sock, Opts], []).
+
+socket_type() ->
+ raw.
+
+udp_recv(Sock, Addr, Port, Data, _Opts) ->
+ case stun_codec:decode(Data) of
+ {ok, Msg, <<>>} ->
+ ?DEBUG("got:~n~s", [stun_codec:pp(Msg)]),
+ case process(Addr, Port, Msg) of
+ RespMsg when is_record(RespMsg, stun) ->
+ ?DEBUG("sent:~n~s", [stun_codec:pp(RespMsg)]),
+ Data1 = stun_codec:encode(RespMsg),
+ gen_udp:send(Sock, Addr, Port, Data1);
+ _ ->
+ ok
+ end;
+ _ ->
+ ok
+ end.
+
+%%====================================================================
+%% gen_fsm callbacks
+%%====================================================================
+init([Sock, Opts]) ->
+ case inet:peername(Sock) of
+ {ok, Addr} ->
+ inet:setopts(Sock, [{active, once}]),
+ TRef = erlang:start_timer(?TIMEOUT, self(), stop),
+ State = #state{sock = Sock, peer = Addr, tref = TRef},
+ case proplists:get_value(certfile, Opts) of
+ undefined ->
+ {ok, session_established, State};
+ CertFile ->
+ {ok, wait_for_tls, State#state{certfile = CertFile}}
+ end;
+ Err ->
+ Err
+ end.
+
+wait_for_tls(Event, State) ->
+ ?INFO_MSG("unexpected event in wait_for_tls: ~p", [Event]),
+ {next_state, wait_for_tls, State}.
+
+session_established(Msg, State) when is_record(Msg, stun) ->
+ ?DEBUG("got:~n~s", [stun_codec:pp(Msg)]),
+ {Addr, Port} = State#state.peer,
+ case process(Addr, Port, Msg) of
+ Resp when is_record(Resp, stun) ->
+ ?DEBUG("sent:~n~s", [stun_codec:pp(Resp)]),
+ Data = stun_codec:encode(Resp),
+ (State#state.sock_mod):send(State#state.sock, Data);
+ _ ->
+ ok
+ end,
+ {next_state, session_established, State};
+session_established(Event, State) ->
+ ?INFO_MSG("unexpected event in session_established: ~p", [Event]),
+ {next_state, session_established, State}.
+
+handle_event(_Event, StateName, State) ->
+ {next_state, StateName, State}.
+
+handle_sync_event(_Event, _From, StateName, State) ->
+ {reply, {error, badarg}, StateName, State}.
+
+handle_info({tcp, Sock, TLSData}, wait_for_tls, State) ->
+ Buf = <<(State#state.buf)/binary, TLSData/binary>>,
+ %% Check if the initial message is a TLS handshake
+ case Buf of
+ _ when size(Buf) < 3 ->
+ {next_state, wait_for_tls,
+ update_state(State#state{buf = Buf})};
+ <<_:16, 1, _/binary>> ->
+ TLSOpts = [{certfile, State#state.certfile}],
+ {ok, TLSSock} = tls:tcp_to_tls(Sock, TLSOpts),
+ case tls:recv_data(TLSSock, Buf) of
+ {ok, Data} ->
+ process_data(session_established,
+ State#state{sock = TLSSock,
+ buf = <<>>,
+ sock_mod = tls},
+ Data);
+ _Err ->
+ {stop, normal, State}
+ end;
+ _ ->
+ process_data(session_established, State, TLSData)
+ end;
+handle_info({tcp, _Sock, TLSData}, StateName,
+ #state{sock_mod = tls} = State) ->
+ case tls:recv_data(State#state.sock, TLSData) of
+ {ok, Data} ->
+ process_data(StateName, State, Data);
+ _Err ->
+ {stop, normal, State}
+ end;
+handle_info({tcp, _Sock, Data}, StateName, State) ->
+ process_data(StateName, State, Data);
+handle_info({tcp_closed, _Sock}, _StateName, State) ->
+ ?DEBUG("connection reset by peer", []),
+ {stop, normal, State};
+handle_info({tcp_error, _Sock, Reason}, _StateName, State) ->
+ ?DEBUG("connection error: ~p", [Reason]),
+ {stop, normal, State};
+handle_info({timeout, TRef, stop}, _StateName,
+ #state{tref = TRef} = State) ->
+ {stop, normal, State};
+handle_info(Info, StateName, State) ->
+ ?INFO_MSG("unexpected info: ~p", [Info]),
+ {next_state, StateName, State}.
+
+terminate(_Reason, _StateName, State) ->
+ catch (State#state.sock_mod):close(State#state.sock),
+ ok.
+
+code_change(_OldVsn, StateName, State, _Extra) ->
+ {ok, StateName, State}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+process(Addr, Port, #stun{class = request, unsupported = []} = Msg) ->
+ Resp = prepare_response(Msg),
+ if Msg#stun.method == ?STUN_METHOD_BINDING ->
+ case stun_codec:version(Msg) of
+ old ->
+ Resp#stun{class = response,
+ 'MAPPED-ADDRESS' = {Addr, Port}};
+ new ->
+ Resp#stun{class = response,
+ 'MAPPED-ADDRESS' = {Addr, Port},
+ 'XOR-MAPPED-ADDRESS' = {Addr, Port}}
+ end;
+ true ->
+ Resp#stun{class = error,
+ 'ERROR-CODE' = {405, <<"Method Not Allowed">>}}
+ end;
+process(_Addr, _Port, #stun{class = request} = Msg) ->
+ Resp = prepare_response(Msg),
+ Resp#stun{class = error,
+ 'UNKNOWN-ATTRIBUTES' = Msg#stun.unsupported,
+ 'ERROR-CODE' = {420, stun_codec:reason(420)}};
+process(_Addr, _Port, _Msg) ->
+ pass.
+
+prepare_response(Msg) ->
+ Version = list_to_binary("ejabberd " ++ ?VERSION),
+ #stun{method = Msg#stun.method,
+ magic = Msg#stun.magic,
+ trid = Msg#stun.trid,
+ 'SOFTWARE' = Version}.
+
+process_data(NextStateName, #state{buf = Buf} = State, Data) ->
+ NewBuf = <<Buf/binary, Data/binary>>,
+ case stun_codec:decode(NewBuf) of
+ {ok, Msg, Tail} ->
+ gen_fsm:send_event(self(), Msg),
+ process_data(NextStateName, State#state{buf = <<>>}, Tail);
+ empty ->
+ NewState = State#state{buf = <<>>},
+ {next_state, NextStateName, update_state(NewState)};
+ more when size(NewBuf) < ?MAX_BUF_SIZE ->
+ NewState = State#state{buf = NewBuf},
+ {next_state, NextStateName, update_state(NewState)};
+ _ ->
+ {stop, normal, State}
+ end.
+
+update_state(#state{sock = Sock} = State) ->
+ case State#state.sock_mod of
+ gen_tcp ->
+ inet:setopts(Sock, [{active, once}]);
+ SockMod ->
+ SockMod:setopts(Sock, [{active, once}])
+ end,
+ cancel_timer(State#state.tref),
+ TRef = erlang:start_timer(?TIMEOUT, self(), stop),
+ State#state{tref = TRef}.
+
+cancel_timer(TRef) ->
+ case erlang:cancel_timer(TRef) of
+ false ->
+ receive
+ {timeout, TRef, _} ->
+ ok
+ after 0 ->
+ ok
+ end;
+ _ ->
+ ok
+ end.