From ebd760b7c9f6044976d0ba947d930d017b09f113 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Fri, 2 May 2014 15:18:57 +0400 Subject: [PATCH] Forking support --- src/mod_sip_proxy.erl | 167 ++++++++++++++++++++++++++++++------------ 1 file changed, 120 insertions(+), 47 deletions(-) diff --git a/src/mod_sip_proxy.erl b/src/mod_sip_proxy.erl index 4bcd6973e..f6e7e0f3c 100644 --- a/src/mod_sip_proxy.erl +++ b/src/mod_sip_proxy.erl @@ -23,11 +23,12 @@ -include("logger.hrl"). -include("esip.hrl"). --record(state, {host = <<"">> :: binary(), - opts = [] :: [{certfile, binary()}], +-record(state, {host = <<"">> :: binary(), + opts = [] :: [{certfile, binary()}], orig_trid, - orig_req :: #sip{}, - client_trid}). + responses = [] :: [#sip{}], + tr_ids = [] :: list(), + orig_req :: #sip{}}). %%%=================================================================== %%% API @@ -56,21 +57,34 @@ wait_for_request({#sip{type = request} = Req, TrID}, State) -> Opts = State#state.opts, Req1 = mod_sip:prepare_request(Req), case connect(Req1, Opts) of - {ok, SIPSocket} -> - Req2 = 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 -> + {ok, SIPSockets} -> + NewState = + lists:foldl( + fun(_SIPSocket, {error, _} = Err) -> + Err; + (SIPSocket, #state{tr_ids = TrIDs} = AccState) -> + Req2 = add_via(SIPSocket, State#state.host, Req1), + case esip:request(SIPSocket, Req2, + {?MODULE, route, [self()]}) of + {ok, ClientTrID} -> + NewTrIDs = [ClientTrID|TrIDs], + AccState#state{tr_ids = NewTrIDs}; + Err -> + cancel_pending_transactions(AccState), + Err + end + end, State, SIPSockets), + case NewState of + {error, _} = 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} + {stop, normal, State}; + _ -> + {next_state, wait_for_response, + NewState#state{orig_req = Req, orig_trid = TrID}} end; {error, notfound} -> esip:reply(TrID, mod_sip:make_response( @@ -90,41 +104,68 @@ 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), + cancel_pending_transactions(State), {next_state, wait_for_response, State}; -wait_for_response({Resp, _TrID}, State) -> +wait_for_response({Resp, TrID}, + #state{orig_req = #sip{method = Method} = Req} = 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}; + {error, timeout} when Method /= <<"INVITE">> -> + %% Absorb useless 408. See RFC4320 + choose_best_response(State), + esip:stop_transaction(State#state.orig_trid), + {stop, normal, State}; + {error, _} -> + {Status, Reason} = esip:error_status(Resp), + State1 = mark_transaction_as_complete(TrID, State), + SIPResp = mod_sip:make_response(Req, + #sip{type = response, + status = Status, + reason = Reason}), + State2 = collect_response(SIPResp, State1), + case State2#state.tr_ids of + [] -> + choose_best_response(State2), + {stop, normal, State2}; + _ -> + {next_state, wait_for_response, State2} + end; #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 + {[_|Vias], NewHdrs} = esip:split_hdrs('via', Resp#sip.hdrs), + NewResp = case Vias of + [] -> + Resp#sip{hdrs = NewHdrs}; + _ -> + Resp#sip{hdrs = [{'via', Vias}|NewHdrs]} + end, + if Status < 300 -> + esip:reply(State#state.orig_trid, NewResp); + true -> + ok + end, + State1 = if Status >= 200 -> + mark_transaction_as_complete(TrID, State); + true -> + State + end, + State2 = if Status >= 300 -> + collect_response(NewResp, State1); + true -> + State1 + end, + if Status >= 600 -> + cancel_pending_transactions(State2); + true -> + ok + end, + case State2#state.tr_ids of + [] -> + choose_best_response(State2), + {stop, normal, State2}; + _ -> + {next_state, wait_for_response, State2} + end end; wait_for_response(_Event, State) -> {next_state, wait_for_response, State}. @@ -155,15 +196,23 @@ connect(#sip{hdrs = Hdrs} = Req, Opts) -> LUser = jlib:nodeprep(ToURI#uri.user), LServer = jlib:nameprep(ToURI#uri.host), case mod_sip_registrar:find_sockets(LUser, LServer) of - [SIPSock|_] -> - {ok, SIPSock}; + [_|_] = SIPSocks -> + {ok, SIPSocks}; [] -> {error, notfound} end; false -> - esip:connect(Req, Opts) + case esip:connect(Req, Opts) of + {ok, SIPSock} -> + {ok, [SIPSock]}; + {error, _} = Err -> + Err + end end. +cancel_pending_transactions(State) -> + lists:foreach(fun esip:cancel/1, State#state.tr_ids). + add_certfile(LServer, Opts) -> case ejabberd_config:get_option({domain_certfile, LServer}, fun iolist_to_binary/1) of @@ -206,3 +255,27 @@ get_configured_vias(LServer) -> {Type, {Host, Port}} end, L) end, []). + +mark_transaction_as_complete(TrID, State) -> + NewTrIDs = lists:delete(TrID, State#state.tr_ids), + State#state{tr_ids = NewTrIDs}. + +collect_response(Resp, #state{responses = Resps} = State) -> + State#state{responses = [Resp|Resps]}. + +choose_best_response(#state{responses = Responses} = State) -> + SortedResponses = lists:keysort(#sip.status, Responses), + case lists:filter( + fun(#sip{status = Status}) -> + Status >= 600 + end, SortedResponses) of + [Resp|_] -> + esip:reply(State#state.orig_trid, Resp); + [] -> + case SortedResponses of + [Resp|_] -> + esip:reply(State#state.orig_trid, Resp); + [] -> + ok + end + end.