summaryrefslogtreecommitdiff
path: root/src/mod_sip_proxy.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_sip_proxy.erl')
-rw-r--r--src/mod_sip_proxy.erl152
1 files changed, 152 insertions, 0 deletions
diff --git a/src/mod_sip_proxy.erl b/src/mod_sip_proxy.erl
new file mode 100644
index 00000000..aa749ccf
--- /dev/null
+++ b/src/mod_sip_proxy.erl
@@ -0,0 +1,152 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2014, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 21 Apr 2014 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mod_sip_proxy).
+
+-define(GEN_FSM, p1_fsm).
+-behaviour(?GEN_FSM).
+
+%% API
+-export([start/2, start_link/2, route/4, route/5]).
+
+%% gen_fsm callbacks
+-export([init/1, wait_for_request/2, wait_for_response/2,
+ handle_event/3, handle_sync_event/4,
+ handle_info/3, terminate/3, code_change/4]).
+
+-include("ejabberd.hrl").
+-include("logger.hrl").
+-include("esip.hrl").
+
+-define(MAX_REDIRECTS, 5).
+
+-record(state, {host = <<"">> :: binary(),
+ opts = [] :: [{certfile, binary()}],
+ orig_trid,
+ orig_req :: #sip{},
+ client_trid}).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+start(LServer, Opts) ->
+ supervisor:start_child(mod_sip_proxy_sup, [LServer, Opts]).
+
+start_link(LServer, Opts) ->
+ ?GEN_FSM:start_link(?MODULE, [LServer, Opts], []).
+
+route(Resp, Req, _SIPSock, TrID, Pid) ->
+ ?GEN_FSM:send_event(Pid, {Resp, Req, TrID}).
+
+route(SIPMsg, _SIPSock, TrID, Pid) ->
+ ?GEN_FSM:send_event(Pid, {SIPMsg, TrID}),
+ wait.
+
+%%%===================================================================
+%%% gen_fsm callbacks
+%%%===================================================================
+init([Host, Opts]) ->
+ {ok, wait_for_request, #state{opts = Opts, host = Host}}.
+
+wait_for_request({#sip{type = request} = Req, TrID}, State) ->
+ Opts = mod_sip:add_certfile(State#state.host, State#state.opts),
+ Req1 = mod_sip:prepare_request(Req),
+ case connect(Req1, Opts) of
+ {ok, SIPSocket} ->
+ Req2 = mod_sip:add_via(SIPSocket, State#state.host, Req1),
+ case esip:request(SIPSocket, Req2, {?MODULE, route, [self()]}) of
+ {ok, ClientTrID} ->
+ {next_state, wait_for_response,
+ State#state{orig_trid = TrID,
+ orig_req = Req,
+ client_trid = ClientTrID}};
+ Err ->
+ {Status, Reason} = esip:error_status(Err),
+ esip:reply(TrID, mod_sip:make_response(
+ Req, #sip{type = response,
+ status = Status,
+ reason = Reason})),
+ {stop, normal, State}
+ end;
+ Err ->
+ {Status, Reason} = esip:error_status(Err),
+ esip:reply(TrID, mod_sip:make_response(
+ Req, #sip{type = response,
+ status = Status,
+ reason = Reason})),
+ {stop, normal, State}
+ end;
+wait_for_request(_Event, State) ->
+ {next_state, wait_for_request, State}.
+
+wait_for_response({#sip{method = <<"CANCEL">>, type = request}, _TrID}, State) ->
+ esip:cancel(State#state.client_trid),
+ {next_state, wait_for_response, State};
+wait_for_response({Resp, _TrID}, State) ->
+ case Resp of
+ {error, _} ->
+ Req = State#state.orig_req,
+ {Status, Reason} = esip:error_status(Resp),
+ case Status of
+ 408 when Req#sip.method /= <<"INVITE">> ->
+ %% Absorb useless 408. See RFC4320
+ esip:stop_transaction(State#state.orig_trid);
+ _ ->
+ ErrResp = mod_sip:make_response(
+ Req,
+ #sip{type = response,
+ status = Status,
+ reason = Reason}),
+ esip:reply(State#state.orig_trid, ErrResp)
+ end,
+ {stop, normal, State};
+ #sip{status = 100} ->
+ {next_state, wait_for_response, State};
+ #sip{status = Status} ->
+ case esip:split_hdrs('via', Resp#sip.hdrs) of
+ {[_], _} ->
+ {stop, normal, State};
+ {[_|Vias], NewHdrs} ->
+ esip:reply(State#state.orig_trid,
+ Resp#sip{hdrs = [{'via', Vias}|NewHdrs]}),
+ if Status < 200 ->
+ {next_state, wait_for_response, State};
+ true ->
+ {stop, normal, State}
+ end
+ end
+ end;
+wait_for_response(_Event, State) ->
+ {next_state, wait_for_response, State}.
+
+handle_event(_Event, StateName, State) ->
+ {next_state, StateName, State}.
+
+handle_sync_event(_Event, _From, StateName, State) ->
+ Reply = ok,
+ {reply, Reply, StateName, State}.
+
+handle_info(_Info, StateName, State) ->
+ {next_state, StateName, State}.
+
+terminate(_Reason, _StateName, _State) ->
+ ok.
+
+code_change(_OldVsn, StateName, State, _Extra) ->
+ {ok, StateName, State}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+connect(Req, Opts) ->
+ case proplists:get_value(socket, Opts) of
+ undefined ->
+ esip:connect(Req, Opts);
+ #sip_socket{} = SIPSock ->
+ {ok, SIPSock}
+ end.