From 93944f56210a01f552c0ad3436a3ea059a9fa478 Mon Sep 17 00:00:00 2001 From: amuhar Date: Mon, 19 Jun 2017 18:39:48 +0300 Subject: [PATCH 01/25] client part without resumption --- src/mod_stream_mgmt_s2s.erl | 512 ++++++++++++++++++++++++++++++++++++ 1 file changed, 512 insertions(+) create mode 100644 src/mod_stream_mgmt_s2s.erl diff --git a/src/mod_stream_mgmt_s2s.erl b/src/mod_stream_mgmt_s2s.erl new file mode 100644 index 00000000000..81ee4c65807 --- /dev/null +++ b/src/mod_stream_mgmt_s2s.erl @@ -0,0 +1,512 @@ +-module(mod_stream_mgmt_s2s). +-behaviour(gen_mod). +-author('amuhar3@gmail.com'). +-protocol({xep, 198, '1.5.2'}). + +%% gen_mod API +-export([start/2, stop/1, reload/3, depends/2, mod_opt_type/1]). +%% hooks +-export([s2s_out_stream_init/2, s2s_out_stream_features/2, + s2s_out_packet/2, s2s_out_handle_recv/3, s2s_out_handle_send/3, + s2s_out_handle_info/2, s2s_out_closed/2, + s2s_out_terminate/2, s2s_out_established/1]). + +-include("xmpp.hrl"). +-include("logger.hrl"). +-include("p1_queue.hrl"). + +% replace ? +-define(is_sm_packet(Pkt), + is_record(Pkt, sm_enabled) or + is_record(Pkt, sm_resumed) or + is_record(Pkt, sm_a) or + is_record(Pkt, sm_r)). + +-type state() :: ejabberd_s2s_out:state(). + +%%%============================================================================= +%%% API +%%%============================================================================= +start(Host, _Opts) -> + ejabberd_hooks:add(s2s_out_init, Host, ?MODULE, s2s_out_stream_init, 50), + ejabberd_hooks:add(s2s_out_authenticated_features, + Host, ?MODULE, s2s_out_stream_features, 50), + ejabberd_hooks:add(s2s_out_packet, Host, ?MODULE, s2s_out_packet, 5), + ejabberd_hooks:add(s2s_out_handle_recv, + Host, ?MODULE, s2s_out_handle_recv, 1), + ejabberd_hooks:add(s2s_out_handle_send, + Host, ?MODULE, s2s_out_handle_send, 50), + ejabberd_hooks:add(s2s_out_handle_info, + Host, ?MODULE, s2s_out_handle_info, 50), + ejabberd_hooks:add(s2s_out_closed, + Host, ?MODULE, s2s_out_closed, 50), + ejabberd_hooks:add(s2s_out_terminate, + Host, ?MODULE, s2s_out_terminate, 50), + % delete after implementation of server part + ejabberd_hooks:add(s2s_out_established, + Host, ?MODULE, s2s_out_established, 50). + +stop(Host) -> + ejabberd_hooks:delete(s2s_out_init, Host, ?MODULE, s2s_out_stream_init, 50), + ejabberd_hooks:delete(s2s_out_authenticated_features, + Host, ?MODULE, s2s_out_stream_features, 50), + ejabberd_hooks:delete(s2s_out_packet, Host, ?MODULE, s2s_out_packet, 50), + ejabberd_hooks:delete(s2s_out_handle_recv, + Host, ?MODULE, s2s_out_handle_recv, 50), + ejabberd_hooks:delete(s2s_out_handle_send, + Host, ?MODULE, s2s_out_handle_send, 50), + ejabberd_hooks:delete(s2s_out_handle_info, + Host, ?MODULE, s2s_out_handle_info, 50), + ejabberd_hooks:delete(s2s_out_closed, + Host, ?MODULE, s2s_out_closed, 50), + ejabberd_hooks:add(s2s_out_terminate, + Host, ?MODULE, s2s_out_terminate, 50), + % delete after implementation of server part + ejabberd_hooks:delete(s2s_out_established, + Host, ?MODULE, s2s_out_established, 50). + +reload(_Host, _NewOpts, _OldOpts) -> + ?WARNING_MSG("module ~s is reloaded, but new configuration will take " + "effect for newly created s2s connections only", [?MODULE]). + +depends(_Host, _Opts) -> []. + +s2s_out_stream_init({ok, #{server_host := ServerHost} = State}, Opts) -> + case proplists:get_value(resume, Opts) of + OldState when OldState /= undefined -> + {ok, State#{mgmt_state => resume, mgmt_old_session => OldState}}; + _ -> + Resume = get_resume_timeout(ServerHost), + MaxResume = get_max_resume_timeout(ServerHost, Resume), + {ok, State#{mgmt_state => inactive, + mgmt_timeout => Resume, + mgmt_max_timeout => MaxResume, + mgmt_queue_type => get_queue_type(ServerHost), + mgmt_max_queue => get_max_ack_queue(ServerHost), + mgmt_ack_timeout => get_ack_timeout(ServerHost), + mgmt_unacked_stanzas => get_max_unacked_stanzas(ServerHost), + mgmt_stanzas_in => 0, + mgmt_stanzas_out => 0, + mgmt_stanzas_req => 0}} + end; +s2s_out_stream_init(Acc, _Opts) -> + Acc. + +s2s_out_stream_features(#{mgmt_state := MgmtState, + mgmt_timeout := Resume, + mgmt_max_timeout := MaxResume, + mgmt_old_session := OldState, + mgmt_queue_type := QueueType} = State, + #stream_features{sub_els = SubEls}) -> + case check_stream_mgmt_support(SubEls, <<>>) of + Xmlns when Xmlns == ?NS_STREAM_MGMT_2; Xmlns == ?NS_STREAM_MGMT_3 -> + case MgmtState of + inactive -> + State1 = + if Resume > 0 -> + send(State, #sm_enable{xmlns = Xmlns, + resume = true, + max = MaxResume}); + true -> + send(State, #sm_enable{xmlns = Xmlns}) + end, + State1#{mgmt_xmlns => Xmlns, + mgmt_state => wait_for_enabled, + mgmt_queue => p1_queue:new(QueueType)}; + _ -> % resume + #{mgmt_privid := Id, mgmt_stanzas_in := H} = OldState, + State1 = send(State, #sm_resume{h = H, previd = Id, xmlns = Xmlns}), + State1#{mgmt_xmlns := Xmlns, mgmt_state => pending} + end; + _ -> + State + end; +s2s_out_stream_features(State, _) -> + State. + +s2s_out_established(#{mgmt_state := resume, + mgmt_old_session := OldState} = State) -> + #{mgmt_privid := Id, mgmt_stanzas_in := H} = OldState, + State1 = send(State, #sm_resume{h = H, previd = Id, xmlns = ?NS_STREAM_MGMT_3}), + State1#{mgmt_state => pending}; +s2s_out_established(#{mgmt_timeout := Resume, + mgmt_max_timeout := MaxResume, + mgmt_queue_type := QueueType} = State) -> + Xmlns = ?NS_STREAM_MGMT_3, + State1 = + if Resume > 0 -> + send(State, #sm_enable{xmlns = Xmlns, + resume = true, + max = MaxResume}); + true -> + send(State, #sm_enable{xmlns = Xmlns}) + end, + + State1#{mgmt_state => wait_for_enabled, + mgmt_xmlns => Xmlns, + mgmt_queue => p1_queue:new(QueueType)}; +s2s_out_established(State) -> + State. + +s2s_out_packet(#{mgmt_state := MgmtState} = State, #sm_resumed{} = Pkt) -> + handle_resumed(Pkt, State); +s2s_out_packet(#{mgmt_state := MgmtState} = State, Pkt) + when ?is_sm_packet(Pkt) -> + if MgmtState == active; MgmtState == pending -> + {stop, perform_stream_mgmt(Pkt, State)}; + MgmtState == wait_for_enabled -> + {stop, negotiate_stream_mgmt(Pkt, State)}; + true -> + {stop, State} + end; +% s2s_out_packet(#{mgmt_state := wait_for_enabled, +% remote_server := RServer} = State, #sm_failed{}) -> +% ?DEBUG("Remote server ~s can't enable stream management", [RServer]), +% State#{mgmt_state => inactive}; +% s2s_out_packet(#{mgmt_state := pending, +% remote_server := RServer, mod := Mod} = State, #sm_failed{}) -> +% ?DEBUG("Remote server ~s can't resume previous session", [RServer]), +% Mod:stop(State#{mgmt_state => resume_failed}); % temp session +s2s_out_packet(State, Pkt) -> + update_num_stanzas_in(State, Pkt). + +% s2s_out_handle_recv(#{lang := Lang} = State, El, {error, Why}) -> +% Xmlns = xmpp:get_ns(El), +% ?INFO_MSG("kjuh~p", [El]), +% if Xmlns == ?NS_STREAM_MGMT_2; Xmlns == ?NS_STREAM_MGMT_3 -> +% Txt = xmpp:io_format_error(Why), +% Err = #sm_failed{reason = 'bad-request', +% text = xmpp:mk_text(Txt, Lang), +% xmlns = Xmlns}, +% send(State, Err); +% true -> +% State +% end; +s2s_out_handle_recv(State, El, Pkt) -> + State. + +s2s_out_handle_send(#{mgmt_state := MgmtState, lang := Lang} = State, Pkt, ok) + when MgmtState == active; MgmtState == wait_for_enabled -> + case Pkt of + _ when ?is_stanza(Pkt) -> + case mgmt_queue_add(State, Pkt) of + #{mgmt_max_queue := exceeded} = State1 -> + Err = xmpp:serr_policy_violation( + <<"Too many unacked stanzas">>, Lang), + send(State1, Err); + State1 when MgmtState == active -> + send_rack(State1); + State1 -> + State1 + end; + _ -> + State + end; +s2s_out_handle_send(State, _, _) -> + State. + +s2s_out_handle_info(#{mgmt_ack_timer := TRef, remote_server := RServer, + mod := Mod} = State, {timeout, TRef, ack_timeout}) -> + ?DEBUG("Timed out waiting for stream management acknowledgement of ~p", [RServer]), + State1 = Mod:close(State), + {stop, new_connection(State1)}; +s2s_out_handle_info(State, _) -> + State. + +% handle_stream_end Mod:close +s2s_out_closed(State, {stream, _}) -> + State; +s2s_out_closed(#{mgmt_state := active, mod := Mod} = State, _) -> + {stop, new_connection(State)}; +s2s_out_closed(State, _) -> + State. + +% terminate - Mod:stop +s2s_out_terminate(#{mgmt_state := resumed} = State, _Reason) -> + {stop, State}; +s2s_out_terminate(#{mgmt_state := MgmtState, + mgmt_old_session := OldState} = State, _Reason) -> + + % route_unacked_stanzas(State), + State; +s2s_out_terminate(State, _Reason) -> + State. + +%%%============================================================================= +%%% Internal functions +%%%============================================================================= + +-spec check_stream_mgmt_support(Els :: [xmlel()], + Res :: binary()) -> binary(). +check_stream_mgmt_support([El | Els], Res) -> + case El of + #xmlel{name = <<"sm">>, attrs = Attrs} -> + case fxml:get_attr(<<"xmlns">>, Attrs) of + {value, ?NS_STREAM_MGMT_3} -> + ?NS_STREAM_MGMT_3; + {value, ?NS_STREAM_MGMT_2} -> + check_stream_mgmt_support(Els, ?NS_STREAM_MGMT_2); + _ -> + check_stream_mgmt_support(Els, Res) + end; + _ -> + check_stream_mgmt_support(Els, Res) + end; +check_stream_mgmt_support([], Res) -> Res. + +% mgmt_state := active, pending and pkt is sm packet +-spec perform_stream_mgmt(xmpp_element(), state()) -> state(). +perform_stream_mgmt(Pkt, #{mgmt_xmlns := Xmlns} = State) -> + case xmpp:get_ns(Pkt) of + Xmlns -> + case Pkt of + #sm_a{} -> + handle_a(State, Pkt); + #sm_r{} -> + handle_r(State); + _ -> + send(State, #sm_failed{reason = 'bad-request', xmlns = Xmlns}) + end; + _ -> + send(State, #sm_failed{reason = 'unsupported-version', xmlns = Xmlns}) + end. + +-spec negotiate_stream_mgmt(xmpp_element(), state()) -> state(). +negotiate_stream_mgmt(Pkt, #{mgmt_xmlns := Xmlns, + mgmt_state := MgmtState} = State) -> + case Pkt of + #sm_enabled{} -> + handle_enabled(State, Pkt); + _ when is_record(Pkt, sm_r); + is_record(Pkt, sm_a) -> + Err = #sm_failed{reason = 'unexpected-request', xmlns = Xmlns}, + send(State, Err); + _ -> + Err = #sm_failed{reason = 'bad-request', xmlns = Xmlns}, + send(State, Err) + end. + +-spec handle_resumed(sm_resumed(), state()) -> state(). +handle_resumed(#sm_resumed{h = H, previd = Id}, + #{mgmt_xmlns := Xmlns, mgmt_old_session := OldState} = State) -> + + % session is resumed + + #{mgmt_stanzas_out := NumStanzasOut, mgmt_queue := Queue} = OldState, + + State1 = check_h_attribute(State#{mgmt_stanzas_out => NumStanzasOut, + mgmt_state => resumed, + mgmt_queue => Queue}, H), + + State2 = resend_unacked_stanzas(State1), + + {ok, send(State2, #sm_r{xmlns = Xmlns})}. + +-spec resend_unacked_stanzas(state()) -> state(). +resend_unacked_stanzas(#{mgmt_state := MgmtState, + mgmt_queue := Queue, + remote_server := RServer} = State) + when MgmtState == pending andalso ?qlen(Queue) > 0 -> + p1_queue:foldl( + fun({_, Time, Pkt}, AccState) -> + send(AccState, Pkt) + end, State, Queue); +resend_unacked_stanzas(State) -> + State. + +-spec handle_enabled(state(), sm_enabled()) -> state(). +handle_enabled(#{remote_server := RHost, + mgmt_timeout := DefaultTimeout, + mgmt_max_timeout := MaxTimeout, + mgmt_queue := Queue} = State, + #sm_enabled{resume = Resume, max = Max, id = Id}) -> + Timeout = if Resume == false -> + 0; + Max /= undefined -> + Max; + true -> + DefaultTimeout + end, + State1 = if Timeout > 0 -> + ?INFO_MSG("Stream management with " + "resumption enabled for ~s", [RHost]), + State#{mgmt_privid => Id}; + true -> + ?INFO_MSG("Stream management enabled for ~s", [RHost]), + State + end, + % Do we need it? + State2 = + case not p1_queue:is_empty(Queue) of + true -> + send_rack(State1); + _ -> + State1 + end, + + State2#{mgmt_state => active, mgmt_timeout => Timeout}. + +-spec handle_r(state()) -> state(). +handle_r(#{mgmt_stanzas_in := H, + mgmt_xmlns := Xmlns} = State) -> + send(State, #sm_a{h = H, xmlns = Xmlns}). + +-spec handle_a(state(), sm_a()) -> state(). +handle_a(#{mgmt_stanzas_out := NumStanzasOut, + remote_server := RServer} = State, #sm_a{h = H}) -> + check_h_attribute(State, H). + +-spec new_connection(state()) -> state(). +new_connection(#{mgmt_state := active, mod := Mod, + mgmt_timeout := 0} = State) -> + Mod:stop(State); +% we could stop this connection after resumption +new_connection(#{remote_server := RServer, server_host := Server, + mgmt_stanzas_in := H,mgmt_privid := Id} = State) -> + {ok, Pid} = ejabberd_s2s_out:start(Server, RServer, [{resume, State}]), + ejabberd_s2s_out:connect(Pid), + State; +new_connection(State) -> + State. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% todo: import from mod_stream_mgmt.erl + +-spec check_h_attribute(state(), non_neg_integer()) -> state(). +check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, + remote_server := RServer} = State, H) + when H > NumStanzasOut -> + ?DEBUG("~s acknowledged ~B stanzas," + "but only ~B were sent ", [RServer, H, NumStanzasOut]), + mgmt_queue_drop(State#{mgmt_stanzas_out => H}, NumStanzasOut); +check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, + remote_server := RServer} = State, H) -> + ?DEBUG("~s acknowledged ~B of ~B " + "stanzas", [RServer, H, NumStanzasOut]), + mgmt_queue_drop(State, H). + +-spec send(state(), xmpp_element()) -> state(). +send(#{mod := Mod} = State, Pkt) -> + Mod:send(State, Pkt). + +send_rack(#{mgmt_ack_timer := _} = State) -> + State; +send_rack(#{mgmt_xmlns := Xmlns, + mgmt_stanzas_out := NumStanzasOut, + mgmt_ack_timeout := AckTimeout} = State) -> + TRef = erlang:start_timer(AckTimeout, self(), ack_timeout), + State1 = State#{mgmt_ack_timer => TRef, mgmt_stanzas_req => NumStanzasOut}, + send(State1, #sm_r{xmlns = Xmlns}). + +-spec mgmt_queue_add(state(), xmpp_element()) -> state(). +mgmt_queue_add(#{mgmt_stanzas_out := NumStanzasOut, + mgmt_queue := Queue} = State, Pkt) -> + NewNum = case NumStanzasOut of + 4294967295 -> 0; + Num -> Num + 1 + end, + Queue1 = p1_queue:in({NewNum, p1_time_compat:timestamp(), Pkt}, Queue), + State1 = State#{mgmt_queue => Queue1, mgmt_stanzas_out => NewNum}, + check_queue_length(State1). + +-spec mgmt_queue_drop(state(), non_neg_integer()) -> state(). +mgmt_queue_drop(#{mgmt_queue := Queue} = State, NumHandled) -> + NewQueue = p1_queue:dropwhile( + fun({N, _T, _E}) -> N =< NumHandled end, Queue), + State#{mgmt_queue => NewQueue}. + +-spec check_queue_length(state()) -> state(). +check_queue_length(#{mgmt_max_queue := Limit} = State) + when Limit == infinity; Limit == exceeded -> + State; +check_queue_length(#{mgmt_queue := Queue, mgmt_max_queue := Limit} = State) -> + case p1_queue:len(Queue) > Limit of + true -> + State#{mgmt_max_queue => exceeded}; + false -> + State + end. + +-spec update_num_stanzas_in(state(), xmpp_element()) -> state(). +update_num_stanzas_in(#{mgmt_state := MgmtState, + mgmt_stanzas_in := NumStanzasIn} = State, El) + when MgmtState == active -> + NewNum = case {xmpp:is_stanza(El), NumStanzasIn} of + {true, 4294967295} -> + 0; + {true, Num} -> + Num + 1; + {false, Num} -> + Num + end, + State#{mgmt_stanzas_in => NewNum}; +update_num_stanzas_in(State, _El) -> + State. + +-spec cancel_ack_timer(state()) -> state(). +cancel_ack_timer(#{mgmt_ack_timer := TRef} = State) -> + case erlang:cancel_timer(TRef) of + false -> + receive {timeout, TRef, _} -> ok + after 0 -> ok + end; + _ -> + ok + end, + maps:remove(mgmt_ack_timer, State); +cancel_ack_timer(State) -> + State. + + +%%%============================================================================= +%%% Configuration processing +%%%============================================================================= + +get_resume_timeout(Host) -> + gen_mod:get_module_opt(Host, ?MODULE, resume_timeout, 300). + +get_max_resume_timeout(Host, ResumeTimeout) -> + case gen_mod:get_module_opt(Host, ?MODULE, max_resume_timeout) of + undefined -> ResumeTimeout; + Max when Max >= ResumeTimeout -> Max; + _ -> ResumeTimeout + end. + +get_queue_type(Host) -> + case gen_mod:get_module_opt(Host, ?MODULE, queue_type) of + undefined -> ejabberd_config:default_queue_type(Host); + Type -> Type + end. + +get_max_ack_queue(Host) -> + gen_mod:get_module_opt(Host, ?MODULE, max_ack_queue, 1000). + +get_ack_timeout(Host) -> + case gen_mod:get_module_opt(Host, ?MODULE, ack_timeout, 10) of + infinity -> infinity; + T -> timer:seconds(T) + end. + +get_max_unacked_stanzas(Host) -> + gen_mod:get_module_opt(Host, ?MODULE, max_unacked_stanzas, 0). + +mod_opt_type(max_unacked_stanzas) -> + fun(I) when is_integer(I), I >= 0 -> I end; +mod_opt_type(max_ack_queue) -> + fun(I) when is_integer(I), I > 0 -> I; + (infinity) -> infinity + end; +mod_opt_type(ack_timeout) -> + fun(I) when is_integer(I), I > 0 -> I; + (infinity) -> infinity + end; +mod_opt_type(resume_timeout) -> + fun(I) when is_integer(I), I >= 0 -> I end; +mod_opt_type(max_resume_timeout) -> + fun(I) when is_integer(I), I >= 0 -> I end; +mod_opt_type(queue_type) -> + fun(file) -> file; + (ram) -> ram + end; +mod_opt_type(_) -> [max_ack_queue, ack_timeout, resume_timeout, + max_resume_timeout, queue_type, max_unacked_stanzas ]. From 5939705ea765ab1ef4420f95c8cb96ded37d0955 Mon Sep 17 00:00:00 2001 From: amuhar Date: Mon, 19 Jun 2017 18:44:52 +0300 Subject: [PATCH 02/25] priority --- src/mod_stream_mgmt_s2s.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mod_stream_mgmt_s2s.erl b/src/mod_stream_mgmt_s2s.erl index 81ee4c65807..70deab0be65 100644 --- a/src/mod_stream_mgmt_s2s.erl +++ b/src/mod_stream_mgmt_s2s.erl @@ -33,7 +33,7 @@ start(Host, _Opts) -> Host, ?MODULE, s2s_out_stream_features, 50), ejabberd_hooks:add(s2s_out_packet, Host, ?MODULE, s2s_out_packet, 5), ejabberd_hooks:add(s2s_out_handle_recv, - Host, ?MODULE, s2s_out_handle_recv, 1), + Host, ?MODULE, s2s_out_handle_recv, 50), ejabberd_hooks:add(s2s_out_handle_send, Host, ?MODULE, s2s_out_handle_send, 50), ejabberd_hooks:add(s2s_out_handle_info, From 17cfb019da8559f87fff26c616ccc23678036964 Mon Sep 17 00:00:00 2001 From: amuhar Date: Tue, 20 Jun 2017 11:44:49 +0300 Subject: [PATCH 03/25] add handle_authenticated_features/2 function --- src/ejabberd_s2s_out.erl | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl index fea5d81625c..48dea99888b 100644 --- a/src/ejabberd_s2s_out.erl +++ b/src/ejabberd_s2s_out.erl @@ -32,7 +32,8 @@ handle_auth_success/2, handle_auth_failure/3, handle_packet/2, handle_stream_end/2, handle_stream_downgraded/2, handle_recv/3, handle_send/3, handle_cdata/2, - handle_stream_established/1, handle_timeout/1]). + handle_stream_established/1, handle_timeout/1, + handle_authenticated_features/2]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% Hooks @@ -210,6 +211,10 @@ dns_retries(#{server := LServer}) -> dns_timeout(#{server := LServer}) -> ejabberd_config:get_option({s2s_dns_timeout, LServer}, timer:seconds(10)). +handle_authenticated_features(StreamFeatures, #{server_host := ServerHost} = State) -> + ejabberd_hooks:run_fold(s2s_out_authenticated_features, + ServerHost, State, [StreamFeatures]). + handle_auth_success(Mech, #{sockmod := SockMod, socket := Socket, ip := IP, remote_server := RServer, @@ -242,8 +247,9 @@ handle_stream_end(Reason, #{server_host := ServerHost} = State) -> handle_stream_downgraded(StreamStart, #{server_host := ServerHost} = State) -> ejabberd_hooks:run_fold(s2s_out_downgraded, ServerHost, State, [StreamStart]). -handle_stream_established(State) -> - State1 = State#{on_route => send}, +handle_stream_established(#{server_host := ServerHost} = State) -> + State0 = ejabberd_hooks:run_fold(s2s_out_established, ServerHost, State, []), + State1 = State0#{on_route => send}, State2 = resend_queue(State1), set_idle_timeout(State2). @@ -306,7 +312,7 @@ handle_info({route, Pkt}, #{queue := Q, on_route := Action} = State) -> handle_info(Info, #{server_host := ServerHost} = State) -> ejabberd_hooks:run_fold(s2s_out_handle_info, ServerHost, State, [Info]). -terminate(Reason, #{server := LServer, +terminate(Reason, #{server := LServer, server_host := ServerHost, remote_server := RServer} = State) -> ejabberd_s2s:remove_connection({LServer, RServer}, self()), State1 = case Reason of @@ -314,7 +320,8 @@ terminate(Reason, #{server := LServer, _ -> State#{stop_reason => internal_failure} end, bounce_queue(State1), - bounce_message_queue(State1). + bounce_message_queue(State1), + ejabberd_hooks:run_fold(s2s_out_terminate, ServerHost, State, [Reason]). code_change(_OldVsn, State, _Extra) -> {ok, State}. From c9df931505a35dfe7f95ad572f1b8c7acd8be096 Mon Sep 17 00:00:00 2001 From: amuhar Date: Sun, 25 Jun 2017 14:54:02 +0300 Subject: [PATCH 04/25] stanza rerouting --- src/mod_stream_mgmt.erl | 3 + src/mod_stream_mgmt_s2s.erl | 294 +++++++++++++++++++----------------- 2 files changed, 157 insertions(+), 140 deletions(-) diff --git a/src/mod_stream_mgmt.erl b/src/mod_stream_mgmt.erl index 2f6b0fc716d..5c058eb3767 100644 --- a/src/mod_stream_mgmt.erl +++ b/src/mod_stream_mgmt.erl @@ -35,6 +35,9 @@ c2s_handle_recv/3]). %% adjust pending session timeout -export([get_resume_timeout/1, set_resume_timeout/2]). +%% API (used by mod_stream_mgmt_s2s) +-export ([mgmt_queue_drop/2, mgmt_queue_add/2, check_queue_length/1, + cancel_ack_timer/1, update_num_stanzas_in/2]). -include("xmpp.hrl"). -include("logger.hrl"). diff --git a/src/mod_stream_mgmt_s2s.erl b/src/mod_stream_mgmt_s2s.erl index 70deab0be65..d2c14cee399 100644 --- a/src/mod_stream_mgmt_s2s.erl +++ b/src/mod_stream_mgmt_s2s.erl @@ -59,7 +59,7 @@ stop(Host) -> Host, ?MODULE, s2s_out_handle_info, 50), ejabberd_hooks:delete(s2s_out_closed, Host, ?MODULE, s2s_out_closed, 50), - ejabberd_hooks:add(s2s_out_terminate, + ejabberd_hooks:delete(s2s_out_terminate, Host, ?MODULE, s2s_out_terminate, 50), % delete after implementation of server part ejabberd_hooks:delete(s2s_out_established, @@ -71,20 +71,24 @@ reload(_Host, _NewOpts, _OldOpts) -> depends(_Host, _Opts) -> []. -s2s_out_stream_init({ok, #{server_host := ServerHost} = State}, Opts) -> +s2s_out_stream_init({ok, #{server_host := ServerHost, mod := Mod} = State}, Opts) -> case proplists:get_value(resume, Opts) of OldState when OldState /= undefined -> - {ok, State#{mgmt_state => resume, mgmt_old_session => OldState}}; + #{mgmt_stanzas_in := H, mgmt_stanzas_out := NumStanzasOut, + mgmt_queue := Queue, mgmt_privid := Id} = OldState, + State1 = State#{mgmt_queue => Queue, + mgmt_stanzas_out => NumStanzasOut, + mgmt_stanzas_in => H, + mgmt_privid => Id}, + {ok, State1#{mgmt_state => resume, mgmt_old_session => OldState}}; _ -> - Resume = get_resume_timeout(ServerHost), - MaxResume = get_max_resume_timeout(ServerHost, Resume), {ok, State#{mgmt_state => inactive, - mgmt_timeout => Resume, - mgmt_max_timeout => MaxResume, + mgmt_timeout => get_resume_timeout(ServerHost), mgmt_queue_type => get_queue_type(ServerHost), mgmt_max_queue => get_max_ack_queue(ServerHost), mgmt_ack_timeout => get_ack_timeout(ServerHost), mgmt_unacked_stanzas => get_max_unacked_stanzas(ServerHost), + mgmt_connection_timeout => get_connection_timeout(ServerHost), mgmt_stanzas_in => 0, mgmt_stanzas_out => 0, mgmt_stanzas_req => 0}} @@ -94,11 +98,9 @@ s2s_out_stream_init(Acc, _Opts) -> s2s_out_stream_features(#{mgmt_state := MgmtState, mgmt_timeout := Resume, - mgmt_max_timeout := MaxResume, - mgmt_old_session := OldState, mgmt_queue_type := QueueType} = State, #stream_features{sub_els = SubEls}) -> - case check_stream_mgmt_support(SubEls, <<>>) of + case check_stream_mgmt_support(SubEls) of Xmlns when Xmlns == ?NS_STREAM_MGMT_2; Xmlns == ?NS_STREAM_MGMT_3 -> case MgmtState of inactive -> @@ -106,7 +108,7 @@ s2s_out_stream_features(#{mgmt_state := MgmtState, if Resume > 0 -> send(State, #sm_enable{xmlns = Xmlns, resume = true, - max = MaxResume}); + max = Resume}); true -> send(State, #sm_enable{xmlns = Xmlns}) end, @@ -114,9 +116,9 @@ s2s_out_stream_features(#{mgmt_state := MgmtState, mgmt_state => wait_for_enabled, mgmt_queue => p1_queue:new(QueueType)}; _ -> % resume - #{mgmt_privid := Id, mgmt_stanzas_in := H} = OldState, + #{mgmt_stanzas_in := H, mgmt_privid := Id} = State, State1 = send(State, #sm_resume{h = H, previd = Id, xmlns = Xmlns}), - State1#{mgmt_xmlns := Xmlns, mgmt_state => pending} + State1#{mgmt_xmlns => Xmlns, mgmt_state => pending} end; _ -> State @@ -124,20 +126,18 @@ s2s_out_stream_features(#{mgmt_state := MgmtState, s2s_out_stream_features(State, _) -> State. -s2s_out_established(#{mgmt_state := resume, - mgmt_old_session := OldState} = State) -> - #{mgmt_privid := Id, mgmt_stanzas_in := H} = OldState, +s2s_out_established(#{mgmt_state := resume} = State) -> + #{mgmt_stanzas_in := H, mgmt_privid := Id} = State, State1 = send(State, #sm_resume{h = H, previd = Id, xmlns = ?NS_STREAM_MGMT_3}), - State1#{mgmt_state => pending}; + State1#{mgmt_xmlns => ?NS_STREAM_MGMT_3, mgmt_state => pending}; s2s_out_established(#{mgmt_timeout := Resume, - mgmt_max_timeout := MaxResume, mgmt_queue_type := QueueType} = State) -> Xmlns = ?NS_STREAM_MGMT_3, State1 = if Resume > 0 -> send(State, #sm_enable{xmlns = Xmlns, resume = true, - max = MaxResume}); + max = Resume}); true -> send(State, #sm_enable{xmlns = Xmlns}) end, @@ -148,53 +148,53 @@ s2s_out_established(#{mgmt_timeout := Resume, s2s_out_established(State) -> State. -s2s_out_packet(#{mgmt_state := MgmtState} = State, #sm_resumed{} = Pkt) -> - handle_resumed(Pkt, State); +s2s_out_packet(#{mgmt_state := pending} = State, #sm_resumed{} = Pkt) -> + {stop, handle_resumed(Pkt, State)}; s2s_out_packet(#{mgmt_state := MgmtState} = State, Pkt) when ?is_sm_packet(Pkt) -> - if MgmtState == active; MgmtState == pending -> + if MgmtState == active -> % ; pending {stop, perform_stream_mgmt(Pkt, State)}; MgmtState == wait_for_enabled -> {stop, negotiate_stream_mgmt(Pkt, State)}; true -> {stop, State} end; -% s2s_out_packet(#{mgmt_state := wait_for_enabled, -% remote_server := RServer} = State, #sm_failed{}) -> -% ?DEBUG("Remote server ~s can't enable stream management", [RServer]), -% State#{mgmt_state => inactive}; -% s2s_out_packet(#{mgmt_state := pending, -% remote_server := RServer, mod := Mod} = State, #sm_failed{}) -> -% ?DEBUG("Remote server ~s can't resume previous session", [RServer]), -% Mod:stop(State#{mgmt_state => resume_failed}); % temp session s2s_out_packet(State, Pkt) -> update_num_stanzas_in(State, Pkt). -% s2s_out_handle_recv(#{lang := Lang} = State, El, {error, Why}) -> -% Xmlns = xmpp:get_ns(El), -% ?INFO_MSG("kjuh~p", [El]), -% if Xmlns == ?NS_STREAM_MGMT_2; Xmlns == ?NS_STREAM_MGMT_3 -> -% Txt = xmpp:io_format_error(Why), -% Err = #sm_failed{reason = 'bad-request', -% text = xmpp:mk_text(Txt, Lang), -% xmlns = Xmlns}, -% send(State, Err); -% true -> -% State -% end; +s2s_out_handle_recv(#{mgmt_state := wait_for_enabled, + remote_server := RServer} = State, _El, #sm_failed{}) -> + ?DEBUG("Remote server ~s can't enable stream management", [RServer]), + State#{mgmt_state => inactive}; +s2s_out_handle_recv(#{mgmt_state := pending, + remote_server := RServer, + mgmt_old_session := OldState} = State, _El, #sm_failed{}) -> + ?DEBUG("Remote server ~s can't resume previous session", [RServer]), + State; +s2s_out_handle_recv(#{lang := Lang} = State, El, {error, Why}) -> + Xmlns = xmpp:get_ns(El), + if Xmlns == ?NS_STREAM_MGMT_2; Xmlns == ?NS_STREAM_MGMT_3 -> + Txt = xmpp:io_format_error(Why), + Err = #sm_failed{reason = 'bad-request', + text = xmpp:mk_text(Txt, Lang), + xmlns = Xmlns}, + send(State, Err); + true -> + State + end; s2s_out_handle_recv(State, El, Pkt) -> State. -s2s_out_handle_send(#{mgmt_state := MgmtState, lang := Lang} = State, Pkt, ok) +s2s_out_handle_send(#{mgmt_state := MgmtState, lang := Lang} = State, Pkt, SendResult) when MgmtState == active; MgmtState == wait_for_enabled -> case Pkt of _ when ?is_stanza(Pkt) -> - case mgmt_queue_add(State, Pkt) of + case mod_stream_mgmt:mgmt_queue_add(State, Pkt) of #{mgmt_max_queue := exceeded} = State1 -> Err = xmpp:serr_policy_violation( <<"Too many unacked stanzas">>, Lang), send(State1, Err); - State1 when MgmtState == active -> + State1 when MgmtState == active, SendResult == ok -> send_rack(State1); State1 -> State1 @@ -204,31 +204,41 @@ s2s_out_handle_send(#{mgmt_state := MgmtState, lang := Lang} = State, Pkt, ok) end; s2s_out_handle_send(State, _, _) -> State. - + s2s_out_handle_info(#{mgmt_ack_timer := TRef, remote_server := RServer, mod := Mod} = State, {timeout, TRef, ack_timeout}) -> - ?DEBUG("Timed out waiting for stream management acknowledgement of ~p", [RServer]), + ?DEBUG("Timed out waiting for stream management " + "acknowledgement of ~s", [RServer]), State1 = Mod:close(State), - {stop, new_connection(State1)}; + {stop, transition_to_resume(State1)}; +s2s_out_handle_info(#{mgmt_state := resume, + remote_server := RServer, mod := Mod} = State, + {timeout, TRef, connection_timeout}) -> + ?DEBUG("Timed out waiting for connection " + "establishment for resumption ~s", [RServer]), + Mod:stop(State), + State; s2s_out_handle_info(State, _) -> State. -% handle_stream_end Mod:close -s2s_out_closed(State, {stream, _}) -> +s2s_out_closed(#{mgmt_state := resume, mod := Mod} = State, _) -> + {stop, transition_to_resume(State#{stream_state => connecting})}; +s2s_out_closed(#{mgmt_state := active} = State, _) -> State; -s2s_out_closed(#{mgmt_state := active, mod := Mod} = State, _) -> - {stop, new_connection(State)}; s2s_out_closed(State, _) -> State. -% terminate - Mod:stop -s2s_out_terminate(#{mgmt_state := resumed} = State, _Reason) -> - {stop, State}; -s2s_out_terminate(#{mgmt_state := MgmtState, - mgmt_old_session := OldState} = State, _Reason) -> +% terminate - Mod:stop +% fix: route messages if timeout = 0 or in pending state + +% s2s_out_terminate(#{mgmt_state := resumed} = State, _Reason) -> +% {stop, State}; +% s2s_out_terminate(#{mgmt_state := resume} = State, _Reason) -> +% bounce_errors(State), +% State; +% s2s_out_terminate(#{mgmt_state := active} = State, _Reason) -> +% transition_to_resume(State); - % route_unacked_stanzas(State), - State; s2s_out_terminate(State, _Reason) -> State. @@ -236,6 +246,10 @@ s2s_out_terminate(State, _Reason) -> %%% Internal functions %%%============================================================================= +-spec check_stream_mgmt_support(Els :: [xmlel()]) -> binary(). +check_stream_mgmt_support(Els) -> + check_stream_mgmt_support(Els, <<>>). + -spec check_stream_mgmt_support(Els :: [xmlel()], Res :: binary()) -> binary(). check_stream_mgmt_support([El | Els], Res) -> @@ -288,19 +302,18 @@ negotiate_stream_mgmt(Pkt, #{mgmt_xmlns := Xmlns, -spec handle_resumed(sm_resumed(), state()) -> state(). handle_resumed(#sm_resumed{h = H, previd = Id}, - #{mgmt_xmlns := Xmlns, mgmt_old_session := OldState} = State) -> - - % session is resumed + #{mgmt_xmlns := Xmlns, remote_server := RServer, + mgmt_old_session := OldState, mod := Mod} = State) -> - #{mgmt_stanzas_out := NumStanzasOut, mgmt_queue := Queue} = OldState, - - State1 = check_h_attribute(State#{mgmt_stanzas_out => NumStanzasOut, - mgmt_state => resumed, - mgmt_queue => Queue}, H), + State1 = check_h_attribute(State, H), State2 = resend_unacked_stanzas(State1), - {ok, send(State2, #sm_r{xmlns = Xmlns})}. + State3 = send(State2#{mgmt_state => resumed}, #sm_r{xmlns = Xmlns}), + + ?DEBUG("Resumed session with ~s", [RServer]), + + {ok, State3}. -spec resend_unacked_stanzas(state()) -> state(). resend_unacked_stanzas(#{mgmt_state := MgmtState, @@ -308,16 +321,16 @@ resend_unacked_stanzas(#{mgmt_state := MgmtState, remote_server := RServer} = State) when MgmtState == pending andalso ?qlen(Queue) > 0 -> p1_queue:foldl( - fun({_, Time, Pkt}, AccState) -> + fun({_, _Time, Pkt}, AccState) -> + % set is_resent = true like in mod_stream_mgmt send(AccState, Pkt) end, State, Queue); resend_unacked_stanzas(State) -> State. -spec handle_enabled(state(), sm_enabled()) -> state(). -handle_enabled(#{remote_server := RHost, +handle_enabled(#{remote_server := RServer, mgmt_timeout := DefaultTimeout, - mgmt_max_timeout := MaxTimeout, mgmt_queue := Queue} = State, #sm_enabled{resume = Resume, max = Max, id = Id}) -> Timeout = if Resume == false -> @@ -329,13 +342,13 @@ handle_enabled(#{remote_server := RHost, end, State1 = if Timeout > 0 -> ?INFO_MSG("Stream management with " - "resumption enabled for ~s", [RHost]), + "resumption enabled for ~s", [RServer]), State#{mgmt_privid => Id}; true -> - ?INFO_MSG("Stream management enabled for ~s", [RHost]), + ?INFO_MSG("Stream management enabled for ~s", [RServer]), State end, - % Do we need it? + State2 = case not p1_queue:is_empty(Queue) of true -> @@ -354,21 +367,56 @@ handle_r(#{mgmt_stanzas_in := H, -spec handle_a(state(), sm_a()) -> state(). handle_a(#{mgmt_stanzas_out := NumStanzasOut, remote_server := RServer} = State, #sm_a{h = H}) -> - check_h_attribute(State, H). - --spec new_connection(state()) -> state(). -new_connection(#{mgmt_state := active, mod := Mod, - mgmt_timeout := 0} = State) -> - Mod:stop(State); -% we could stop this connection after resumption -new_connection(#{remote_server := RServer, server_host := Server, - mgmt_stanzas_in := H,mgmt_privid := Id} = State) -> - {ok, Pid} = ejabberd_s2s_out:start(Server, RServer, [{resume, State}]), - ejabberd_s2s_out:connect(Pid), + State1 = check_h_attribute(State, H), + resend_rack(State1). + +-spec transition_to_resume(state()) -> state(). +transition_to_resume(#{mgmt_state := active, mod := Mod, + mgmt_timeout := 0} = State) -> + State; % route messages from queue +transition_to_resume(#{mgmt_state := active, mod := Mod, + remote_server := RServer,server_host := Server, + mgmt_connection_timeout := Timeout} = State) -> + State1 = mod_stream_mgmt:cancel_ack_timer(State), + {ok, Pid} = Mod:start(Server, RServer, [{resume, State1}]), + erlang:start_timer(Timeout, Pid, connection_timeout), + ?DEBUG("Try to connect to remote server ~s", [RServer]), + timer:sleep(100000), + Mod:connect(Pid), + State1; +transition_to_resume(#{mgmt_state := resume, mod := Mod, remote_server := RServer} = State) -> + Mod:connect(self()), State; -new_connection(State) -> +transition_to_resume(State) -> State. +%% fix: filter some messages + +bounce_errors(#{mgmt_state := resume, + mgmt_queue := Queue} = State) + when ?qlen(Queue) > 0 -> + p1_queue:foreach( + fun({_, _, Pkt}) -> + Error = xmpp:err_remote_server_timeout(), + ejabberd_router:route_error(Pkt, Error) + end, Queue); +bounce_errors(State) -> + ok. + +-spec route_unacked_stanzas(state()) -> state(). +route_unacked_stanzas(#{mgmt_queue := Queue, + mgmt_state := pending, + mgmt_xmlns := Xmlns} = State) + when ?qlen(Queue) > 0 -> + State1 = send(State, #sm_enable{xmlns = Xmlns}), + p1_queue:foldl( + fun({_, _, Pkt}, AccState) -> + % set is_resend = true like in mod_stream_mgmt + send(AccState, Pkt) + end, State1, Queue); +route_unacked_stanzas(_State) -> + ok. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% todo: import from mod_stream_mgmt.erl @@ -378,12 +426,12 @@ check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, when H > NumStanzasOut -> ?DEBUG("~s acknowledged ~B stanzas," "but only ~B were sent ", [RServer, H, NumStanzasOut]), - mgmt_queue_drop(State#{mgmt_stanzas_out => H}, NumStanzasOut); + mod_stream_mgmt:mgmt_queue_drop(State#{mgmt_stanzas_out => H}, NumStanzasOut); check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, remote_server := RServer} = State, H) -> ?DEBUG("~s acknowledged ~B of ~B " "stanzas", [RServer, H, NumStanzasOut]), - mgmt_queue_drop(State, H). + mod_stream_mgmt:mgmt_queue_drop(State, H). -spec send(state(), xmpp_element()) -> state(). send(#{mod := Mod} = State, Pkt) -> @@ -398,34 +446,17 @@ send_rack(#{mgmt_xmlns := Xmlns, State1 = State#{mgmt_ack_timer => TRef, mgmt_stanzas_req => NumStanzasOut}, send(State1, #sm_r{xmlns = Xmlns}). --spec mgmt_queue_add(state(), xmpp_element()) -> state(). -mgmt_queue_add(#{mgmt_stanzas_out := NumStanzasOut, - mgmt_queue := Queue} = State, Pkt) -> - NewNum = case NumStanzasOut of - 4294967295 -> 0; - Num -> Num + 1 - end, - Queue1 = p1_queue:in({NewNum, p1_time_compat:timestamp(), Pkt}, Queue), - State1 = State#{mgmt_queue => Queue1, mgmt_stanzas_out => NewNum}, - check_queue_length(State1). - --spec mgmt_queue_drop(state(), non_neg_integer()) -> state(). -mgmt_queue_drop(#{mgmt_queue := Queue} = State, NumHandled) -> - NewQueue = p1_queue:dropwhile( - fun({N, _T, _E}) -> N =< NumHandled end, Queue), - State#{mgmt_queue => NewQueue}. - --spec check_queue_length(state()) -> state(). -check_queue_length(#{mgmt_max_queue := Limit} = State) - when Limit == infinity; Limit == exceeded -> - State; -check_queue_length(#{mgmt_queue := Queue, mgmt_max_queue := Limit} = State) -> - case p1_queue:len(Queue) > Limit of - true -> - State#{mgmt_max_queue => exceeded}; - false -> - State - end. +resend_rack(#{mgmt_ack_timer := _, + mgmt_queue := Queue, + mgmt_stanzas_out := NumStanzasOut, + mgmt_stanzas_req := NumStanzasReq} = State) -> + State1 = State, % mod_stream_mgmt:cancel_ack_timer(State), + case NumStanzasReq < NumStanzasOut andalso not p1_queue:is_empty(Queue) of + true -> send_rack(State1); + false -> State1 + end; +resend_rack(State) -> + State. -spec update_num_stanzas_in(state(), xmpp_element()) -> state(). update_num_stanzas_in(#{mgmt_state := MgmtState, @@ -443,21 +474,6 @@ update_num_stanzas_in(#{mgmt_state := MgmtState, update_num_stanzas_in(State, _El) -> State. --spec cancel_ack_timer(state()) -> state(). -cancel_ack_timer(#{mgmt_ack_timer := TRef} = State) -> - case erlang:cancel_timer(TRef) of - false -> - receive {timeout, TRef, _} -> ok - after 0 -> ok - end; - _ -> - ok - end, - maps:remove(mgmt_ack_timer, State); -cancel_ack_timer(State) -> - State. - - %%%============================================================================= %%% Configuration processing %%%============================================================================= @@ -465,13 +481,6 @@ cancel_ack_timer(State) -> get_resume_timeout(Host) -> gen_mod:get_module_opt(Host, ?MODULE, resume_timeout, 300). -get_max_resume_timeout(Host, ResumeTimeout) -> - case gen_mod:get_module_opt(Host, ?MODULE, max_resume_timeout) of - undefined -> ResumeTimeout; - Max when Max >= ResumeTimeout -> Max; - _ -> ResumeTimeout - end. - get_queue_type(Host) -> case gen_mod:get_module_opt(Host, ?MODULE, queue_type) of undefined -> ejabberd_config:default_queue_type(Host); @@ -482,7 +491,7 @@ get_max_ack_queue(Host) -> gen_mod:get_module_opt(Host, ?MODULE, max_ack_queue, 1000). get_ack_timeout(Host) -> - case gen_mod:get_module_opt(Host, ?MODULE, ack_timeout, 10) of + case gen_mod:get_module_opt(Host, ?MODULE, ack_timeout, 10) of % change default infinity -> infinity; T -> timer:seconds(T) end. @@ -490,6 +499,11 @@ get_ack_timeout(Host) -> get_max_unacked_stanzas(Host) -> gen_mod:get_module_opt(Host, ?MODULE, max_unacked_stanzas, 0). +get_connection_timeout(Host) -> + gen_mod:get_module_opt(Host, ?MODULE, connection_timeout, 60000). + +mod_opt_type(connection_timeout) -> + fun(I) when is_integer(I), I >= 0 -> I end; mod_opt_type(max_unacked_stanzas) -> fun(I) when is_integer(I), I >= 0 -> I end; mod_opt_type(max_ack_queue) -> @@ -509,4 +523,4 @@ mod_opt_type(queue_type) -> (ram) -> ram end; mod_opt_type(_) -> [max_ack_queue, ack_timeout, resume_timeout, - max_resume_timeout, queue_type, max_unacked_stanzas ]. + queue_type, max_unacked_stanzas, connection_timeout]. From c0ae0aea229b6f7738d420dfe828b9f3511aa759 Mon Sep 17 00:00:00 2001 From: amuhar Date: Mon, 26 Jun 2017 02:24:50 +0300 Subject: [PATCH 05/25] new connection to remote server --- src/mod_stream_mgmt_s2s.erl | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/mod_stream_mgmt_s2s.erl b/src/mod_stream_mgmt_s2s.erl index d2c14cee399..7fabc6121df 100644 --- a/src/mod_stream_mgmt_s2s.erl +++ b/src/mod_stream_mgmt_s2s.erl @@ -72,6 +72,7 @@ reload(_Host, _NewOpts, _OldOpts) -> depends(_Host, _Opts) -> []. s2s_out_stream_init({ok, #{server_host := ServerHost, mod := Mod} = State}, Opts) -> + ?INFO_MSG("init pid ~p", [self()]), case proplists:get_value(resume, Opts) of OldState when OldState /= undefined -> #{mgmt_stanzas_in := H, mgmt_stanzas_out := NumStanzasOut, @@ -127,6 +128,7 @@ s2s_out_stream_features(State, _) -> State. s2s_out_established(#{mgmt_state := resume} = State) -> + % register connection ? #{mgmt_stanzas_in := H, mgmt_privid := Id} = State, State1 = send(State, #sm_resume{h = H, previd = Id, xmlns = ?NS_STREAM_MGMT_3}), State1#{mgmt_xmlns => ?NS_STREAM_MGMT_3, mgmt_state => pending}; @@ -209,22 +211,23 @@ s2s_out_handle_info(#{mgmt_ack_timer := TRef, remote_server := RServer, mod := Mod} = State, {timeout, TRef, ack_timeout}) -> ?DEBUG("Timed out waiting for stream management " "acknowledgement of ~s", [RServer]), - State1 = Mod:close(State), - {stop, transition_to_resume(State1)}; + Mod:stop(State); + % State1 = Mod:close(State), + % {stop, transition_to_resume(State1)}; s2s_out_handle_info(#{mgmt_state := resume, remote_server := RServer, mod := Mod} = State, {timeout, TRef, connection_timeout}) -> ?DEBUG("Timed out waiting for connection " - "establishment for resumption ~s", [RServer]), - Mod:stop(State), + "establishment with ~s for resumption", [RServer]), + % Mod:stop(State), State; s2s_out_handle_info(State, _) -> State. s2s_out_closed(#{mgmt_state := resume, mod := Mod} = State, _) -> {stop, transition_to_resume(State#{stream_state => connecting})}; -s2s_out_closed(#{mgmt_state := active} = State, _) -> - State; +% s2s_out_closed(#{mgmt_state := active} = State, _) -> +% State; s2s_out_closed(State, _) -> State. @@ -234,7 +237,7 @@ s2s_out_closed(State, _) -> % s2s_out_terminate(#{mgmt_state := resumed} = State, _Reason) -> % {stop, State}; % s2s_out_terminate(#{mgmt_state := resume} = State, _Reason) -> -% bounce_errors(State), +% % bounce_errors(State), % State; % s2s_out_terminate(#{mgmt_state := active} = State, _Reason) -> % transition_to_resume(State); @@ -378,13 +381,18 @@ transition_to_resume(#{mgmt_state := active, mod := Mod, remote_server := RServer,server_host := Server, mgmt_connection_timeout := Timeout} = State) -> State1 = mod_stream_mgmt:cancel_ack_timer(State), - {ok, Pid} = Mod:start(Server, RServer, [{resume, State1}]), - erlang:start_timer(Timeout, Pid, connection_timeout), ?DEBUG("Try to connect to remote server ~s", [RServer]), - timer:sleep(100000), - Mod:connect(Pid), + {ok, Pid} = + ejabberd_s2s:start_connection(jid:make(Server), + jid:make(RServer), + [{resume, State1}]), + + erlang:start_timer(Timeout, Pid, connection_timeout), + State1; -transition_to_resume(#{mgmt_state := resume, mod := Mod, remote_server := RServer} = State) -> +transition_to_resume(#{mgmt_state := resume, + mod := Mod, + remote_server := RServer} = State) -> Mod:connect(self()), State; transition_to_resume(State) -> @@ -500,7 +508,7 @@ get_max_unacked_stanzas(Host) -> gen_mod:get_module_opt(Host, ?MODULE, max_unacked_stanzas, 0). get_connection_timeout(Host) -> - gen_mod:get_module_opt(Host, ?MODULE, connection_timeout, 60000). + gen_mod:get_module_opt(Host, ?MODULE, connection_timeout, 120000). mod_opt_type(connection_timeout) -> fun(I) when is_integer(I), I >= 0 -> I end; From d488c2b32e10d62d87895a048927b3824a1f9f22 Mon Sep 17 00:00:00 2001 From: amuhar Date: Mon, 26 Jun 2017 02:36:08 +0300 Subject: [PATCH 06/25] new connection to remote server --- src/mod_stream_mgmt_s2s.erl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mod_stream_mgmt_s2s.erl b/src/mod_stream_mgmt_s2s.erl index 7fabc6121df..d493beff262 100644 --- a/src/mod_stream_mgmt_s2s.erl +++ b/src/mod_stream_mgmt_s2s.erl @@ -72,7 +72,6 @@ reload(_Host, _NewOpts, _OldOpts) -> depends(_Host, _Opts) -> []. s2s_out_stream_init({ok, #{server_host := ServerHost, mod := Mod} = State}, Opts) -> - ?INFO_MSG("init pid ~p", [self()]), case proplists:get_value(resume, Opts) of OldState when OldState /= undefined -> #{mgmt_stanzas_in := H, mgmt_stanzas_out := NumStanzasOut, From a18d58513d766a206947920771694fbc6de49a52 Mon Sep 17 00:00:00 2001 From: amuhar Date: Wed, 28 Jun 2017 02:57:53 +0300 Subject: [PATCH 07/25] stream management enabled for client and server --- src/mod_stream_mgmt_s2s.erl | 712 ++++++++++++++++++++++-------------- 1 file changed, 432 insertions(+), 280 deletions(-) diff --git a/src/mod_stream_mgmt_s2s.erl b/src/mod_stream_mgmt_s2s.erl index d493beff262..f673eab7d64 100644 --- a/src/mod_stream_mgmt_s2s.erl +++ b/src/mod_stream_mgmt_s2s.erl @@ -5,11 +5,16 @@ %% gen_mod API -export([start/2, stop/1, reload/3, depends/2, mod_opt_type/1]). -%% hooks +%% client part hooks -export([s2s_out_stream_init/2, s2s_out_stream_features/2, - s2s_out_packet/2, s2s_out_handle_recv/3, s2s_out_handle_send/3, - s2s_out_handle_info/2, s2s_out_closed/2, - s2s_out_terminate/2, s2s_out_established/1]). + s2s_out_packet/2, s2s_out_established/1]). + % s2s_out_packet/2, s2s_out_handle_recv/3, s2s_out_handle_send/3, + %s2s_out_handle_info/2, s2s_out_closed/2, + % s2s_out_terminate/2]). % , s2s_out_established/1]). +%% server part hooks +-export([s2s_in_post_auth_features/2, s2s_in_init/2, + s2s_in_authenticated_packet/2]). + -include("xmpp.hrl"). -include("logger.hrl"). @@ -17,12 +22,14 @@ % replace ? -define(is_sm_packet(Pkt), + is_record(Pkt, sm_enable) or is_record(Pkt, sm_enabled) or + is_record(Pkt, sm_resume) or is_record(Pkt, sm_resumed) or is_record(Pkt, sm_a) or is_record(Pkt, sm_r)). --type state() :: ejabberd_s2s_out:state(). +-type state() :: ejabberd_s2s_out:state(). % | ejabberd_s2s_in:state(). %%%============================================================================= %%% API @@ -32,38 +39,50 @@ start(Host, _Opts) -> ejabberd_hooks:add(s2s_out_authenticated_features, Host, ?MODULE, s2s_out_stream_features, 50), ejabberd_hooks:add(s2s_out_packet, Host, ?MODULE, s2s_out_packet, 5), - ejabberd_hooks:add(s2s_out_handle_recv, - Host, ?MODULE, s2s_out_handle_recv, 50), - ejabberd_hooks:add(s2s_out_handle_send, - Host, ?MODULE, s2s_out_handle_send, 50), - ejabberd_hooks:add(s2s_out_handle_info, - Host, ?MODULE, s2s_out_handle_info, 50), - ejabberd_hooks:add(s2s_out_closed, - Host, ?MODULE, s2s_out_closed, 50), - ejabberd_hooks:add(s2s_out_terminate, - Host, ?MODULE, s2s_out_terminate, 50), + % ejabberd_hooks:add(s2s_out_handle_recv, + % Host, ?MODULE, s2s_out_handle_recv, 50), + % ejabberd_hooks:add(s2s_out_handle_send, + % Host, ?MODULE, s2s_out_handle_send, 50), + % ejabberd_hooks:add(s2s_out_handle_info, + % Host, ?MODULE, s2s_out_handle_info, 50), + % ejabberd_hooks:add(s2s_out_closed, + % Host, ?MODULE, s2s_out_closed, 50), + % ejabberd_hooks:add(s2s_out_terminate, + % Host, ?MODULE, s2s_out_terminate, 50), % delete after implementation of server part ejabberd_hooks:add(s2s_out_established, - Host, ?MODULE, s2s_out_established, 50). + Host, ?MODULE, s2s_out_established, 50), + %% server part + ejabberd_hooks:add(s2s_in_post_auth_features, + Host, ?MODULE, s2s_in_post_auth_features, 50), + ejabberd_hooks:add(s2s_in_init, ?MODULE, s2s_in_init, 50), + ejabberd_hooks:add(s2s_in_authenticated_packet, + Host, ?MODULE, s2s_in_authenticated_packet, 50). stop(Host) -> ejabberd_hooks:delete(s2s_out_init, Host, ?MODULE, s2s_out_stream_init, 50), ejabberd_hooks:delete(s2s_out_authenticated_features, Host, ?MODULE, s2s_out_stream_features, 50), ejabberd_hooks:delete(s2s_out_packet, Host, ?MODULE, s2s_out_packet, 50), - ejabberd_hooks:delete(s2s_out_handle_recv, - Host, ?MODULE, s2s_out_handle_recv, 50), - ejabberd_hooks:delete(s2s_out_handle_send, - Host, ?MODULE, s2s_out_handle_send, 50), - ejabberd_hooks:delete(s2s_out_handle_info, - Host, ?MODULE, s2s_out_handle_info, 50), - ejabberd_hooks:delete(s2s_out_closed, - Host, ?MODULE, s2s_out_closed, 50), - ejabberd_hooks:delete(s2s_out_terminate, - Host, ?MODULE, s2s_out_terminate, 50), + % ejabberd_hooks:delete(s2s_out_handle_recv, + % Host, ?MODULE, s2s_out_handle_recv, 50), + % ejabberd_hooks:delete(s2s_out_handle_send, + % Host, ?MODULE, s2s_out_handle_send, 50), + % ejabberd_hooks:delete(s2s_out_handle_info, + % Host, ?MODULE, s2s_out_handle_info, 50), + % ejabberd_hooks:delete(s2s_out_closed, + % Host, ?MODULE, s2s_out_closed, 50), + % ejabberd_hooks:delete(s2s_out_terminate, + % Host, ?MODULE, s2s_out_terminate, 50), % delete after implementation of server part ejabberd_hooks:delete(s2s_out_established, - Host, ?MODULE, s2s_out_established, 50). + Host, ?MODULE, s2s_out_established, 50), + %% server part + ejabberd_hooks:delete(s2s_in_post_auth_features, + Host, ?MODULE, s2s_in_post_auth_features, 50), + % ejabberd_hooks:delete(s2s_in_init, ?MODULE, s2s_in_init), + ejabberd_hooks:delete(s2s_in_authenticated_packet, + Host, ?MODULE, s2s_in_authenticated_packet, 50). reload(_Host, _NewOpts, _OldOpts) -> ?WARNING_MSG("module ~s is reloaded, but new configuration will take " @@ -71,17 +90,18 @@ reload(_Host, _NewOpts, _OldOpts) -> depends(_Host, _Opts) -> []. +%% client part s2s_out_stream_init({ok, #{server_host := ServerHost, mod := Mod} = State}, Opts) -> - case proplists:get_value(resume, Opts) of - OldState when OldState /= undefined -> - #{mgmt_stanzas_in := H, mgmt_stanzas_out := NumStanzasOut, - mgmt_queue := Queue, mgmt_privid := Id} = OldState, - State1 = State#{mgmt_queue => Queue, - mgmt_stanzas_out => NumStanzasOut, - mgmt_stanzas_in => H, - mgmt_privid => Id}, - {ok, State1#{mgmt_state => resume, mgmt_old_session => OldState}}; - _ -> + % case proplists:get_value(resume, Opts) of + % OldState when OldState /= undefined -> + % #{mgmt_stanzas_in := H, mgmt_stanzas_out := NumStanzasOut, + % mgmt_queue := Queue, mgmt_privid := Id} = OldState, + % State1 = State#{mgmt_queue => Queue, + % mgmt_stanzas_out => NumStanzasOut, + % mgmt_stanzas_in => H, + % mgmt_privid => Id}, + % {ok, State1#{mgmt_state => resume, mgmt_old_session => OldState}}; + % _ -> {ok, State#{mgmt_state => inactive, mgmt_timeout => get_resume_timeout(ServerHost), mgmt_queue_type => get_queue_type(ServerHost), @@ -91,8 +111,8 @@ s2s_out_stream_init({ok, #{server_host := ServerHost, mod := Mod} = State}, Opts mgmt_connection_timeout => get_connection_timeout(ServerHost), mgmt_stanzas_in => 0, mgmt_stanzas_out => 0, - mgmt_stanzas_req => 0}} - end; + mgmt_stanzas_req => 0}}; + % end; s2s_out_stream_init(Acc, _Opts) -> Acc. @@ -102,8 +122,8 @@ s2s_out_stream_features(#{mgmt_state := MgmtState, #stream_features{sub_els = SubEls}) -> case check_stream_mgmt_support(SubEls) of Xmlns when Xmlns == ?NS_STREAM_MGMT_2; Xmlns == ?NS_STREAM_MGMT_3 -> - case MgmtState of - inactive -> + % case MgmtState of + % inactive -> State1 = if Resume > 0 -> send(State, #sm_enable{xmlns = Xmlns, @@ -115,22 +135,21 @@ s2s_out_stream_features(#{mgmt_state := MgmtState, State1#{mgmt_xmlns => Xmlns, mgmt_state => wait_for_enabled, mgmt_queue => p1_queue:new(QueueType)}; - _ -> % resume - #{mgmt_stanzas_in := H, mgmt_privid := Id} = State, - State1 = send(State, #sm_resume{h = H, previd = Id, xmlns = Xmlns}), - State1#{mgmt_xmlns => Xmlns, mgmt_state => pending} - end; + % _ -> % resume + % #{mgmt_stanzas_in := H, mgmt_privid := Id} = State, + % State1 = send(State, #sm_resume{h = H, previd = Id, xmlns = Xmlns}), + % State1#{mgmt_xmlns => Xmlns, mgmt_state => pending} + % end; _ -> State end; s2s_out_stream_features(State, _) -> State. -s2s_out_established(#{mgmt_state := resume} = State) -> - % register connection ? - #{mgmt_stanzas_in := H, mgmt_privid := Id} = State, - State1 = send(State, #sm_resume{h = H, previd = Id, xmlns = ?NS_STREAM_MGMT_3}), - State1#{mgmt_xmlns => ?NS_STREAM_MGMT_3, mgmt_state => pending}; +% s2s_out_established(#{mgmt_state := resume} = State) -> +% #{mgmt_stanzas_in := H, mgmt_privid := Id} = State, +% State1 = send(State, #sm_resume{h = H, previd = Id, xmlns = ?NS_STREAM_MGMT_3}), +% State1#{mgmt_xmlns => ?NS_STREAM_MGMT_3, mgmt_state => pending}; s2s_out_established(#{mgmt_timeout := Resume, mgmt_queue_type := QueueType} = State) -> Xmlns = ?NS_STREAM_MGMT_3, @@ -149,100 +168,145 @@ s2s_out_established(#{mgmt_timeout := Resume, s2s_out_established(State) -> State. -s2s_out_packet(#{mgmt_state := pending} = State, #sm_resumed{} = Pkt) -> - {stop, handle_resumed(Pkt, State)}; -s2s_out_packet(#{mgmt_state := MgmtState} = State, Pkt) +s2s_out_packet(#{mgmt_state := MgmtState} = State, Pkt) when ?is_sm_packet(Pkt) -> - if MgmtState == active -> % ; pending - {stop, perform_stream_mgmt(Pkt, State)}; - MgmtState == wait_for_enabled -> + if MgmtState == wait_for_enabled -> {stop, negotiate_stream_mgmt(Pkt, State)}; true -> {stop, State} end; s2s_out_packet(State, Pkt) -> - update_num_stanzas_in(State, Pkt). + State. -s2s_out_handle_recv(#{mgmt_state := wait_for_enabled, - remote_server := RServer} = State, _El, #sm_failed{}) -> - ?DEBUG("Remote server ~s can't enable stream management", [RServer]), - State#{mgmt_state => inactive}; -s2s_out_handle_recv(#{mgmt_state := pending, - remote_server := RServer, - mgmt_old_session := OldState} = State, _El, #sm_failed{}) -> - ?DEBUG("Remote server ~s can't resume previous session", [RServer]), - State; -s2s_out_handle_recv(#{lang := Lang} = State, El, {error, Why}) -> - Xmlns = xmpp:get_ns(El), - if Xmlns == ?NS_STREAM_MGMT_2; Xmlns == ?NS_STREAM_MGMT_3 -> - Txt = xmpp:io_format_error(Why), - Err = #sm_failed{reason = 'bad-request', - text = xmpp:mk_text(Txt, Lang), - xmlns = Xmlns}, - send(State, Err); +%% client part + +%% server part + +s2s_in_post_auth_features(Acc, _Host) -> + [#feature_sm{xmlns = ?NS_STREAM_MGMT_2}, + #feature_sm{xmlns = ?NS_STREAM_MGMT_3}| Acc]. + +s2s_in_init({ok, #{server_host := Host} = State}, _Opts) -> + Timeout = get_resume_timeout(Host), + MaxTimeout = get_max_resume_timeout(Host, Timeout), + {ok, State#{mgmt_state => inactive, + mgmt_timeout => Timeout, + mgmt_max_timeout => MaxTimeout, + mgmt_queue_type => get_queue_type(Host), + mgmt_stanzas_in => 0}}; +s2s_in_init(State, _Opts) -> + State. + +s2s_in_authenticated_packet(#{mgmt_state := MgmtState} = State, Pkt) + when ?is_sm_packet(Pkt) -> + if MgmtState == inactive -> + {stop, server_negotiate_stream_mgmt(Pkt, State)}; true -> - State + {stop, State} end; -s2s_out_handle_recv(State, El, Pkt) -> - State. +s2s_in_authenticated_packet(State, Pkt) -> + update_num_stanzas_in(State, Pkt). + + + +%% server part + +% s2s_out_packet(#{mgmt_state := pending} = State, #sm_resumed{} = Pkt) -> +% {stop, handle_resumed(Pkt, State)}; +% s2s_out_packet(#{mgmt_state := MgmtState} = State, Pkt) +% when ?is_sm_packet(Pkt) -> +% if MgmtState == active; MgmtState == pending -> +% {stop, perform_stream_mgmt(Pkt, State)}; +% MgmtState == wait_for_enabled -> +% {stop, negotiate_stream_mgmt(Pkt, State)}; +% true -> +% {stop, State} +% end; +% s2s_out_packet(State, Pkt) -> +% update_num_stanzas_in(State, Pkt). + +% s2s_out_handle_recv(#{mgmt_state := wait_for_enabled, +% remote_server := RServer} = State, _El, #sm_failed{}) -> +% ?DEBUG("Remote server ~s can't enable stream management", [RServer]), +% State#{mgmt_state => inactive}; +% s2s_out_handle_recv(#{mgmt_state := pending, +% remote_server := RServer, +% mod := Mod} = State, _El, #sm_failed{}) -> +% ?DEBUG("Remote server ~s can't resume previous session", [RServer]), +% Mod:stop(State); +% s2s_out_handle_recv(#{lang := Lang} = State, El, {error, Why}) -> +% Xmlns = xmpp:get_ns(El), +% if Xmlns == ?NS_STREAM_MGMT_2; Xmlns == ?NS_STREAM_MGMT_3 -> +% Txt = xmpp:io_format_error(Why), +% Err = #sm_failed{reason = 'bad-request', +% text = xmpp:mk_text(Txt, Lang), +% xmlns = Xmlns}, +% send(State, Err); +% true -> +% State +% end; +% s2s_out_handle_recv(State, El, Pkt) -> +% State. -s2s_out_handle_send(#{mgmt_state := MgmtState, lang := Lang} = State, Pkt, SendResult) - when MgmtState == active; MgmtState == wait_for_enabled -> - case Pkt of - _ when ?is_stanza(Pkt) -> - case mod_stream_mgmt:mgmt_queue_add(State, Pkt) of - #{mgmt_max_queue := exceeded} = State1 -> - Err = xmpp:serr_policy_violation( - <<"Too many unacked stanzas">>, Lang), - send(State1, Err); - State1 when MgmtState == active, SendResult == ok -> - send_rack(State1); - State1 -> - State1 - end; - _ -> - State - end; -s2s_out_handle_send(State, _, _) -> - State. +% s2s_out_handle_send(#{mgmt_state := MgmtState, lang := Lang} = State, Pkt, SendResult) +% when MgmtState == active; MgmtState == pending; MgmtState == wait_for_enabled -> +% case Pkt of +% _ when ?is_stanza(Pkt) -> +% Meta = xmpp:get_meta(Pkt), +% case maps:get(mgmt_is_resent, Meta, false) of +% false -> +% case mod_stream_mgmt:mgmt_queue_add(State, Pkt) of +% #{mgmt_max_queue := exceeded} = State1 -> +% Err = xmpp:serr_policy_violation( +% <<"Too many unacked stanzas">>, Lang), +% send(State1, Err); +% State1 when MgmtState /= wait_for_enabled, SendResult == ok -> +% send_rack(State1); +% State1 -> +% State1 +% end; +% true -> +% State +% end; +% _ -> +% State +% end; +% s2s_out_handle_send(State, _, _) -> +% State. -s2s_out_handle_info(#{mgmt_ack_timer := TRef, remote_server := RServer, - mod := Mod} = State, {timeout, TRef, ack_timeout}) -> - ?DEBUG("Timed out waiting for stream management " - "acknowledgement of ~s", [RServer]), - Mod:stop(State); - % State1 = Mod:close(State), - % {stop, transition_to_resume(State1)}; -s2s_out_handle_info(#{mgmt_state := resume, - remote_server := RServer, mod := Mod} = State, - {timeout, TRef, connection_timeout}) -> - ?DEBUG("Timed out waiting for connection " - "establishment with ~s for resumption", [RServer]), - % Mod:stop(State), - State; -s2s_out_handle_info(State, _) -> - State. - -s2s_out_closed(#{mgmt_state := resume, mod := Mod} = State, _) -> - {stop, transition_to_resume(State#{stream_state => connecting})}; -% s2s_out_closed(#{mgmt_state := active} = State, _) -> +% s2s_out_handle_info(#{mgmt_ack_timer := TRef, remote_server := RServer, +% mod := Mod} = State, {timeout, TRef, ack_timeout}) -> +% ?DEBUG("Timed out waiting for stream management " +% "acknowledgement of ~s", [RServer]), +% Mod:stop(State); +% s2s_out_handle_info(#{mgmt_state := resume, +% remote_server := RServer, mod := Mod} = State, +% {timeout, TRef, connection_timeout}) -> +% ?DEBUG("Timed out waiting for connection " +% "establishment with ~s for resumption", [RServer]), +% Mod:stop(State), % State; -s2s_out_closed(State, _) -> - State. +% s2s_out_handle_info(State, _) -> +% State. -% terminate - Mod:stop -% fix: route messages if timeout = 0 or in pending state +% s2s_out_closed(#{mgmt_state := resume, mod := Mod} = State, _) -> +% {stop, transition_to_resume(State#{stream_state => connecting})}; +% s2s_out_closed(State, _) -> +% State. + +% % terminate - Mod:stop -% s2s_out_terminate(#{mgmt_state := resumed} = State, _Reason) -> -% {stop, State}; -% s2s_out_terminate(#{mgmt_state := resume} = State, _Reason) -> -% % bounce_errors(State), -% State; % s2s_out_terminate(#{mgmt_state := active} = State, _Reason) -> % transition_to_resume(State); +% s2s_out_terminate(#{mgmt_state := resume} = State, _Reason) -> +% bounce_errors(State), +% State; +% % в данном состоянии что делать? +% s2s_out_terminate(#{mgmt_state := pending} = State, _Reason) -> +% State; +% s2s_out_terminate(State, _Reason) -> +% State. -s2s_out_terminate(State, _Reason) -> - State. %%%============================================================================= %%% Internal functions @@ -270,31 +334,59 @@ check_stream_mgmt_support([El | Els], Res) -> end; check_stream_mgmt_support([], Res) -> Res. -% mgmt_state := active, pending and pkt is sm packet --spec perform_stream_mgmt(xmpp_element(), state()) -> state(). -perform_stream_mgmt(Pkt, #{mgmt_xmlns := Xmlns} = State) -> - case xmpp:get_ns(Pkt) of - Xmlns -> - case Pkt of - #sm_a{} -> - handle_a(State, Pkt); - #sm_r{} -> - handle_r(State); - _ -> - send(State, #sm_failed{reason = 'bad-request', xmlns = Xmlns}) - end; +-spec server_negotiate_stream_mgmt(xmpp_element(), state()) -> state(). +server_negotiate_stream_mgmt(Pkt, State) -> + Xmlns = xmpp:get_ns(Pkt), + case Pkt of + #sm_enable{} -> + handle_enable(State#{mgmt_xmlns => Xmlns}, Pkt); + _ when is_record(Pkt, sm_a); + is_record(Pkt, sm_r); + is_record(Pkt, sm_resume) -> + Err = #sm_failed{reason = 'unexpected-request', xmlns = Xmlns}, + send(State, Err); _ -> - send(State, #sm_failed{reason = 'unsupported-version', xmlns = Xmlns}) + Err = #sm_failed{reason = 'bad-request', xmlns = Xmlns}, + send(State, Err) end. +handle_enable(#{remote_server := RServer, + mgmt_timeout := DefaultTimeout, + mgmt_max_timeout := MaxTimeout, + mgmt_xmlns := Xmlns, + mgmt_queue_type := QueueType} = State, + #sm_enable{resume = Resume, max = Max}) -> + Timeout = + if Resume == false -> + 0; + Max /= undefined, Max > 0, Max =< MaxTimeout -> + Max; + true -> + DefaultTimeout + end, + Res = if Timeout > 0 -> + ?INFO_MSG("Stream management with " + "resumption enabled for ~s", [RServer]), + #sm_enabled{resume = true, + id = make_resume_id(State), + max = Timeout, xmlns = Xmlns}; + true -> + ?INFO_MSG("Stream management enabled for ~s", [RServer]), + #sm_enabled{xmlns = Xmlns} + end, + State1 = State#{mgmt_state => active, + mgmt_timeout => Timeout, + mgmt_queue => p1_queue:new(QueueType)}, + send(State1, Res). + -spec negotiate_stream_mgmt(xmpp_element(), state()) -> state(). -negotiate_stream_mgmt(Pkt, #{mgmt_xmlns := Xmlns, - mgmt_state := MgmtState} = State) -> +negotiate_stream_mgmt(Pkt, #{mgmt_xmlns := Xmlns} = State) -> case Pkt of #sm_enabled{} -> handle_enabled(State, Pkt); - _ when is_record(Pkt, sm_r); - is_record(Pkt, sm_a) -> + _ when is_record(Pkt, sm_a); + is_record(Pkt, sm_r); + is_record(Pkt, sm_resumed) -> Err = #sm_failed{reason = 'unexpected-request', xmlns = Xmlns}, send(State, Err); _ -> @@ -302,34 +394,6 @@ negotiate_stream_mgmt(Pkt, #{mgmt_xmlns := Xmlns, send(State, Err) end. --spec handle_resumed(sm_resumed(), state()) -> state(). -handle_resumed(#sm_resumed{h = H, previd = Id}, - #{mgmt_xmlns := Xmlns, remote_server := RServer, - mgmt_old_session := OldState, mod := Mod} = State) -> - - State1 = check_h_attribute(State, H), - - State2 = resend_unacked_stanzas(State1), - - State3 = send(State2#{mgmt_state => resumed}, #sm_r{xmlns = Xmlns}), - - ?DEBUG("Resumed session with ~s", [RServer]), - - {ok, State3}. - --spec resend_unacked_stanzas(state()) -> state(). -resend_unacked_stanzas(#{mgmt_state := MgmtState, - mgmt_queue := Queue, - remote_server := RServer} = State) - when MgmtState == pending andalso ?qlen(Queue) > 0 -> - p1_queue:foldl( - fun({_, _Time, Pkt}, AccState) -> - % set is_resent = true like in mod_stream_mgmt - send(AccState, Pkt) - end, State, Queue); -resend_unacked_stanzas(State) -> - State. - -spec handle_enabled(state(), sm_enabled()) -> state(). handle_enabled(#{remote_server := RServer, mgmt_timeout := DefaultTimeout, @@ -350,125 +414,206 @@ handle_enabled(#{remote_server := RServer, ?INFO_MSG("Stream management enabled for ~s", [RServer]), State end, - - State2 = - case not p1_queue:is_empty(Queue) of - true -> - send_rack(State1); - _ -> - State1 - end, - - State2#{mgmt_state => active, mgmt_timeout => Timeout}. - --spec handle_r(state()) -> state(). -handle_r(#{mgmt_stanzas_in := H, - mgmt_xmlns := Xmlns} = State) -> - send(State, #sm_a{h = H, xmlns = Xmlns}). - --spec handle_a(state(), sm_a()) -> state(). -handle_a(#{mgmt_stanzas_out := NumStanzasOut, - remote_server := RServer} = State, #sm_a{h = H}) -> - State1 = check_h_attribute(State, H), - resend_rack(State1). - --spec transition_to_resume(state()) -> state(). -transition_to_resume(#{mgmt_state := active, mod := Mod, - mgmt_timeout := 0} = State) -> - State; % route messages from queue -transition_to_resume(#{mgmt_state := active, mod := Mod, - remote_server := RServer,server_host := Server, - mgmt_connection_timeout := Timeout} = State) -> - State1 = mod_stream_mgmt:cancel_ack_timer(State), - ?DEBUG("Try to connect to remote server ~s", [RServer]), - {ok, Pid} = - ejabberd_s2s:start_connection(jid:make(Server), - jid:make(RServer), - [{resume, State1}]), - - erlang:start_timer(Timeout, Pid, connection_timeout), - - State1; -transition_to_resume(#{mgmt_state := resume, - mod := Mod, - remote_server := RServer} = State) -> - Mod:connect(self()), - State; -transition_to_resume(State) -> - State. -%% fix: filter some messages - -bounce_errors(#{mgmt_state := resume, - mgmt_queue := Queue} = State) - when ?qlen(Queue) > 0 -> - p1_queue:foreach( - fun({_, _, Pkt}) -> - Error = xmpp:err_remote_server_timeout(), - ejabberd_router:route_error(Pkt, Error) - end, Queue); -bounce_errors(State) -> - ok. - --spec route_unacked_stanzas(state()) -> state(). -route_unacked_stanzas(#{mgmt_queue := Queue, - mgmt_state := pending, - mgmt_xmlns := Xmlns} = State) - when ?qlen(Queue) > 0 -> - State1 = send(State, #sm_enable{xmlns = Xmlns}), - p1_queue:foldl( - fun({_, _, Pkt}, AccState) -> - % set is_resend = true like in mod_stream_mgmt - send(AccState, Pkt) - end, State1, Queue); -route_unacked_stanzas(_State) -> - ok. + State1#{mgmt_state => active, mgmt_timeout => Timeout}. + +% What should we encode? +-spec make_resume_id(state()) -> binary(). +make_resume_id(#{owner := Owner, remote_server := RServer}) -> + misc:term_to_base64({RServer, Owner}). + +% % mgmt_state := active, pending and pkt is sm packet +% -spec perform_stream_mgmt(xmpp_element(), state()) -> state(). +% perform_stream_mgmt(Pkt, #{mgmt_xmlns := Xmlns} = State) -> +% case xmpp:get_ns(Pkt) of +% Xmlns -> +% case Pkt of +% #sm_a{} -> +% handle_a(State, Pkt); +% #sm_r{} -> +% handle_r(State); +% _ -> +% send(State, #sm_failed{reason = 'bad-request', xmlns = Xmlns}) +% end; +% _ -> +% send(State, #sm_failed{reason = 'unsupported-version', xmlns = Xmlns}) +% end. + +% -spec negotiate_stream_mgmt(xmpp_element(), state()) -> state(). +% negotiate_stream_mgmt(Pkt, #{mgmt_xmlns := Xmlns} = State) -> +% case Pkt of +% #sm_enabled{} -> +% handle_enabled(State, Pkt); +% _ when is_record(Pkt, sm_r); +% is_record(Pkt, sm_a) -> +% Err = #sm_failed{reason = 'unexpected-request', xmlns = Xmlns}, +% send(State, Err); +% _ -> +% Err = #sm_failed{reason = 'bad-request', xmlns = Xmlns}, +% send(State, Err) +% end. + +% -spec handle_resumed(sm_resumed(), state()) -> state(). +% handle_resumed(#sm_resumed{h = H, previd = Id}, +% #{mgmt_xmlns := Xmlns, remote_server := RServer} = State) -> +% State1 = check_h_attribute(State, H), +% State2 = resend_unacked_stanzas(State1), +% State3 = send(State2, #sm_r{xmlns = Xmlns}), +% ?DEBUG("Resumed session with ~s", [RServer]), +% {ok, State3}. + +% -spec resend_unacked_stanzas(state()) -> state(). +% resend_unacked_stanzas(#{mgmt_state := MgmtState, +% mgmt_queue := Queue, +% remote_server := RServer} = State) +% when MgmtState == pending andalso ?qlen(Queue) > 0 -> +% p1_queue:foldl( +% fun({_, Time, Pkt}, AccState) -> +% NewPkt = mod_stream_mgmt:add_resent_delay_info(AccState, Pkt, Time), +% send(AccState, xmpp:put_meta(NewPkt, mgmt_is_resent, true)) +% end, State, Queue); +% resend_unacked_stanzas(State) -> +% State. + +% -spec handle_enabled(state(), sm_enabled()) -> state(). +% handle_enabled(#{remote_server := RServer, +% mgmt_timeout := DefaultTimeout, +% mgmt_queue := Queue} = State, +% #sm_enabled{resume = Resume, max = Max, id = Id}) -> +% Timeout = if Resume == false -> +% 0; +% Max /= undefined -> +% Max; +% true -> +% DefaultTimeout +% end, +% State1 = if Timeout > 0 -> +% ?INFO_MSG("Stream management with " +% "resumption enabled for ~s", [RServer]), +% State#{mgmt_privid => Id}; +% true -> +% ?INFO_MSG("Stream management enabled for ~s", [RServer]), +% State +% end, + +% State2 = +% case not p1_queue:is_empty(Queue) of +% true -> +% send_rack(State1); +% _ -> +% State1 +% end, + +% State2#{mgmt_state => active, mgmt_timeout => Timeout}. + +% -spec handle_r(state()) -> state(). +% handle_r(#{mgmt_stanzas_in := H, +% mgmt_xmlns := Xmlns} = State) -> +% send(State, #sm_a{h = H, xmlns = Xmlns}). + +% -spec handle_a(state(), sm_a()) -> state(). +% handle_a(#{mgmt_stanzas_out := NumStanzasOut, +% remote_server := RServer} = State, #sm_a{h = H}) -> +% State1 = check_h_attribute(State, H), +% resend_rack(State1). + +% -spec transition_to_resume(state()) -> state(). +% transition_to_resume(#{mgmt_state := active, mod := Mod, +% mgmt_timeout := 0} = State) -> +% State; % route messages from queue +% transition_to_resume(#{mgmt_state := active, mod := Mod, +% remote_server := RServer,server_host := Server, +% mgmt_connection_timeout := Timeout} = State) -> +% State1 = mod_stream_mgmt:cancel_ack_timer(State), +% ?DEBUG("Try to connect to remote server ~s", [RServer]), +% {ok, Pid} = +% ejabberd_s2s:start_connection(jid:make(Server), +% jid:make(RServer), +% [{resume, State1}]), + +% erlang:start_timer(Timeout, Pid, connection_timeout), + +% State1; +% transition_to_resume(#{mgmt_state := resume, +% mod := Mod, +% remote_server := RServer} = State) -> +% Mod:connect(self()), +% State; +% transition_to_resume(State) -> +% State. + +% % %% fix: filter some messages + +% bounce_errors(#{mgmt_state := resume, +% mgmt_queue := Queue} = State) +% when ?qlen(Queue) > 0 -> +% p1_queue:foreach( +% fun({_, _, Pkt}) -> +% Error = xmpp:err_remote_server_timeout(), +% ejabberd_router:route_error(Pkt, Error) +% end, Queue); +% bounce_errors(State) -> +% ok. + +% % -spec route_unacked_stanzas(state()) -> state(). +% % route_unacked_stanzas(#{mgmt_queue := Queue, +% % mgmt_state := pending, +% % mgmt_xmlns := Xmlns} = State) +% % when ?qlen(Queue) > 0 -> +% % State1 = send(State, #sm_enable{xmlns = Xmlns}), +% % p1_queue:foldl( +% % fun({_, _, Pkt}, AccState) -> +% % % set is_resend = true like in mod_stream_mgmt +% % send(AccState, Pkt) +% % end, State1, Queue); +% % route_unacked_stanzas(_State) -> +% % ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% todo: import from mod_stream_mgmt.erl --spec check_h_attribute(state(), non_neg_integer()) -> state(). -check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, - remote_server := RServer} = State, H) - when H > NumStanzasOut -> - ?DEBUG("~s acknowledged ~B stanzas," - "but only ~B were sent ", [RServer, H, NumStanzasOut]), - mod_stream_mgmt:mgmt_queue_drop(State#{mgmt_stanzas_out => H}, NumStanzasOut); -check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, - remote_server := RServer} = State, H) -> - ?DEBUG("~s acknowledged ~B of ~B " - "stanzas", [RServer, H, NumStanzasOut]), - mod_stream_mgmt:mgmt_queue_drop(State, H). +% -spec check_h_attribute(state(), non_neg_integer()) -> state(). +% check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, +% remote_server := RServer} = State, H) +% when H > NumStanzasOut -> +% ?DEBUG("~s acknowledged ~B stanzas," +% "but only ~B were sent ", [RServer, H, NumStanzasOut]), +% mod_stream_mgmt:mgmt_queue_drop(State#{mgmt_stanzas_out => H}, NumStanzasOut); +% check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, +% remote_server := RServer} = State, H) -> +% ?DEBUG("~s acknowledged ~B of ~B " +% "stanzas", [RServer, H, NumStanzasOut]), +% mod_stream_mgmt:mgmt_queue_drop(State, H). -spec send(state(), xmpp_element()) -> state(). send(#{mod := Mod} = State, Pkt) -> Mod:send(State, Pkt). -send_rack(#{mgmt_ack_timer := _} = State) -> - State; -send_rack(#{mgmt_xmlns := Xmlns, - mgmt_stanzas_out := NumStanzasOut, - mgmt_ack_timeout := AckTimeout} = State) -> - TRef = erlang:start_timer(AckTimeout, self(), ack_timeout), - State1 = State#{mgmt_ack_timer => TRef, mgmt_stanzas_req => NumStanzasOut}, - send(State1, #sm_r{xmlns = Xmlns}). - -resend_rack(#{mgmt_ack_timer := _, - mgmt_queue := Queue, - mgmt_stanzas_out := NumStanzasOut, - mgmt_stanzas_req := NumStanzasReq} = State) -> - State1 = State, % mod_stream_mgmt:cancel_ack_timer(State), - case NumStanzasReq < NumStanzasOut andalso not p1_queue:is_empty(Queue) of - true -> send_rack(State1); - false -> State1 - end; -resend_rack(State) -> - State. +% send_rack(#{mgmt_ack_timer := _} = State) -> +% State; +% send_rack(#{mgmt_xmlns := Xmlns, +% mgmt_stanzas_out := NumStanzasOut, +% mgmt_ack_timeout := AckTimeout} = State) -> +% TRef = erlang:start_timer(AckTimeout, self(), ack_timeout), +% State1 = State#{mgmt_ack_timer => TRef, mgmt_stanzas_req => NumStanzasOut}, +% send(State1, #sm_r{xmlns = Xmlns}). + +% resend_rack(#{mgmt_ack_timer := _, +% mgmt_queue := Queue, +% mgmt_stanzas_out := NumStanzasOut, +% mgmt_stanzas_req := NumStanzasReq} = State) -> +% State1 = State, % mod_stream_mgmt:cancel_ack_timer(State), +% case NumStanzasReq < NumStanzasOut andalso not p1_queue:is_empty(Queue) of +% true -> send_rack(State1); +% false -> State1 +% end; +% resend_rack(State) -> +% State. -spec update_num_stanzas_in(state(), xmpp_element()) -> state(). update_num_stanzas_in(#{mgmt_state := MgmtState, mgmt_stanzas_in := NumStanzasIn} = State, El) - when MgmtState == active -> + when MgmtState == active -> %; MgmtState == pending -> NewNum = case {xmpp:is_stanza(El), NumStanzasIn} of {true, 4294967295} -> 0; @@ -488,6 +633,13 @@ update_num_stanzas_in(State, _El) -> get_resume_timeout(Host) -> gen_mod:get_module_opt(Host, ?MODULE, resume_timeout, 300). +get_max_resume_timeout(Host, ResumeTimeout) -> + case gen_mod:get_module_opt(Host, ?MODULE, max_resume_timeout) of + undefined -> ResumeTimeout; + Max when Max >= ResumeTimeout -> Max; + _ -> ResumeTimeout + end. + get_queue_type(Host) -> case gen_mod:get_module_opt(Host, ?MODULE, queue_type) of undefined -> ejabberd_config:default_queue_type(Host); @@ -529,5 +681,5 @@ mod_opt_type(queue_type) -> fun(file) -> file; (ram) -> ram end; -mod_opt_type(_) -> [max_ack_queue, ack_timeout, resume_timeout, +mod_opt_type(_) -> [max_ack_queue, ack_timeout, resume_timeout, max_resume_timeout, queue_type, max_unacked_stanzas, connection_timeout]. From 39eb153d04ddbd545c2bd60e2952928985d85105 Mon Sep 17 00:00:00 2001 From: amuhar Date: Thu, 29 Jun 2017 17:46:07 +0300 Subject: [PATCH 08/25] stanza acknowledgement server and client part --- src/mod_stream_mgmt_s2s.erl | 182 +++++++++++++++++++++++++----------- 1 file changed, 129 insertions(+), 53 deletions(-) diff --git a/src/mod_stream_mgmt_s2s.erl b/src/mod_stream_mgmt_s2s.erl index f673eab7d64..a696545f7ff 100644 --- a/src/mod_stream_mgmt_s2s.erl +++ b/src/mod_stream_mgmt_s2s.erl @@ -7,7 +7,7 @@ -export([start/2, stop/1, reload/3, depends/2, mod_opt_type/1]). %% client part hooks -export([s2s_out_stream_init/2, s2s_out_stream_features/2, - s2s_out_packet/2, s2s_out_established/1]). + s2s_out_packet/2, s2s_handle_send/3, s2s_out_established/1]). % s2s_out_packet/2, s2s_out_handle_recv/3, s2s_out_handle_send/3, %s2s_out_handle_info/2, s2s_out_closed/2, % s2s_out_terminate/2]). % , s2s_out_established/1]). @@ -41,8 +41,8 @@ start(Host, _Opts) -> ejabberd_hooks:add(s2s_out_packet, Host, ?MODULE, s2s_out_packet, 5), % ejabberd_hooks:add(s2s_out_handle_recv, % Host, ?MODULE, s2s_out_handle_recv, 50), - % ejabberd_hooks:add(s2s_out_handle_send, - % Host, ?MODULE, s2s_out_handle_send, 50), + ejabberd_hooks:add(s2s_out_handle_send, + Host, ?MODULE, s2s_handle_send, 50), % ejabberd_hooks:add(s2s_out_handle_info, % Host, ?MODULE, s2s_out_handle_info, 50), % ejabberd_hooks:add(s2s_out_closed, @@ -57,7 +57,9 @@ start(Host, _Opts) -> Host, ?MODULE, s2s_in_post_auth_features, 50), ejabberd_hooks:add(s2s_in_init, ?MODULE, s2s_in_init, 50), ejabberd_hooks:add(s2s_in_authenticated_packet, - Host, ?MODULE, s2s_in_authenticated_packet, 50). + Host, ?MODULE, s2s_in_authenticated_packet, 50), + ejabberd_hooks:add(s2s_in_handle_send, + Host, ?MODULE, s2s_handle_send, 50). stop(Host) -> ejabberd_hooks:delete(s2s_out_init, Host, ?MODULE, s2s_out_stream_init, 50), @@ -66,8 +68,8 @@ stop(Host) -> ejabberd_hooks:delete(s2s_out_packet, Host, ?MODULE, s2s_out_packet, 50), % ejabberd_hooks:delete(s2s_out_handle_recv, % Host, ?MODULE, s2s_out_handle_recv, 50), - % ejabberd_hooks:delete(s2s_out_handle_send, - % Host, ?MODULE, s2s_out_handle_send, 50), + ejabberd_hooks:delete(s2s_out_handle_send, + Host, ?MODULE, s2s_out_handle_send, 50), % ejabberd_hooks:delete(s2s_out_handle_info, % Host, ?MODULE, s2s_out_handle_info, 50), % ejabberd_hooks:delete(s2s_out_closed, @@ -82,7 +84,9 @@ stop(Host) -> Host, ?MODULE, s2s_in_post_auth_features, 50), % ejabberd_hooks:delete(s2s_in_init, ?MODULE, s2s_in_init), ejabberd_hooks:delete(s2s_in_authenticated_packet, - Host, ?MODULE, s2s_in_authenticated_packet, 50). + Host, ?MODULE, s2s_in_authenticated_packet, 50), + ejabberd_hooks:delete(s2s_in_handle_send, + Host, ?MODULE, s2s_handle_send, 50). reload(_Host, _NewOpts, _OldOpts) -> ?WARNING_MSG("module ~s is reloaded, but new configuration will take " @@ -170,12 +174,40 @@ s2s_out_established(State) -> s2s_out_packet(#{mgmt_state := MgmtState} = State, Pkt) when ?is_sm_packet(Pkt) -> - if MgmtState == wait_for_enabled -> + if MgmtState == active -> + {stop, perform_stream_mgmt(Pkt, State)}; + MgmtState == wait_for_enabled -> % может быть и в других состояниях можно {stop, negotiate_stream_mgmt(Pkt, State)}; true -> {stop, State} end; s2s_out_packet(State, Pkt) -> + update_num_stanzas_in(State, Pkt). + +s2s_handle_send(#{mgmt_state := MgmtState, lang := Lang} = State, Pkt, SendResult) + when MgmtState == active; MgmtState == wait_for_enabled -> + case Pkt of + _ when ?is_stanza(Pkt) -> + Meta = xmpp:get_meta(Pkt), + case maps:get(mgmt_is_resent, Meta, false) of + false -> + case mod_stream_mgmt:mgmt_queue_add(State, Pkt) of + #{mgmt_max_queue := exceeded} = State1 -> + Err = xmpp:serr_policy_violation( + <<"Too many unacked stanzas">>, Lang), + send(State1, Err); + State1 when MgmtState /= wait_for_enabled, SendResult == ok -> + send_rack(State1); + State1 -> + State1 + end; + true -> + State + end; + _ -> + State + end; +s2s_handle_send(State, _, _) -> State. %% client part @@ -199,7 +231,9 @@ s2s_in_init(State, _Opts) -> s2s_in_authenticated_packet(#{mgmt_state := MgmtState} = State, Pkt) when ?is_sm_packet(Pkt) -> - if MgmtState == inactive -> + if MgmtState == active -> + {stop, server_perform_stream_mgmt(Pkt, State)}; + MgmtState == inactive -> {stop, server_negotiate_stream_mgmt(Pkt, State)}; true -> {stop, State} @@ -379,6 +413,23 @@ handle_enable(#{remote_server := RServer, mgmt_queue => p1_queue:new(QueueType)}, send(State1, Res). +% mgmt_state := active, pending and pkt is sm packet +-spec server_perform_stream_mgmt(xmpp_element(), state()) -> state(). +server_perform_stream_mgmt(Pkt, #{mgmt_xmlns := Xmlns} = State) -> + case xmpp:get_ns(Pkt) of + Xmlns -> + case Pkt of + #sm_a{} -> + handle_a(State, Pkt); + #sm_r{} -> + handle_r(State); + _ -> + send(State, #sm_failed{reason = 'bad-request', xmlns = Xmlns}) + end; + _ -> + send(State, #sm_failed{reason = 'unsupported-version', xmlns = Xmlns}) + end. + -spec negotiate_stream_mgmt(xmpp_element(), state()) -> state(). negotiate_stream_mgmt(Pkt, #{mgmt_xmlns := Xmlns} = State) -> case Pkt of @@ -414,8 +465,43 @@ handle_enabled(#{remote_server := RServer, ?INFO_MSG("Stream management enabled for ~s", [RServer]), State end, + State2 = + case not p1_queue:is_empty(Queue) of + true -> + send_rack(State1); + _ -> + State1 + end, - State1#{mgmt_state => active, mgmt_timeout => Timeout}. + State2#{mgmt_state => active, mgmt_timeout => Timeout}. + +% mgmt_state := active, pending and pkt is sm packet +-spec perform_stream_mgmt(xmpp_element(), state()) -> state(). +perform_stream_mgmt(Pkt, #{mgmt_xmlns := Xmlns} = State) -> + case xmpp:get_ns(Pkt) of + Xmlns -> + case Pkt of + #sm_a{} -> + handle_a(State, Pkt); + #sm_r{} -> + handle_r(State); + _ -> + send(State, #sm_failed{reason = 'bad-request', xmlns = Xmlns}) + end; + _ -> + send(State, #sm_failed{reason = 'unsupported-version', xmlns = Xmlns}) + end. + +-spec handle_r(state()) -> state(). +handle_r(#{mgmt_stanzas_in := H, + mgmt_xmlns := Xmlns} = State) -> + send(State, #sm_a{h = H, xmlns = Xmlns}). + +-spec handle_a(state(), sm_a()) -> state(). +handle_a(#{mgmt_stanzas_out := NumStanzasOut, + remote_server := RServer} = State, #sm_a{h = H}) -> + State1 = check_h_attribute(State, H), + resend_rack(State1). % What should we encode? -spec make_resume_id(state()) -> binary(). @@ -506,17 +592,6 @@ make_resume_id(#{owner := Owner, remote_server := RServer}) -> % State2#{mgmt_state => active, mgmt_timeout => Timeout}. -% -spec handle_r(state()) -> state(). -% handle_r(#{mgmt_stanzas_in := H, -% mgmt_xmlns := Xmlns} = State) -> -% send(State, #sm_a{h = H, xmlns = Xmlns}). - -% -spec handle_a(state(), sm_a()) -> state(). -% handle_a(#{mgmt_stanzas_out := NumStanzasOut, -% remote_server := RServer} = State, #sm_a{h = H}) -> -% State1 = check_h_attribute(State, H), -% resend_rack(State1). - % -spec transition_to_resume(state()) -> state(). % transition_to_resume(#{mgmt_state := active, mod := Mod, % mgmt_timeout := 0} = State) -> @@ -572,43 +647,44 @@ make_resume_id(#{owner := Owner, remote_server := RServer}) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% todo: import from mod_stream_mgmt.erl -% -spec check_h_attribute(state(), non_neg_integer()) -> state(). -% check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, -% remote_server := RServer} = State, H) -% when H > NumStanzasOut -> -% ?DEBUG("~s acknowledged ~B stanzas," -% "but only ~B were sent ", [RServer, H, NumStanzasOut]), -% mod_stream_mgmt:mgmt_queue_drop(State#{mgmt_stanzas_out => H}, NumStanzasOut); -% check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, -% remote_server := RServer} = State, H) -> -% ?DEBUG("~s acknowledged ~B of ~B " -% "stanzas", [RServer, H, NumStanzasOut]), -% mod_stream_mgmt:mgmt_queue_drop(State, H). +-spec check_h_attribute(state(), non_neg_integer()) -> state(). +check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, + remote_server := RServer} = State, H) + when H > NumStanzasOut -> + ?DEBUG("~s acknowledged ~B stanzas," + "but only ~B were sent ", [RServer, H, NumStanzasOut]), + mod_stream_mgmt:mgmt_queue_drop(State#{mgmt_stanzas_out => H}, NumStanzasOut); +check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, + remote_server := RServer} = State, H) -> + ?DEBUG("~s acknowledged ~B of ~B " + "stanzas", [RServer, H, NumStanzasOut]), + mod_stream_mgmt:mgmt_queue_drop(State, H). -spec send(state(), xmpp_element()) -> state(). send(#{mod := Mod} = State, Pkt) -> + ?INFO_MSG("mod ~p", [Mod]), Mod:send(State, Pkt). -% send_rack(#{mgmt_ack_timer := _} = State) -> -% State; -% send_rack(#{mgmt_xmlns := Xmlns, -% mgmt_stanzas_out := NumStanzasOut, -% mgmt_ack_timeout := AckTimeout} = State) -> -% TRef = erlang:start_timer(AckTimeout, self(), ack_timeout), -% State1 = State#{mgmt_ack_timer => TRef, mgmt_stanzas_req => NumStanzasOut}, -% send(State1, #sm_r{xmlns = Xmlns}). - -% resend_rack(#{mgmt_ack_timer := _, -% mgmt_queue := Queue, -% mgmt_stanzas_out := NumStanzasOut, -% mgmt_stanzas_req := NumStanzasReq} = State) -> -% State1 = State, % mod_stream_mgmt:cancel_ack_timer(State), -% case NumStanzasReq < NumStanzasOut andalso not p1_queue:is_empty(Queue) of -% true -> send_rack(State1); -% false -> State1 -% end; -% resend_rack(State) -> -% State. +send_rack(#{mgmt_ack_timer := _} = State) -> + State; +send_rack(#{mgmt_xmlns := Xmlns, + mgmt_stanzas_out := NumStanzasOut, + mgmt_ack_timeout := AckTimeout} = State) -> + TRef = erlang:start_timer(AckTimeout, self(), ack_timeout), + State1 = State#{mgmt_ack_timer => TRef, mgmt_stanzas_req => NumStanzasOut}, + send(State1, #sm_r{xmlns = Xmlns}). + +resend_rack(#{mgmt_ack_timer := _, + mgmt_queue := Queue, + mgmt_stanzas_out := NumStanzasOut, + mgmt_stanzas_req := NumStanzasReq} = State) -> + State1 = mod_stream_mgmt:cancel_ack_timer(State), + case NumStanzasReq < NumStanzasOut andalso not p1_queue:is_empty(Queue) of + true -> send_rack(State1); + false -> State1 + end; +resend_rack(State) -> + State. -spec update_num_stanzas_in(state(), xmpp_element()) -> state(). update_num_stanzas_in(#{mgmt_state := MgmtState, From ef7e29ce0467d530a7f0bbdb83c4cce1f426045b Mon Sep 17 00:00:00 2001 From: amuhar Date: Thu, 29 Jun 2017 21:13:12 +0300 Subject: [PATCH 09/25] timer for stanza acknowledgement --- src/mod_stream_mgmt_s2s.erl | 123 ++++++++---------------------------- 1 file changed, 28 insertions(+), 95 deletions(-) diff --git a/src/mod_stream_mgmt_s2s.erl b/src/mod_stream_mgmt_s2s.erl index a696545f7ff..f117b1a5666 100644 --- a/src/mod_stream_mgmt_s2s.erl +++ b/src/mod_stream_mgmt_s2s.erl @@ -7,7 +7,8 @@ -export([start/2, stop/1, reload/3, depends/2, mod_opt_type/1]). %% client part hooks -export([s2s_out_stream_init/2, s2s_out_stream_features/2, - s2s_out_packet/2, s2s_handle_send/3, s2s_out_established/1]). + s2s_out_packet/2, s2s_handle_send/3, s2s_handle_info/2, + s2s_out_established/1]). % s2s_out_packet/2, s2s_out_handle_recv/3, s2s_out_handle_send/3, %s2s_out_handle_info/2, s2s_out_closed/2, % s2s_out_terminate/2]). % , s2s_out_established/1]). @@ -43,8 +44,8 @@ start(Host, _Opts) -> % Host, ?MODULE, s2s_out_handle_recv, 50), ejabberd_hooks:add(s2s_out_handle_send, Host, ?MODULE, s2s_handle_send, 50), - % ejabberd_hooks:add(s2s_out_handle_info, - % Host, ?MODULE, s2s_out_handle_info, 50), + ejabberd_hooks:add(s2s_out_handle_info, + Host, ?MODULE, s2s_handle_info, 50), % ejabberd_hooks:add(s2s_out_closed, % Host, ?MODULE, s2s_out_closed, 50), % ejabberd_hooks:add(s2s_out_terminate, @@ -59,7 +60,9 @@ start(Host, _Opts) -> ejabberd_hooks:add(s2s_in_authenticated_packet, Host, ?MODULE, s2s_in_authenticated_packet, 50), ejabberd_hooks:add(s2s_in_handle_send, - Host, ?MODULE, s2s_handle_send, 50). + Host, ?MODULE, s2s_handle_send, 50), + ejabberd_hooks:add(s2s_in_handle_info, + Host, ?MODULE, s2s_handle_info, 50). stop(Host) -> ejabberd_hooks:delete(s2s_out_init, Host, ?MODULE, s2s_out_stream_init, 50), @@ -70,8 +73,8 @@ stop(Host) -> % Host, ?MODULE, s2s_out_handle_recv, 50), ejabberd_hooks:delete(s2s_out_handle_send, Host, ?MODULE, s2s_out_handle_send, 50), - % ejabberd_hooks:delete(s2s_out_handle_info, - % Host, ?MODULE, s2s_out_handle_info, 50), + ejabberd_hooks:delete(s2s_out_handle_info, + Host, ?MODULE, s2s_handle_info, 50), % ejabberd_hooks:delete(s2s_out_closed, % Host, ?MODULE, s2s_out_closed, 50), % ejabberd_hooks:delete(s2s_out_terminate, @@ -86,7 +89,9 @@ stop(Host) -> ejabberd_hooks:delete(s2s_in_authenticated_packet, Host, ?MODULE, s2s_in_authenticated_packet, 50), ejabberd_hooks:delete(s2s_in_handle_send, - Host, ?MODULE, s2s_handle_send, 50). + Host, ?MODULE, s2s_handle_send, 50), + ejabberd_hooks:delete(s2s_in_handle_info, + Host, ?MODULE, s2s_handle_info). reload(_Host, _NewOpts, _OldOpts) -> ?WARNING_MSG("module ~s is reloaded, but new configuration will take " @@ -111,8 +116,8 @@ s2s_out_stream_init({ok, #{server_host := ServerHost, mod := Mod} = State}, Opts mgmt_queue_type => get_queue_type(ServerHost), mgmt_max_queue => get_max_ack_queue(ServerHost), mgmt_ack_timeout => get_ack_timeout(ServerHost), - mgmt_unacked_stanzas => get_max_unacked_stanzas(ServerHost), - mgmt_connection_timeout => get_connection_timeout(ServerHost), + % mgmt_unacked_stanzas => get_max_unacked_stanzas(ServerHost), + % mgmt_connection_timeout => get_connection_timeout(ServerHost), mgmt_stanzas_in => 0, mgmt_stanzas_out => 0, mgmt_stanzas_req => 0}}; @@ -186,6 +191,7 @@ s2s_out_packet(State, Pkt) -> s2s_handle_send(#{mgmt_state := MgmtState, lang := Lang} = State, Pkt, SendResult) when MgmtState == active; MgmtState == wait_for_enabled -> + ?INFO_MSG("handle send ~p", [Pkt]), case Pkt of _ when ?is_stanza(Pkt) -> Meta = xmpp:get_meta(Pkt), @@ -210,6 +216,15 @@ s2s_handle_send(#{mgmt_state := MgmtState, lang := Lang} = State, Pkt, SendResul s2s_handle_send(State, _, _) -> State. +s2s_handle_info(#{mgmt_ack_timer := TRef, remote_server := RServer, + mod := Mod} = State, {timeout, TRef, ack_timeout}) -> + ?DEBUG("Timed out waiting for stream management " + "acknowledgement of ~s", [RServer]), + Mod:stop(State), + {stop, State}; +s2s_handle_info(State, _) -> + State. + %% client part %% server part @@ -225,7 +240,10 @@ s2s_in_init({ok, #{server_host := Host} = State}, _Opts) -> mgmt_timeout => Timeout, mgmt_max_timeout => MaxTimeout, mgmt_queue_type => get_queue_type(Host), - mgmt_stanzas_in => 0}}; + mgmt_ack_timeout => get_ack_timeout(Host), + mgmt_stanzas_in => 0, + mgmt_stanzas_out => 0, + mgmt_stanzas_req => 0}}; s2s_in_init(State, _Opts) -> State. @@ -282,31 +300,6 @@ s2s_in_authenticated_packet(State, Pkt) -> % s2s_out_handle_recv(State, El, Pkt) -> % State. -% s2s_out_handle_send(#{mgmt_state := MgmtState, lang := Lang} = State, Pkt, SendResult) -% when MgmtState == active; MgmtState == pending; MgmtState == wait_for_enabled -> -% case Pkt of -% _ when ?is_stanza(Pkt) -> -% Meta = xmpp:get_meta(Pkt), -% case maps:get(mgmt_is_resent, Meta, false) of -% false -> -% case mod_stream_mgmt:mgmt_queue_add(State, Pkt) of -% #{mgmt_max_queue := exceeded} = State1 -> -% Err = xmpp:serr_policy_violation( -% <<"Too many unacked stanzas">>, Lang), -% send(State1, Err); -% State1 when MgmtState /= wait_for_enabled, SendResult == ok -> -% send_rack(State1); -% State1 -> -% State1 -% end; -% true -> -% State -% end; -% _ -> -% State -% end; -% s2s_out_handle_send(State, _, _) -> -% State. % s2s_out_handle_info(#{mgmt_ack_timer := TRef, remote_server := RServer, % mod := Mod} = State, {timeout, TRef, ack_timeout}) -> @@ -508,36 +501,6 @@ handle_a(#{mgmt_stanzas_out := NumStanzasOut, make_resume_id(#{owner := Owner, remote_server := RServer}) -> misc:term_to_base64({RServer, Owner}). -% % mgmt_state := active, pending and pkt is sm packet -% -spec perform_stream_mgmt(xmpp_element(), state()) -> state(). -% perform_stream_mgmt(Pkt, #{mgmt_xmlns := Xmlns} = State) -> -% case xmpp:get_ns(Pkt) of -% Xmlns -> -% case Pkt of -% #sm_a{} -> -% handle_a(State, Pkt); -% #sm_r{} -> -% handle_r(State); -% _ -> -% send(State, #sm_failed{reason = 'bad-request', xmlns = Xmlns}) -% end; -% _ -> -% send(State, #sm_failed{reason = 'unsupported-version', xmlns = Xmlns}) -% end. - -% -spec negotiate_stream_mgmt(xmpp_element(), state()) -> state(). -% negotiate_stream_mgmt(Pkt, #{mgmt_xmlns := Xmlns} = State) -> -% case Pkt of -% #sm_enabled{} -> -% handle_enabled(State, Pkt); -% _ when is_record(Pkt, sm_r); -% is_record(Pkt, sm_a) -> -% Err = #sm_failed{reason = 'unexpected-request', xmlns = Xmlns}, -% send(State, Err); -% _ -> -% Err = #sm_failed{reason = 'bad-request', xmlns = Xmlns}, -% send(State, Err) -% end. % -spec handle_resumed(sm_resumed(), state()) -> state(). % handle_resumed(#sm_resumed{h = H, previd = Id}, @@ -561,36 +524,6 @@ make_resume_id(#{owner := Owner, remote_server := RServer}) -> % resend_unacked_stanzas(State) -> % State. -% -spec handle_enabled(state(), sm_enabled()) -> state(). -% handle_enabled(#{remote_server := RServer, -% mgmt_timeout := DefaultTimeout, -% mgmt_queue := Queue} = State, -% #sm_enabled{resume = Resume, max = Max, id = Id}) -> -% Timeout = if Resume == false -> -% 0; -% Max /= undefined -> -% Max; -% true -> -% DefaultTimeout -% end, -% State1 = if Timeout > 0 -> -% ?INFO_MSG("Stream management with " -% "resumption enabled for ~s", [RServer]), -% State#{mgmt_privid => Id}; -% true -> -% ?INFO_MSG("Stream management enabled for ~s", [RServer]), -% State -% end, - -% State2 = -% case not p1_queue:is_empty(Queue) of -% true -> -% send_rack(State1); -% _ -> -% State1 -% end, - -% State2#{mgmt_state => active, mgmt_timeout => Timeout}. % -spec transition_to_resume(state()) -> state(). % transition_to_resume(#{mgmt_state := active, mod := Mod, From a403f4f237436656e014409d4a715e1c1591b5a2 Mon Sep 17 00:00:00 2001 From: amuhar Date: Thu, 6 Jul 2017 19:21:19 +0300 Subject: [PATCH 10/25] server can't enable stream management error --- src/mod_stream_mgmt_s2s.erl | 138 ++++++++++++++++++------------------ 1 file changed, 68 insertions(+), 70 deletions(-) diff --git a/src/mod_stream_mgmt_s2s.erl b/src/mod_stream_mgmt_s2s.erl index f117b1a5666..d757cf4c34b 100644 --- a/src/mod_stream_mgmt_s2s.erl +++ b/src/mod_stream_mgmt_s2s.erl @@ -7,14 +7,14 @@ -export([start/2, stop/1, reload/3, depends/2, mod_opt_type/1]). %% client part hooks -export([s2s_out_stream_init/2, s2s_out_stream_features/2, - s2s_out_packet/2, s2s_handle_send/3, s2s_handle_info/2, - s2s_out_established/1]). + s2s_out_packet/2, s2s_out_handle_recv/3, s2s_handle_send/3, + s2s_handle_info/2, s2s_out_established/1]). % s2s_out_packet/2, s2s_out_handle_recv/3, s2s_out_handle_send/3, %s2s_out_handle_info/2, s2s_out_closed/2, % s2s_out_terminate/2]). % , s2s_out_established/1]). %% server part hooks --export([s2s_in_post_auth_features/2, s2s_in_init/2, - s2s_in_authenticated_packet/2]). +-export([s2s_in_stream_init/2, s2s_in_stream_features/2, + s2s_in_unauthenticated_packet/2, s2s_in_authenticated_packet/2]). -include("xmpp.hrl"). @@ -39,9 +39,9 @@ start(Host, _Opts) -> ejabberd_hooks:add(s2s_out_init, Host, ?MODULE, s2s_out_stream_init, 50), ejabberd_hooks:add(s2s_out_authenticated_features, Host, ?MODULE, s2s_out_stream_features, 50), - ejabberd_hooks:add(s2s_out_packet, Host, ?MODULE, s2s_out_packet, 5), - % ejabberd_hooks:add(s2s_out_handle_recv, - % Host, ?MODULE, s2s_out_handle_recv, 50), + ejabberd_hooks:add(s2s_out_packet, Host, ?MODULE, s2s_out_packet, 50), + ejabberd_hooks:add(s2s_out_handle_recv, + Host, ?MODULE, s2s_out_handle_recv, 50), ejabberd_hooks:add(s2s_out_handle_send, Host, ?MODULE, s2s_handle_send, 50), ejabberd_hooks:add(s2s_out_handle_info, @@ -50,13 +50,14 @@ start(Host, _Opts) -> % Host, ?MODULE, s2s_out_closed, 50), % ejabberd_hooks:add(s2s_out_terminate, % Host, ?MODULE, s2s_out_terminate, 50), - % delete after implementation of server part ejabberd_hooks:add(s2s_out_established, Host, ?MODULE, s2s_out_established, 50), %% server part ejabberd_hooks:add(s2s_in_post_auth_features, - Host, ?MODULE, s2s_in_post_auth_features, 50), - ejabberd_hooks:add(s2s_in_init, ?MODULE, s2s_in_init, 50), + Host, ?MODULE, s2s_in_stream_features, 50), + ejabberd_hooks:add(s2s_in_init, ?MODULE, s2s_in_stream_init, 50), + ejabberd_hooks:add(s2s_in_unauthenticated_packet, + Host, ?MODULE, s2s_in_unauthenticated_packet, 50), ejabberd_hooks:add(s2s_in_authenticated_packet, Host, ?MODULE, s2s_in_authenticated_packet, 50), ejabberd_hooks:add(s2s_in_handle_send, @@ -69,8 +70,8 @@ stop(Host) -> ejabberd_hooks:delete(s2s_out_authenticated_features, Host, ?MODULE, s2s_out_stream_features, 50), ejabberd_hooks:delete(s2s_out_packet, Host, ?MODULE, s2s_out_packet, 50), - % ejabberd_hooks:delete(s2s_out_handle_recv, - % Host, ?MODULE, s2s_out_handle_recv, 50), + ejabberd_hooks:delete(s2s_out_handle_recv, + Host, ?MODULE, s2s_out_handle_recv, 50), ejabberd_hooks:delete(s2s_out_handle_send, Host, ?MODULE, s2s_out_handle_send, 50), ejabberd_hooks:delete(s2s_out_handle_info, @@ -84,8 +85,10 @@ stop(Host) -> Host, ?MODULE, s2s_out_established, 50), %% server part ejabberd_hooks:delete(s2s_in_post_auth_features, - Host, ?MODULE, s2s_in_post_auth_features, 50), + Host, ?MODULE, s2s_in_stream_features, 50), % ejabberd_hooks:delete(s2s_in_init, ?MODULE, s2s_in_init), + ejabberd_hooks:delete(s2s_in_unauthenticated_packet, + Host, ?MODULE, s2s_in_unauthenticated_packet, 50), ejabberd_hooks:delete(s2s_in_authenticated_packet, Host, ?MODULE, s2s_in_authenticated_packet, 50), ejabberd_hooks:delete(s2s_in_handle_send, @@ -191,7 +194,6 @@ s2s_out_packet(State, Pkt) -> s2s_handle_send(#{mgmt_state := MgmtState, lang := Lang} = State, Pkt, SendResult) when MgmtState == active; MgmtState == wait_for_enabled -> - ?INFO_MSG("handle send ~p", [Pkt]), case Pkt of _ when ?is_stanza(Pkt) -> Meta = xmpp:get_meta(Pkt), @@ -225,15 +227,18 @@ s2s_handle_info(#{mgmt_ack_timer := TRef, remote_server := RServer, s2s_handle_info(State, _) -> State. +s2s_out_handle_recv(#{mgmt_state := wait_for_enabled, + remote_server := RServer} = State, _El, #sm_failed{}) -> + ?DEBUG("Remote server ~s can't enable stream management", [RServer]), + State#{mgmt_state => inactive}; +s2s_out_handle_recv(State, _El, _Pkt) -> + State. + %% client part %% server part -s2s_in_post_auth_features(Acc, _Host) -> - [#feature_sm{xmlns = ?NS_STREAM_MGMT_2}, - #feature_sm{xmlns = ?NS_STREAM_MGMT_3}| Acc]. - -s2s_in_init({ok, #{server_host := Host} = State}, _Opts) -> +s2s_in_stream_init({ok, #{server_host := Host} = State}, _Opts) -> Timeout = get_resume_timeout(Host), MaxTimeout = get_max_resume_timeout(Host, Timeout), {ok, State#{mgmt_state => inactive, @@ -244,13 +249,23 @@ s2s_in_init({ok, #{server_host := Host} = State}, _Opts) -> mgmt_stanzas_in => 0, mgmt_stanzas_out => 0, mgmt_stanzas_req => 0}}; -s2s_in_init(State, _Opts) -> +s2s_in_stream_init(State, _Opts) -> + State. + +s2s_in_stream_features(Acc, _Host) -> + [#feature_sm{xmlns = ?NS_STREAM_MGMT_2}, + #feature_sm{xmlns = ?NS_STREAM_MGMT_3}| Acc]. + +s2s_in_unauthenticated_packet(State, Pkt) when ?is_sm_packet(Pkt) -> + Err = #sm_failed{reason = 'unexpected-request', xmlns = ?NS_STREAM_MGMT_3}, + {stop, send(State, Err)}; +s2s_in_unauthenticated_packet(State, Pkt) -> State. s2s_in_authenticated_packet(#{mgmt_state := MgmtState} = State, Pkt) when ?is_sm_packet(Pkt) -> if MgmtState == active -> - {stop, server_perform_stream_mgmt(Pkt, State)}; + {stop, perform_stream_mgmt(Pkt, State)}; MgmtState == inactive -> {stop, server_negotiate_stream_mgmt(Pkt, State)}; true -> @@ -377,38 +392,10 @@ server_negotiate_stream_mgmt(Pkt, State) -> send(State, Err) end. -handle_enable(#{remote_server := RServer, - mgmt_timeout := DefaultTimeout, - mgmt_max_timeout := MaxTimeout, - mgmt_xmlns := Xmlns, - mgmt_queue_type := QueueType} = State, - #sm_enable{resume = Resume, max = Max}) -> - Timeout = - if Resume == false -> - 0; - Max /= undefined, Max > 0, Max =< MaxTimeout -> - Max; - true -> - DefaultTimeout - end, - Res = if Timeout > 0 -> - ?INFO_MSG("Stream management with " - "resumption enabled for ~s", [RServer]), - #sm_enabled{resume = true, - id = make_resume_id(State), - max = Timeout, xmlns = Xmlns}; - true -> - ?INFO_MSG("Stream management enabled for ~s", [RServer]), - #sm_enabled{xmlns = Xmlns} - end, - State1 = State#{mgmt_state => active, - mgmt_timeout => Timeout, - mgmt_queue => p1_queue:new(QueueType)}, - send(State1, Res). - % mgmt_state := active, pending and pkt is sm packet --spec server_perform_stream_mgmt(xmpp_element(), state()) -> state(). -server_perform_stream_mgmt(Pkt, #{mgmt_xmlns := Xmlns} = State) -> +% ask about error messages for client part +-spec perform_stream_mgmt(xmpp_element(), state()) -> state(). +perform_stream_mgmt(Pkt, #{mgmt_xmlns := Xmlns} = State) -> case xmpp:get_ns(Pkt) of Xmlns -> case Pkt of @@ -438,6 +425,35 @@ negotiate_stream_mgmt(Pkt, #{mgmt_xmlns := Xmlns} = State) -> send(State, Err) end. +handle_enable(#{remote_server := RServer, + mgmt_timeout := DefaultTimeout, + mgmt_max_timeout := MaxTimeout, + mgmt_xmlns := Xmlns, + mgmt_queue_type := QueueType} = State, + #sm_enable{resume = Resume, max = Max}) -> + Timeout = + if Resume == false -> + 0; + Max /= undefined, Max > 0, Max =< MaxTimeout -> + Max; + true -> + DefaultTimeout + end, + Res = if Timeout > 0 -> + ?INFO_MSG("Stream management with " + "resumption enabled for ~s", [RServer]), + #sm_enabled{resume = true, + id = make_resume_id(State), + max = Timeout, xmlns = Xmlns}; + true -> + ?INFO_MSG("Stream management enabled for ~s", [RServer]), + #sm_enabled{xmlns = Xmlns} + end, + State1 = State#{mgmt_state => active, + mgmt_timeout => Timeout, + mgmt_queue => p1_queue:new(QueueType)}, + send(State1, Res). + -spec handle_enabled(state(), sm_enabled()) -> state(). handle_enabled(#{remote_server := RServer, mgmt_timeout := DefaultTimeout, @@ -468,23 +484,6 @@ handle_enabled(#{remote_server := RServer, State2#{mgmt_state => active, mgmt_timeout => Timeout}. -% mgmt_state := active, pending and pkt is sm packet --spec perform_stream_mgmt(xmpp_element(), state()) -> state(). -perform_stream_mgmt(Pkt, #{mgmt_xmlns := Xmlns} = State) -> - case xmpp:get_ns(Pkt) of - Xmlns -> - case Pkt of - #sm_a{} -> - handle_a(State, Pkt); - #sm_r{} -> - handle_r(State); - _ -> - send(State, #sm_failed{reason = 'bad-request', xmlns = Xmlns}) - end; - _ -> - send(State, #sm_failed{reason = 'unsupported-version', xmlns = Xmlns}) - end. - -spec handle_r(state()) -> state(). handle_r(#{mgmt_stanzas_in := H, mgmt_xmlns := Xmlns} = State) -> @@ -595,7 +594,6 @@ check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, -spec send(state(), xmpp_element()) -> state(). send(#{mod := Mod} = State, Pkt) -> - ?INFO_MSG("mod ~p", [Mod]), Mod:send(State, Pkt). send_rack(#{mgmt_ack_timer := _} = State) -> From b68f33c3ddcffe2b22cc96012b8428beb556c5dd Mon Sep 17 00:00:00 2001 From: amuhar Date: Tue, 25 Jul 2017 18:55:37 +0300 Subject: [PATCH 11/25] resend unacked stanzas --- src/ejabberd_s2s_in.erl | 19 +- src/ejabberd_s2s_out.erl | 2 +- src/mod_stream_mgmt.erl | 4 +- src/mod_stream_mgmt_s2s.erl | 675 ++++++++++++++++++++---------------- 4 files changed, 392 insertions(+), 308 deletions(-) diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl index 48a650a4ee8..424bf78afa7 100644 --- a/src/ejabberd_s2s_in.erl +++ b/src/ejabberd_s2s_in.erl @@ -41,7 +41,7 @@ -export([handle_unexpected_info/2, handle_unexpected_cast/2, reject_unauthenticated_packet/2, process_closed/2]). %% API --export([stop/1, close/1, close/2, send/2, update_state/2, establish/1, +-export([stop/1, close/1, close/2, send/2, call/3, reply/2, update_state/2, establish/1, host_up/1, host_down/1]). -include("ejabberd.hrl"). @@ -96,6 +96,13 @@ establish(State) -> update_state(Ref, Callback) -> xmpp_stream_in:cast(Ref, {update_state, Callback}). +-spec call(pid(), term(), non_neg_integer() | infinity) -> term(). +call(Ref, Msg, Timeout) -> + xmpp_stream_in:call(Ref, Msg, Timeout). + +reply(Ref, Reply) -> + xmpp_stream_in:reply(Ref, Reply). + -spec host_up(binary()) -> ok. host_up(Host) -> ejabberd_hooks:add(s2s_in_closed, Host, ?MODULE, @@ -176,6 +183,7 @@ handle_stream_end(Reason, #{server_host := LServer} = State) -> State1 = State#{stop_reason => Reason}, ejabberd_hooks:run_fold(s2s_in_closed, LServer, State1, [Reason]). +% unique_id можно заменить в будущем handle_stream_established(State) -> set_idle_timeout(State#{established => true}). @@ -260,6 +268,7 @@ init([State, Opts]) -> false -> [compression_none | TLSOpts1]; true -> TLSOpts1 end, + UniqueId = p1_time_compat:unique_integer(), State1 = State#{tls_options => TLSOpts2, auth_domains => sets:new(), xmlns => ?NS_SERVER, @@ -268,7 +277,8 @@ init([State, Opts]) -> lserver => ?MYNAME, server_host => ?MYNAME, established => false, - shaper => Shaper}, + shaper => Shaper, + unique_id => UniqueId}, % for xep-0198 s2s ejabberd_hooks:run_fold(s2s_in_init, {ok, State1}, [Opts]). handle_call(Request, From, #{server_host := LServer} = State) -> @@ -286,7 +296,7 @@ handle_info(Info, #{server_host := LServer} = State) -> ejabberd_hooks:run_fold(s2s_in_handle_info, LServer, State, [Info]). terminate(Reason, #{auth_domains := AuthDomains, - sockmod := SockMod, socket := Socket} = State) -> + sockmod := SockMod, socket := Socket, server_host := LServer} = State) -> case maps:get(stop_reason, State, undefined) of {tls, _} = Err -> ?ERROR_MSG("(~s) Failed to secure inbound s2s connection: ~s", @@ -302,7 +312,8 @@ terminate(Reason, #{auth_domains := AuthDomains, end, ok, AuthDomains); _ -> ok - end. + end, + ejabberd_hooks:run_fold(s2s_in_terminate, LServer, State, [Reason]). code_change(_OldVsn, State, _Extra) -> {ok, State}. diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl index 48dea99888b..2280a247d4f 100644 --- a/src/ejabberd_s2s_out.erl +++ b/src/ejabberd_s2s_out.erl @@ -321,7 +321,7 @@ terminate(Reason, #{server := LServer, server_host := ServerHost, end, bounce_queue(State1), bounce_message_queue(State1), - ejabberd_hooks:run_fold(s2s_out_terminate, ServerHost, State, [Reason]). + ejabberd_hooks:run_fold(s2s_out_terminate, ServerHost, State1, [Reason]). code_change(_OldVsn, State, _Extra) -> {ok, State}. diff --git a/src/mod_stream_mgmt.erl b/src/mod_stream_mgmt.erl index 5c058eb3767..bc1e1473622 100644 --- a/src/mod_stream_mgmt.erl +++ b/src/mod_stream_mgmt.erl @@ -36,8 +36,8 @@ %% adjust pending session timeout -export([get_resume_timeout/1, set_resume_timeout/2]). %% API (used by mod_stream_mgmt_s2s) --export ([mgmt_queue_drop/2, mgmt_queue_add/2, check_queue_length/1, - cancel_ack_timer/1, update_num_stanzas_in/2]). +-export ([mgmt_queue_drop/2, mgmt_queue_add/2, cancel_ack_timer/1, + update_num_stanzas_in/2]). -include("xmpp.hrl"). -include("logger.hrl"). diff --git a/src/mod_stream_mgmt_s2s.erl b/src/mod_stream_mgmt_s2s.erl index d757cf4c34b..98a1ef3bc5b 100644 --- a/src/mod_stream_mgmt_s2s.erl +++ b/src/mod_stream_mgmt_s2s.erl @@ -7,21 +7,19 @@ -export([start/2, stop/1, reload/3, depends/2, mod_opt_type/1]). %% client part hooks -export([s2s_out_stream_init/2, s2s_out_stream_features/2, - s2s_out_packet/2, s2s_out_handle_recv/3, s2s_handle_send/3, - s2s_handle_info/2, s2s_out_established/1]). - % s2s_out_packet/2, s2s_out_handle_recv/3, s2s_out_handle_send/3, - %s2s_out_handle_info/2, s2s_out_closed/2, - % s2s_out_terminate/2]). % , s2s_out_established/1]). + s2s_out_packet/2, s2s_out_handle_recv/3, s2s_out_handle_send/3, + s2s_out_handle_info/2, s2s_out_closed/2, + s2s_out_terminate/2, s2s_out_established/1]). %% server part hooks -export([s2s_in_stream_init/2, s2s_in_stream_features/2, - s2s_in_unauthenticated_packet/2, s2s_in_authenticated_packet/2]). - + s2s_in_unauthenticated_packet/2, s2s_in_authenticated_packet/2, + s2s_in_handle_call/3, s2s_in_handle_info/2, + s2s_in_closed/2, s2s_in_terminate/2]). -include("xmpp.hrl"). -include("logger.hrl"). -include("p1_queue.hrl"). -% replace ? -define(is_sm_packet(Pkt), is_record(Pkt, sm_enable) or is_record(Pkt, sm_enabled) or @@ -30,7 +28,7 @@ is_record(Pkt, sm_a) or is_record(Pkt, sm_r)). --type state() :: ejabberd_s2s_out:state(). % | ejabberd_s2s_in:state(). +-type state() :: map(). %%%============================================================================= %%% API @@ -43,27 +41,31 @@ start(Host, _Opts) -> ejabberd_hooks:add(s2s_out_handle_recv, Host, ?MODULE, s2s_out_handle_recv, 50), ejabberd_hooks:add(s2s_out_handle_send, - Host, ?MODULE, s2s_handle_send, 50), + Host, ?MODULE, s2s_out_handle_send, 50), ejabberd_hooks:add(s2s_out_handle_info, - Host, ?MODULE, s2s_handle_info, 50), - % ejabberd_hooks:add(s2s_out_closed, - % Host, ?MODULE, s2s_out_closed, 50), - % ejabberd_hooks:add(s2s_out_terminate, - % Host, ?MODULE, s2s_out_terminate, 50), + Host, ?MODULE, s2s_out_handle_info, 50), + ejabberd_hooks:add(s2s_out_closed, + Host, ?MODULE, s2s_out_closed, 50), + ejabberd_hooks:add(s2s_out_terminate, + Host, ?MODULE, s2s_out_terminate, 50), ejabberd_hooks:add(s2s_out_established, Host, ?MODULE, s2s_out_established, 50), %% server part + ejabberd_hooks:add(s2s_in_init, ?MODULE, s2s_in_stream_init, 50), ejabberd_hooks:add(s2s_in_post_auth_features, Host, ?MODULE, s2s_in_stream_features, 50), - ejabberd_hooks:add(s2s_in_init, ?MODULE, s2s_in_stream_init, 50), ejabberd_hooks:add(s2s_in_unauthenticated_packet, Host, ?MODULE, s2s_in_unauthenticated_packet, 50), ejabberd_hooks:add(s2s_in_authenticated_packet, Host, ?MODULE, s2s_in_authenticated_packet, 50), - ejabberd_hooks:add(s2s_in_handle_send, - Host, ?MODULE, s2s_handle_send, 50), + ejabberd_hooks:add(s2s_in_handle_call, + Host, ?MODULE, s2s_in_handle_call, 50), ejabberd_hooks:add(s2s_in_handle_info, - Host, ?MODULE, s2s_handle_info, 50). + Host, ?MODULE, s2s_in_handle_info, 50), + ejabberd_hooks:add(s2s_in_closed, + Host, ?MODULE, s2s_in_closed, 50), + ejabberd_hooks:add(s2s_in_terminate, + Host, ?MODULE, s2s_in_terminate, 50). stop(Host) -> ejabberd_hooks:delete(s2s_out_init, Host, ?MODULE, s2s_out_stream_init, 50), @@ -75,11 +77,11 @@ stop(Host) -> ejabberd_hooks:delete(s2s_out_handle_send, Host, ?MODULE, s2s_out_handle_send, 50), ejabberd_hooks:delete(s2s_out_handle_info, - Host, ?MODULE, s2s_handle_info, 50), - % ejabberd_hooks:delete(s2s_out_closed, - % Host, ?MODULE, s2s_out_closed, 50), - % ejabberd_hooks:delete(s2s_out_terminate, - % Host, ?MODULE, s2s_out_terminate, 50), + Host, ?MODULE, s2s_out_handle_info, 50), + ejabberd_hooks:delete(s2s_out_closed, + Host, ?MODULE, s2s_out_closed, 50), + ejabberd_hooks:delete(s2s_out_terminate, + Host, ?MODULE, s2s_out_terminate, 50), % delete after implementation of server part ejabberd_hooks:delete(s2s_out_established, Host, ?MODULE, s2s_out_established, 50), @@ -91,10 +93,14 @@ stop(Host) -> Host, ?MODULE, s2s_in_unauthenticated_packet, 50), ejabberd_hooks:delete(s2s_in_authenticated_packet, Host, ?MODULE, s2s_in_authenticated_packet, 50), - ejabberd_hooks:delete(s2s_in_handle_send, - Host, ?MODULE, s2s_handle_send, 50), + ejabberd_hooks:delete(s2s_in_handle_call, + Host, ?MODULE, s2s_in_handle_call, 50), ejabberd_hooks:delete(s2s_in_handle_info, - Host, ?MODULE, s2s_handle_info). + Host, ?MODULE, s2s_in_handle_info, 50), + ejabberd_hooks:delete(s2s_in_closed, + Host, ?MODULE, s2s_in_closed, 50), + ejabberd_hooks:delete(s2s_in_terminate, + Host, ?MODULE, s2s_in_terminate, 50). reload(_Host, _NewOpts, _OldOpts) -> ?WARNING_MSG("module ~s is reloaded, but new configuration will take " @@ -103,39 +109,33 @@ reload(_Host, _NewOpts, _OldOpts) -> depends(_Host, _Opts) -> []. %% client part -s2s_out_stream_init({ok, #{server_host := ServerHost, mod := Mod} = State}, Opts) -> - % case proplists:get_value(resume, Opts) of - % OldState when OldState /= undefined -> - % #{mgmt_stanzas_in := H, mgmt_stanzas_out := NumStanzasOut, - % mgmt_queue := Queue, mgmt_privid := Id} = OldState, - % State1 = State#{mgmt_queue => Queue, - % mgmt_stanzas_out => NumStanzasOut, - % mgmt_stanzas_in => H, - % mgmt_privid => Id}, - % {ok, State1#{mgmt_state => resume, mgmt_old_session => OldState}}; - % _ -> - {ok, State#{mgmt_state => inactive, - mgmt_timeout => get_resume_timeout(ServerHost), - mgmt_queue_type => get_queue_type(ServerHost), - mgmt_max_queue => get_max_ack_queue(ServerHost), - mgmt_ack_timeout => get_ack_timeout(ServerHost), - % mgmt_unacked_stanzas => get_max_unacked_stanzas(ServerHost), - % mgmt_connection_timeout => get_connection_timeout(ServerHost), - mgmt_stanzas_in => 0, - mgmt_stanzas_out => 0, - mgmt_stanzas_req => 0}}; - % end; +s2s_out_stream_init({ok, #{server_host := ServerHost} = State}, Opts) -> + + State1 = State#{mgmt_timeout => get_resume_timeout(ServerHost), + mgmt_queue_type => get_queue_type(ServerHost), + mgmt_max_queue => get_max_ack_queue(ServerHost), + mgmt_ack_timeout => get_ack_timeout(ServerHost), + mgmt_connection_timeout => get_connection_timeout(ServerHost), + mgmt_stanzas_out => 0, + mgmt_stanzas_req => 0}, + + case proplists:get_value(resume, Opts) of + OldState when OldState /= undefined -> + {ok, State1#{mgmt_old_state => OldState, mgmt_state => resume}}; + _ -> + {ok, State1#{mgmt_state => inactive}} + end; s2s_out_stream_init(Acc, _Opts) -> Acc. s2s_out_stream_features(#{mgmt_state := MgmtState, - mgmt_timeout := Resume, - mgmt_queue_type := QueueType} = State, + mgmt_timeout := Resume} = State, #stream_features{sub_els = SubEls}) -> case check_stream_mgmt_support(SubEls) of Xmlns when Xmlns == ?NS_STREAM_MGMT_2; Xmlns == ?NS_STREAM_MGMT_3 -> - % case MgmtState of - % inactive -> + case MgmtState of + inactive -> + #{mgmt_queue_type := QueueType} = State, State1 = if Resume > 0 -> send(State, #sm_enable{xmlns = Xmlns, @@ -147,21 +147,21 @@ s2s_out_stream_features(#{mgmt_state := MgmtState, State1#{mgmt_xmlns => Xmlns, mgmt_state => wait_for_enabled, mgmt_queue => p1_queue:new(QueueType)}; - % _ -> % resume - % #{mgmt_stanzas_in := H, mgmt_privid := Id} = State, - % State1 = send(State, #sm_resume{h = H, previd = Id, xmlns = Xmlns}), - % State1#{mgmt_xmlns => Xmlns, mgmt_state => pending} - % end; + _ -> + #{mgmt_old_state := #{mgmt_previd := Id}} = State, + State1 = send(State, #sm_resume{h = 0, xmlns = Xmlns, previd = Id}), + State1#{mgmt_xmlns => Xmlns, mgmt_state => pending} + end; _ -> State end; s2s_out_stream_features(State, _) -> State. -% s2s_out_established(#{mgmt_state := resume} = State) -> -% #{mgmt_stanzas_in := H, mgmt_privid := Id} = State, -% State1 = send(State, #sm_resume{h = H, previd = Id, xmlns = ?NS_STREAM_MGMT_3}), -% State1#{mgmt_xmlns => ?NS_STREAM_MGMT_3, mgmt_state => pending}; +s2s_out_established(#{mgmt_state := resume} = State) -> + #{mgmt_old_state := #{mgmt_previd := Id}} = State, + State1 = send(State, #sm_resume{h = 0, xmlns = ?NS_STREAM_MGMT_3, previd = Id}), + State1#{mgmt_xmlns => ?NS_STREAM_MGMT_3, mgmt_state => pending}; s2s_out_established(#{mgmt_timeout := Resume, mgmt_queue_type := QueueType} = State) -> Xmlns = ?NS_STREAM_MGMT_3, @@ -180,31 +180,34 @@ s2s_out_established(#{mgmt_timeout := Resume, s2s_out_established(State) -> State. +s2s_out_packet(#{mgmt_state := pending} = State, #sm_resumed{} = Pkt) -> + {stop, handle_resumed(Pkt, State)}; s2s_out_packet(#{mgmt_state := MgmtState} = State, Pkt) when ?is_sm_packet(Pkt) -> if MgmtState == active -> {stop, perform_stream_mgmt(Pkt, State)}; - MgmtState == wait_for_enabled -> % может быть и в других состояниях можно + MgmtState == wait_for_enabled -> {stop, negotiate_stream_mgmt(Pkt, State)}; true -> {stop, State} end; -s2s_out_packet(State, Pkt) -> - update_num_stanzas_in(State, Pkt). +s2s_out_packet(State, _Pkt) -> + State. -s2s_handle_send(#{mgmt_state := MgmtState, lang := Lang} = State, Pkt, SendResult) - when MgmtState == active; MgmtState == wait_for_enabled -> +s2s_out_handle_send(#{mgmt_state := MgmtState, lang := Lang} = State, Pkt, SendResult) + when MgmtState == active; + MgmtState == wait_for_enabled -> case Pkt of _ when ?is_stanza(Pkt) -> Meta = xmpp:get_meta(Pkt), case maps:get(mgmt_is_resent, Meta, false) of false -> case mod_stream_mgmt:mgmt_queue_add(State, Pkt) of - #{mgmt_max_queue := exceeded} = State1 -> - Err = xmpp:serr_policy_violation( - <<"Too many unacked stanzas">>, Lang), - send(State1, Err); - State1 when MgmtState /= wait_for_enabled, SendResult == ok -> + % #{mgmt_max_queue := exceeded} = State1 -> + % Err = xmpp:serr_policy_violation( + % <<"Too many unacked stanzas">>, Lang), + % send(State1, Err); + State1 when MgmtState == active, SendResult == ok -> send_rack(State1); State1 -> State1 @@ -215,25 +218,82 @@ s2s_handle_send(#{mgmt_state := MgmtState, lang := Lang} = State, Pkt, SendResul _ -> State end; -s2s_handle_send(State, _, _) -> +s2s_out_handle_send(State, _, _) -> State. -s2s_handle_info(#{mgmt_ack_timer := TRef, remote_server := RServer, +s2s_out_handle_info(#{mgmt_ack_timer := TRef, remote_server := RServer, mod := Mod} = State, {timeout, TRef, ack_timeout}) -> ?DEBUG("Timed out waiting for stream management " "acknowledgement of ~s", [RServer]), - Mod:stop(State), - {stop, State}; -s2s_handle_info(State, _) -> + Mod:stop(State); +s2s_out_handle_info(#{mgmt_state := resume, + remote_server := RServer, mod := Mod} = State, + {timeout, _TRef, connection_timeout}) -> + ?DEBUG("Timed out waiting for connection " + "establishment for resumption previous session with ~s", [RServer]), + Mod:stop(State#{mgmt_state => timeout}); +s2s_out_handle_info(State, _) -> State. s2s_out_handle_recv(#{mgmt_state := wait_for_enabled, remote_server := RServer} = State, _El, #sm_failed{}) -> ?DEBUG("Remote server ~s can't enable stream management", [RServer]), State#{mgmt_state => inactive}; +s2s_out_handle_recv(#{mgmt_state := pending, + remote_server := RServer, + mgmt_timeout := Resume, + mgmt_xmlns := Xmlns, + mgmt_queue_type := QueueType, + mgmt_old_state := OldState} = State, _El, #sm_failed{}) -> + ?DEBUG("Remote server ~s can't resume previous session", [RServer]), + #{mgmt_queue := Queue} = OldState, + State1 = State#{mgmt_state => wait_for_enabled, + mgmt_queue => p1_queue:new(QueueType)}, + State2 = maps:remove(mgmt_old_state, State1), + State3 = + if Resume > 0 -> + send(State2, #sm_enable{xmlns = Xmlns, + resume = true, + max = Resume}); + true -> + send(State2, #sm_enable{xmlns = Xmlns}) + end, + resend_unacked_stanzas(State3, Queue); +s2s_out_handle_recv(#{lang := Lang} = State, El, {error, Why}) -> % для сервера тоже добавить + Xmlns = xmpp:get_ns(El), + if Xmlns == ?NS_STREAM_MGMT_2; Xmlns == ?NS_STREAM_MGMT_3 -> + Txt = xmpp:io_format_error(Why), + Err = #sm_failed{reason = 'bad-request', + text = xmpp:mk_text(Txt, Lang), + xmlns = Xmlns}, + send(State, Err); + true -> + State + end; s2s_out_handle_recv(State, _El, _Pkt) -> State. +% check reason ? +s2s_out_closed(#{mgmt_state := resume} = State, _) -> + {stop, transition_to_resume(State#{stream_state => connecting})}; +s2s_out_closed(State, _) -> + State. + +s2s_out_terminate(#{mgmt_state := active} = State, _Reason) -> + transition_to_resume(State); +s2s_out_terminate(#{mgmt_state := timeout, + mgmt_old_state := OldState} = State, _Reason) -> + #{mgmt_queue := Queue} = OldState, + bounce_errors(State, Queue), + State; +s2s_out_terminate(#{mgmt_state := pending, + mgmt_old_state := OldState} = State, _Reason) -> + #{mgmt_queue := Queue} = OldState, + route_unacked_stanzas(State, Queue), + State; +s2s_out_terminate(State, _Reason) -> + State. + %% client part %% server part @@ -244,11 +304,7 @@ s2s_in_stream_init({ok, #{server_host := Host} = State}, _Opts) -> {ok, State#{mgmt_state => inactive, mgmt_timeout => Timeout, mgmt_max_timeout => MaxTimeout, - mgmt_queue_type => get_queue_type(Host), - mgmt_ack_timeout => get_ack_timeout(Host), - mgmt_stanzas_in => 0, - mgmt_stanzas_out => 0, - mgmt_stanzas_req => 0}}; + mgmt_stanzas_in => 0}}; s2s_in_stream_init(State, _Opts) -> State. @@ -259,95 +315,56 @@ s2s_in_stream_features(Acc, _Host) -> s2s_in_unauthenticated_packet(State, Pkt) when ?is_sm_packet(Pkt) -> Err = #sm_failed{reason = 'unexpected-request', xmlns = ?NS_STREAM_MGMT_3}, {stop, send(State, Err)}; -s2s_in_unauthenticated_packet(State, Pkt) -> +s2s_in_unauthenticated_packet(State, _Pkt) -> State. +s2s_in_authenticated_packet(#{mgmt_state := inactive} = State, #sm_resume{} = Pkt) -> + State1 = handle_resume(State, Pkt), + {stop, State1}; s2s_in_authenticated_packet(#{mgmt_state := MgmtState} = State, Pkt) when ?is_sm_packet(Pkt) -> - if MgmtState == active -> + if MgmtState == active; MgmtState == pending -> {stop, perform_stream_mgmt(Pkt, State)}; - MgmtState == inactive -> - {stop, server_negotiate_stream_mgmt(Pkt, State)}; true -> - {stop, State} + {stop, negotiate_stream_mgmt(Pkt, State)} end; s2s_in_authenticated_packet(State, Pkt) -> - update_num_stanzas_in(State, Pkt). - + mod_stream_mgmt:update_num_stanzas_in(State, Pkt). + +s2s_in_handle_call(#{mgmt_state := pending, mod:= Mod, + remote_server := RServer, unique_id := UniqueId} = State, + {resume_session, RServer, UniqueId}, From) -> + Mod:reply(From, {resume, State}), + {stop, State#{mgmt_state => resumed}}; +s2s_in_handle_call(#{mod := Mod} = State, {resume_session, _, _}, From) -> + Mod:reply(From, error), + {stop, State}; +s2s_in_handle_call(State, _Call, _From) -> + State. +s2s_in_handle_info(#{mgmt_state := pending, remote_server := RServer, + mod := Mod} = State, {timeout, _, pending_timeout}) -> + ?DEBUG("Timed out waiting for resumption of stream for ~s", [RServer]), + Mod:stop(State); +s2s_in_handle_info(State, _Msg) -> + State. + +% Should we filter reasons? +s2s_in_closed(#{mgmt_state := active} = State, _Reason) -> + {stop, transition_to_pending(State)}; +s2s_in_closed(State, _Reason) -> + State. -%% server part +% it isn't importand: we can delete it and delete from ejabberd_s2s_in module +% ejabberd_hooks:run_fold(..) +s2s_in_terminate(#{mgmt_state := resumed, + remote_server := RServer} = State, _Reason) -> + ?INFO_MSG("Closing former stream of resumed session for ~s", [RServer]), + State; +s2s_in_terminate(State, _Reason) -> + State. -% s2s_out_packet(#{mgmt_state := pending} = State, #sm_resumed{} = Pkt) -> -% {stop, handle_resumed(Pkt, State)}; -% s2s_out_packet(#{mgmt_state := MgmtState} = State, Pkt) -% when ?is_sm_packet(Pkt) -> -% if MgmtState == active; MgmtState == pending -> -% {stop, perform_stream_mgmt(Pkt, State)}; -% MgmtState == wait_for_enabled -> -% {stop, negotiate_stream_mgmt(Pkt, State)}; -% true -> -% {stop, State} -% end; -% s2s_out_packet(State, Pkt) -> -% update_num_stanzas_in(State, Pkt). - -% s2s_out_handle_recv(#{mgmt_state := wait_for_enabled, -% remote_server := RServer} = State, _El, #sm_failed{}) -> -% ?DEBUG("Remote server ~s can't enable stream management", [RServer]), -% State#{mgmt_state => inactive}; -% s2s_out_handle_recv(#{mgmt_state := pending, -% remote_server := RServer, -% mod := Mod} = State, _El, #sm_failed{}) -> -% ?DEBUG("Remote server ~s can't resume previous session", [RServer]), -% Mod:stop(State); -% s2s_out_handle_recv(#{lang := Lang} = State, El, {error, Why}) -> -% Xmlns = xmpp:get_ns(El), -% if Xmlns == ?NS_STREAM_MGMT_2; Xmlns == ?NS_STREAM_MGMT_3 -> -% Txt = xmpp:io_format_error(Why), -% Err = #sm_failed{reason = 'bad-request', -% text = xmpp:mk_text(Txt, Lang), -% xmlns = Xmlns}, -% send(State, Err); -% true -> -% State -% end; -% s2s_out_handle_recv(State, El, Pkt) -> -% State. - - -% s2s_out_handle_info(#{mgmt_ack_timer := TRef, remote_server := RServer, -% mod := Mod} = State, {timeout, TRef, ack_timeout}) -> -% ?DEBUG("Timed out waiting for stream management " -% "acknowledgement of ~s", [RServer]), -% Mod:stop(State); -% s2s_out_handle_info(#{mgmt_state := resume, -% remote_server := RServer, mod := Mod} = State, -% {timeout, TRef, connection_timeout}) -> -% ?DEBUG("Timed out waiting for connection " -% "establishment with ~s for resumption", [RServer]), -% Mod:stop(State), -% State; -% s2s_out_handle_info(State, _) -> -% State. - -% s2s_out_closed(#{mgmt_state := resume, mod := Mod} = State, _) -> -% {stop, transition_to_resume(State#{stream_state => connecting})}; -% s2s_out_closed(State, _) -> -% State. - -% % terminate - Mod:stop - -% s2s_out_terminate(#{mgmt_state := active} = State, _Reason) -> -% transition_to_resume(State); -% s2s_out_terminate(#{mgmt_state := resume} = State, _Reason) -> -% bounce_errors(State), -% State; -% % в данном состоянии что делать? -% s2s_out_terminate(#{mgmt_state := pending} = State, _Reason) -> -% State; -% s2s_out_terminate(State, _Reason) -> -% State. +%% server part end %%%============================================================================= @@ -376,15 +393,25 @@ check_stream_mgmt_support([El | Els], Res) -> end; check_stream_mgmt_support([], Res) -> Res. --spec server_negotiate_stream_mgmt(xmpp_element(), state()) -> state(). -server_negotiate_stream_mgmt(Pkt, State) -> - Xmlns = xmpp:get_ns(Pkt), +-spec negotiate_stream_mgmt(xmpp_element(), state()) -> state(). +negotiate_stream_mgmt(Pkt, State) -> + {ServerPart, Xmlns} = + case State of + #{mgmt_xmlns := MgmtXmlns} -> + {false, MgmtXmlns}; + _ -> + {true, xmpp:get_ns(Pkt)} + end, + case Pkt of - #sm_enable{} -> + #sm_enable{} when ServerPart -> handle_enable(State#{mgmt_xmlns => Xmlns}, Pkt); + #sm_enabled{} when not ServerPart -> + handle_enabled(State, Pkt); _ when is_record(Pkt, sm_a); is_record(Pkt, sm_r); - is_record(Pkt, sm_resume) -> + (is_record(Pkt, sm_resume) andalso ServerPart); + (is_record(Pkt, sm_resumed) andalso not ServerPart) -> Err = #sm_failed{reason = 'unexpected-request', xmlns = Xmlns}, send(State, Err); _ -> @@ -392,8 +419,6 @@ server_negotiate_stream_mgmt(Pkt, State) -> send(State, Err) end. -% mgmt_state := active, pending and pkt is sm packet -% ask about error messages for client part -spec perform_stream_mgmt(xmpp_element(), state()) -> state(). perform_stream_mgmt(Pkt, #{mgmt_xmlns := Xmlns} = State) -> case xmpp:get_ns(Pkt) of @@ -410,26 +435,10 @@ perform_stream_mgmt(Pkt, #{mgmt_xmlns := Xmlns} = State) -> send(State, #sm_failed{reason = 'unsupported-version', xmlns = Xmlns}) end. --spec negotiate_stream_mgmt(xmpp_element(), state()) -> state(). -negotiate_stream_mgmt(Pkt, #{mgmt_xmlns := Xmlns} = State) -> - case Pkt of - #sm_enabled{} -> - handle_enabled(State, Pkt); - _ when is_record(Pkt, sm_a); - is_record(Pkt, sm_r); - is_record(Pkt, sm_resumed) -> - Err = #sm_failed{reason = 'unexpected-request', xmlns = Xmlns}, - send(State, Err); - _ -> - Err = #sm_failed{reason = 'bad-request', xmlns = Xmlns}, - send(State, Err) - end. - handle_enable(#{remote_server := RServer, mgmt_timeout := DefaultTimeout, mgmt_max_timeout := MaxTimeout, - mgmt_xmlns := Xmlns, - mgmt_queue_type := QueueType} = State, + mgmt_xmlns := Xmlns} = State, #sm_enable{resume = Resume, max = Max}) -> Timeout = if Resume == false -> @@ -450,8 +459,7 @@ handle_enable(#{remote_server := RServer, #sm_enabled{xmlns = Xmlns} end, State1 = State#{mgmt_state => active, - mgmt_timeout => Timeout, - mgmt_queue => p1_queue:new(QueueType)}, + mgmt_timeout => Timeout}, send(State1, Res). -spec handle_enabled(state(), sm_enabled()) -> state(). @@ -469,20 +477,20 @@ handle_enabled(#{remote_server := RServer, State1 = if Timeout > 0 -> ?INFO_MSG("Stream management with " "resumption enabled for ~s", [RServer]), - State#{mgmt_privid => Id}; + State#{mgmt_state => active, + mgmt_previd => Id, + mgmt_timeout => Timeout}; true -> ?INFO_MSG("Stream management enabled for ~s", [RServer]), - State + State#{mgmt_state => active, mgmt_timeout => Timeout} end, - State2 = - case not p1_queue:is_empty(Queue) of - true -> - send_rack(State1); - _ -> - State1 - end, - State2#{mgmt_state => active, mgmt_timeout => Timeout}. + case not p1_queue:is_empty(Queue) of + true -> + send_rack(State1); + _ -> + State1 + end. -spec handle_r(state()) -> state(). handle_r(#{mgmt_stanzas_in := H, @@ -490,94 +498,166 @@ handle_r(#{mgmt_stanzas_in := H, send(State, #sm_a{h = H, xmlns = Xmlns}). -spec handle_a(state(), sm_a()) -> state(). -handle_a(#{mgmt_stanzas_out := NumStanzasOut, - remote_server := RServer} = State, #sm_a{h = H}) -> +handle_a(State, #sm_a{h = H}) -> State1 = check_h_attribute(State, H), resend_rack(State1). -% What should we encode? -spec make_resume_id(state()) -> binary(). -make_resume_id(#{owner := Owner, remote_server := RServer}) -> - misc:term_to_base64({RServer, Owner}). - - -% -spec handle_resumed(sm_resumed(), state()) -> state(). -% handle_resumed(#sm_resumed{h = H, previd = Id}, -% #{mgmt_xmlns := Xmlns, remote_server := RServer} = State) -> -% State1 = check_h_attribute(State, H), -% State2 = resend_unacked_stanzas(State1), -% State3 = send(State2, #sm_r{xmlns = Xmlns}), -% ?DEBUG("Resumed session with ~s", [RServer]), -% {ok, State3}. - -% -spec resend_unacked_stanzas(state()) -> state(). -% resend_unacked_stanzas(#{mgmt_state := MgmtState, -% mgmt_queue := Queue, -% remote_server := RServer} = State) -% when MgmtState == pending andalso ?qlen(Queue) > 0 -> -% p1_queue:foldl( -% fun({_, Time, Pkt}, AccState) -> -% NewPkt = mod_stream_mgmt:add_resent_delay_info(AccState, Pkt, Time), -% send(AccState, xmpp:put_meta(NewPkt, mgmt_is_resent, true)) -% end, State, Queue); -% resend_unacked_stanzas(State) -> -% State. - - -% -spec transition_to_resume(state()) -> state(). -% transition_to_resume(#{mgmt_state := active, mod := Mod, -% mgmt_timeout := 0} = State) -> -% State; % route messages from queue -% transition_to_resume(#{mgmt_state := active, mod := Mod, -% remote_server := RServer,server_host := Server, -% mgmt_connection_timeout := Timeout} = State) -> -% State1 = mod_stream_mgmt:cancel_ack_timer(State), -% ?DEBUG("Try to connect to remote server ~s", [RServer]), -% {ok, Pid} = -% ejabberd_s2s:start_connection(jid:make(Server), -% jid:make(RServer), -% [{resume, State1}]), - -% erlang:start_timer(Timeout, Pid, connection_timeout), - -% State1; -% transition_to_resume(#{mgmt_state := resume, -% mod := Mod, -% remote_server := RServer} = State) -> -% Mod:connect(self()), -% State; -% transition_to_resume(State) -> -% State. - -% % %% fix: filter some messages - -% bounce_errors(#{mgmt_state := resume, -% mgmt_queue := Queue} = State) -% when ?qlen(Queue) > 0 -> -% p1_queue:foreach( -% fun({_, _, Pkt}) -> -% Error = xmpp:err_remote_server_timeout(), -% ejabberd_router:route_error(Pkt, Error) -% end, Queue); -% bounce_errors(State) -> -% ok. - -% % -spec route_unacked_stanzas(state()) -> state(). -% % route_unacked_stanzas(#{mgmt_queue := Queue, -% % mgmt_state := pending, -% % mgmt_xmlns := Xmlns} = State) -% % when ?qlen(Queue) > 0 -> -% % State1 = send(State, #sm_enable{xmlns = Xmlns}), -% % p1_queue:foldl( -% % fun({_, _, Pkt}, AccState) -> -% % % set is_resend = true like in mod_stream_mgmt -% % send(AccState, Pkt) -% % end, State1, Queue); -% % route_unacked_stanzas(_State) -> -% % ok. +make_resume_id(#{remote_server := RServer, unique_id := UniqueId}) -> + misc:term_to_base64({RServer, UniqueId}). + +-spec transition_to_resume(state()) -> state(). +transition_to_resume(#{mgmt_state := active, + mgmt_queue := Queue, + mgmt_timeout := 0} = State) -> + route_unacked_stanzas(State, Queue), + State; +transition_to_resume(#{mgmt_state := active, + remote_server := RServer, + server_host := Server, + mgmt_connection_timeout := Timeout} = State) -> + State1 = mod_stream_mgmt:cancel_ack_timer(State), + ?DEBUG("Try to connect to remote server ~s", [RServer]), + {ok, Pid} = + ejabberd_s2s:start_connection(jid:make(Server), + jid:make(RServer), + [{resume, State1}]), + erlang:start_timer(Timeout, Pid, connection_timeout), + State1; +transition_to_resume(#{mgmt_state := resume, mod := Mod} = State) -> + Mod:connect(self()), + State; +transition_to_resume(State) -> + State. -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% todo: import from mod_stream_mgmt.erl +-spec transition_to_pending(state()) -> state(). +transition_to_pending(#{mgmt_state := active, mod := Mod, + mgmt_timeout := 0} = State) -> + Mod:stop(State); +transition_to_pending(#{mgmt_state := active, + remote_server := RServer, mgmt_timeout := Timeout} = State) -> + ?INFO_MSG("Waiting for resumption of stream for ~s", [RServer]), + erlang:start_timer(timer:seconds(Timeout), self(), pending_timeout), + State#{mgmt_state => pending}; +transition_to_pending(State) -> + State. + +-spec resume_session(pid(), {binary(), integer()}) -> {resume, state()} | error. +resume_session(Pid, {RServer, UniqueId}) -> + ejabberd_s2s_in:call(Pid, {resume_session, RServer, UniqueId}, timer:seconds(15)). + +-spec inherit_session_state({binary(), binary()}, list()) -> {ok, state()}| + {error, binary()}. +inherit_session_state(_ResumeId, []) -> + {error, <<"Previous session PID is dead">>}; +inherit_session_state({RServer, UniqueId} = ResumeId, [{_, Pid, _, _}|Specs]) + when is_pid(Pid) -> + try resume_session(Pid, {RServer, UniqueId}) of + {resume, OldState} -> + ejabberd_s2s_in:stop(Pid), + {ok, OldState}; + error -> + inherit_session_state(ResumeId, Specs) + catch + _:_ -> + inherit_session_state(ResumeId, Specs) + end; +inherit_session_state(ResumeId, [_H|L] ) -> + inherit_session_state(ResumeId, L). + +handle_resume(#{lang := Lang, remote_server := RServer} = State, + #sm_resume{previd = ResumeId, xmlns = Xmlns}) -> + case misc:base64_to_term(ResumeId) of + {term, {RServer, UniqueId}} -> + case inherit_session_state({RServer, UniqueId}, + supervisor:which_children(ejabberd_s2s_in_sup)) of + {ok, OldState} -> + #{mgmt_stanzas_in := H, + mgmt_timeout := Timeout, + mgmt_xmlns := AttrXmlns} = OldState, + + State1 = State#{mgmt_state => active, + mgmt_stanzas_in => H, + mgmt_timeout => Timeout, + mgmt_xmlns => AttrXmlns, + unique_id => UniqueId}, + + State2 = send(State1, #sm_resumed{previd = ResumeId, + h = H, + xmlns = AttrXmlns}), + ?INFO_MSG("Resumed session for ~s", [RServer]), + State2; + {error, Msg} -> + ?INFO_MSG("Cannot resume session for ~s: ~s", [RServer, Msg]), + Err = #sm_failed{reason = 'item-not-found', + text = xmpp:mk_text(Msg, Lang), + xmlns = Xmlns}, + send(State, Err) + end; + _ -> + Msg = <<"Invalid 'previd' value">>, + ?INFO_MSG("Cannot resume session for ~s: ~s", [RServer, Msg]), + Err = #sm_failed{reason = 'item-not-found', + text = xmpp:mk_text(Msg, Lang), + xmlns = Xmlns}, + send(State, Err) + end. + +-spec handle_resumed(sm_resumed(), state()) -> state(). +handle_resumed(#sm_resumed{h = H, previd = _Id}, + #{remote_server := RServer, mgmt_old_state := OldState} = State) -> + ResumedState = copy_state(OldState, State), + #{mgmt_xmlns := Xmlns, mgmt_queue := Queue} = ResumedState, + State1 = check_h_attribute(ResumedState, H), + State2 = resend_unacked_stanzas(State1#{mgmt_state => active}, Queue), + ?DEBUG("Resumed session for ~s", [RServer]), + send(State2, #sm_r{xmlns = Xmlns}). + +resend_unacked_stanzas(#{remote_server := RServer} = State, Queue) + when ?qlen(Queue) > 0 -> + ?DEBUG("Resending ~B unacknowledged stanza(s) to ~s", + [p1_queue:len(Queue), RServer]), + p1_queue:foldl( + fun({_, Time, Pkt}, AccState) -> + NewPkt = add_resent_delay_info(AccState, Pkt, Time), + send(AccState, xmpp:put_meta(NewPkt, mgmt_is_resent, true)) + end, State, Queue); +resend_unacked_stanzas(State, _) -> + State. + +route_unacked_stanzas(#{remote_server := RServer} = State, Queue) + when ?qlen(Queue) > 0 -> + ?DEBUG("Re-rout ~B unacknowledged stanza(s) to ~s", + [p1_queue:len(Queue), RServer]), + p1_queue:foreach( + fun({_, Time, Pkt}) -> + NewPkt = add_resent_delay_info(State, Pkt, Time), + ejabberd_router:route(xmpp:put_meta(NewPkt, mgmt_is_resent, true)) + end, Queue); +route_unacked_stanzas(_State, _Queue) -> + ok. + +bounce_errors(#{mgmt_state := resume}, Queue) + when ?qlen(Queue) > 0 -> + p1_queue:foreach( + fun({_, _, Pkt}) -> + Error = xmpp:err_remote_server_timeout(), + ejabberd_router:route_error(Pkt, Error) + end, Queue); +bounce_errors(_State, _) -> + ok. + +-spec copy_state(state(), state()) -> state(). +copy_state(#{mgmt_timeout := Timeout, + mgmt_xmlns := Xmlns, + mgmt_queue := Queue, + mgmt_stanzas_out := NumStanzasOut, + mgmt_previd := Id} = _OldState, NewState) -> + NewState#{mgmt_timeout => Timeout, + mgmt_xmlns => Xmlns, + mgmt_queue => Queue, + mgmt_stanzas_out => NumStanzasOut, + mgmt_previd => Id}. -spec check_h_attribute(state(), non_neg_integer()) -> state(). check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, @@ -592,6 +672,15 @@ check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, "stanzas", [RServer, H, NumStanzasOut]), mod_stream_mgmt:mgmt_queue_drop(State, H). +-spec add_resent_delay_info(state(), stanza(), erlang:timestamp()) -> stanza(). +add_resent_delay_info(#{server_host := LServer}, El, Time) -> + xmpp_util:add_delay_info(El, jid:make(LServer), Time, <<"Resent">>); +add_resent_delay_info(_State, El, _Time) -> + El. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% todo: import from mod_stream_mgmt.erl + -spec send(state(), xmpp_element()) -> state(). send(#{mod := Mod} = State, Pkt) -> Mod:send(State, Pkt). @@ -617,22 +706,6 @@ resend_rack(#{mgmt_ack_timer := _, resend_rack(State) -> State. --spec update_num_stanzas_in(state(), xmpp_element()) -> state(). -update_num_stanzas_in(#{mgmt_state := MgmtState, - mgmt_stanzas_in := NumStanzasIn} = State, El) - when MgmtState == active -> %; MgmtState == pending -> - NewNum = case {xmpp:is_stanza(El), NumStanzasIn} of - {true, 4294967295} -> - 0; - {true, Num} -> - Num + 1; - {false, Num} -> - Num - end, - State#{mgmt_stanzas_in => NewNum}; -update_num_stanzas_in(State, _El) -> - State. - %%%============================================================================= %%% Configuration processing %%%============================================================================= @@ -657,21 +730,21 @@ get_max_ack_queue(Host) -> gen_mod:get_module_opt(Host, ?MODULE, max_ack_queue, 1000). get_ack_timeout(Host) -> - case gen_mod:get_module_opt(Host, ?MODULE, ack_timeout, 10) of % change default + case gen_mod:get_module_opt(Host, ?MODULE, ack_timeout, 60) of % change default infinity -> infinity; T -> timer:seconds(T) end. -get_max_unacked_stanzas(Host) -> - gen_mod:get_module_opt(Host, ?MODULE, max_unacked_stanzas, 0). +% get_max_unacked_stanzas(Host) -> +% gen_mod:get_module_opt(Host, ?MODULE, max_unacked_stanzas, 0). get_connection_timeout(Host) -> gen_mod:get_module_opt(Host, ?MODULE, connection_timeout, 120000). mod_opt_type(connection_timeout) -> fun(I) when is_integer(I), I >= 0 -> I end; -mod_opt_type(max_unacked_stanzas) -> - fun(I) when is_integer(I), I >= 0 -> I end; +% mod_opt_type(max_unacked_stanzas) -> +% fun(I) when is_integer(I), I >= 0 -> I end; mod_opt_type(max_ack_queue) -> fun(I) when is_integer(I), I > 0 -> I; (infinity) -> infinity @@ -689,4 +762,4 @@ mod_opt_type(queue_type) -> (ram) -> ram end; mod_opt_type(_) -> [max_ack_queue, ack_timeout, resume_timeout, max_resume_timeout, - queue_type, max_unacked_stanzas, connection_timeout]. + queue_type, connection_timeout]. %max_unacked_stanzas, From b07fbd19357f06116668ad93b48f646b28f99b45 Mon Sep 17 00:00:00 2001 From: amuhar Date: Sat, 12 Aug 2017 17:35:22 +0300 Subject: [PATCH 12/25] create connection for resumption with resume/3 function --- src/ejabberd_s2s_in.erl | 12 ++--- src/mod_stream_mgmt_s2s.erl | 98 +++++++++++++++++++++---------------- 2 files changed, 62 insertions(+), 48 deletions(-) diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl index 424bf78afa7..30aa6d74fca 100644 --- a/src/ejabberd_s2s_in.erl +++ b/src/ejabberd_s2s_in.erl @@ -176,14 +176,16 @@ handle_stream_start(_StreamStart, #{lserver := LServer} = State) -> send(State, xmpp:serr_host_unknown()); true -> ServerHost = ejabberd_router:host_of_route(LServer), - State#{server_host => ServerHost} + UniqueId = p1_time_compat:unique_integer(), + State1 = State#{server_host => ServerHost, + unique_id => UniqueId}, % for xep-0198 s2s + ejabberd_hooks:run_fold(s2s_in_stream_started, ServerHost, State1, []) end. handle_stream_end(Reason, #{server_host := LServer} = State) -> State1 = State#{stop_reason => Reason}, ejabberd_hooks:run_fold(s2s_in_closed, LServer, State1, [Reason]). - -% unique_id можно заменить в будущем + handle_stream_established(State) -> set_idle_timeout(State#{established => true}). @@ -268,7 +270,6 @@ init([State, Opts]) -> false -> [compression_none | TLSOpts1]; true -> TLSOpts1 end, - UniqueId = p1_time_compat:unique_integer(), State1 = State#{tls_options => TLSOpts2, auth_domains => sets:new(), xmlns => ?NS_SERVER, @@ -277,8 +278,7 @@ init([State, Opts]) -> lserver => ?MYNAME, server_host => ?MYNAME, established => false, - shaper => Shaper, - unique_id => UniqueId}, % for xep-0198 s2s + shaper => Shaper}, ejabberd_hooks:run_fold(s2s_in_init, {ok, State1}, [Opts]). handle_call(Request, From, #{server_host := LServer} = State) -> diff --git a/src/mod_stream_mgmt_s2s.erl b/src/mod_stream_mgmt_s2s.erl index 98a1ef3bc5b..fd97d39faaa 100644 --- a/src/mod_stream_mgmt_s2s.erl +++ b/src/mod_stream_mgmt_s2s.erl @@ -11,7 +11,7 @@ s2s_out_handle_info/2, s2s_out_closed/2, s2s_out_terminate/2, s2s_out_established/1]). %% server part hooks --export([s2s_in_stream_init/2, s2s_in_stream_features/2, +-export([s2s_in_stream_started/1, s2s_in_stream_features/2, s2s_in_unauthenticated_packet/2, s2s_in_authenticated_packet/2, s2s_in_handle_call/3, s2s_in_handle_info/2, s2s_in_closed/2, s2s_in_terminate/2]). @@ -28,6 +28,11 @@ is_record(Pkt, sm_a) or is_record(Pkt, sm_r)). +% replace to separate file + +-record(s2s, {fromto = {<<"">>, <<"">>} :: {binary(), binary()} | '_', + pid = self() :: pid() | '_' | '$1'}). + -type state() :: map(). %%%============================================================================= @@ -35,8 +40,8 @@ %%%============================================================================= start(Host, _Opts) -> ejabberd_hooks:add(s2s_out_init, Host, ?MODULE, s2s_out_stream_init, 50), - ejabberd_hooks:add(s2s_out_authenticated_features, - Host, ?MODULE, s2s_out_stream_features, 50), + % ejabberd_hooks:add(s2s_out_authenticated_features, + % Host, ?MODULE, s2s_out_stream_features, 50), ejabberd_hooks:add(s2s_out_packet, Host, ?MODULE, s2s_out_packet, 50), ejabberd_hooks:add(s2s_out_handle_recv, Host, ?MODULE, s2s_out_handle_recv, 50), @@ -51,7 +56,8 @@ start(Host, _Opts) -> ejabberd_hooks:add(s2s_out_established, Host, ?MODULE, s2s_out_established, 50), %% server part - ejabberd_hooks:add(s2s_in_init, ?MODULE, s2s_in_stream_init, 50), + ejabberd_hooks:add(s2s_in_stream_started, + Host, ?MODULE, s2s_in_stream_started, 50), ejabberd_hooks:add(s2s_in_post_auth_features, Host, ?MODULE, s2s_in_stream_features, 50), ejabberd_hooks:add(s2s_in_unauthenticated_packet, @@ -86,9 +92,10 @@ stop(Host) -> ejabberd_hooks:delete(s2s_out_established, Host, ?MODULE, s2s_out_established, 50), %% server part + ejabberd_hooks:delete(s2s_in_stream_started, + Host, ?MODULE, s2s_in_stream_started, 50), ejabberd_hooks:delete(s2s_in_post_auth_features, Host, ?MODULE, s2s_in_stream_features, 50), - % ejabberd_hooks:delete(s2s_in_init, ?MODULE, s2s_in_init), ejabberd_hooks:delete(s2s_in_unauthenticated_packet, Host, ?MODULE, s2s_in_unauthenticated_packet, 50), ejabberd_hooks:delete(s2s_in_authenticated_packet, @@ -149,8 +156,8 @@ s2s_out_stream_features(#{mgmt_state := MgmtState, mgmt_queue => p1_queue:new(QueueType)}; _ -> #{mgmt_old_state := #{mgmt_previd := Id}} = State, - State1 = send(State, #sm_resume{h = 0, xmlns = Xmlns, previd = Id}), - State1#{mgmt_xmlns => Xmlns, mgmt_state => pending} + send(State#{mgmt_xmlns => Xmlns, mgmt_state => pending}, + #sm_resume{h = 0, xmlns = Xmlns, previd = Id}) end; _ -> State @@ -181,6 +188,7 @@ s2s_out_established(State) -> State. s2s_out_packet(#{mgmt_state := pending} = State, #sm_resumed{} = Pkt) -> + % нужно исправить {stop, handle_resumed(Pkt, State)}; s2s_out_packet(#{mgmt_state := MgmtState} = State, Pkt) when ?is_sm_packet(Pkt) -> @@ -203,10 +211,10 @@ s2s_out_handle_send(#{mgmt_state := MgmtState, lang := Lang} = State, Pkt, SendR case maps:get(mgmt_is_resent, Meta, false) of false -> case mod_stream_mgmt:mgmt_queue_add(State, Pkt) of - % #{mgmt_max_queue := exceeded} = State1 -> - % Err = xmpp:serr_policy_violation( - % <<"Too many unacked stanzas">>, Lang), - % send(State1, Err); + #{mgmt_max_queue := exceeded} = State1 -> + Err = xmpp:serr_policy_violation( + <<"Too many unacked stanzas">>, Lang), + send(State1, Err); State1 when MgmtState == active, SendResult == ok -> send_rack(State1); State1 -> @@ -259,17 +267,6 @@ s2s_out_handle_recv(#{mgmt_state := pending, send(State2, #sm_enable{xmlns = Xmlns}) end, resend_unacked_stanzas(State3, Queue); -s2s_out_handle_recv(#{lang := Lang} = State, El, {error, Why}) -> % для сервера тоже добавить - Xmlns = xmpp:get_ns(El), - if Xmlns == ?NS_STREAM_MGMT_2; Xmlns == ?NS_STREAM_MGMT_3 -> - Txt = xmpp:io_format_error(Why), - Err = #sm_failed{reason = 'bad-request', - text = xmpp:mk_text(Txt, Lang), - xmlns = Xmlns}, - send(State, Err); - true -> - State - end; s2s_out_handle_recv(State, _El, _Pkt) -> State. @@ -298,14 +295,14 @@ s2s_out_terminate(State, _Reason) -> %% server part -s2s_in_stream_init({ok, #{server_host := Host} = State}, _Opts) -> +s2s_in_stream_started(#{server_host := Host} = State) -> Timeout = get_resume_timeout(Host), MaxTimeout = get_max_resume_timeout(Host, Timeout), - {ok, State#{mgmt_state => inactive, - mgmt_timeout => Timeout, - mgmt_max_timeout => MaxTimeout, - mgmt_stanzas_in => 0}}; -s2s_in_stream_init(State, _Opts) -> + State#{mgmt_state => inactive, + mgmt_timeout => Timeout, + mgmt_max_timeout => MaxTimeout, + mgmt_stanzas_in => 0}; +s2s_in_stream_started(State) -> State. s2s_in_stream_features(Acc, _Host) -> @@ -313,14 +310,15 @@ s2s_in_stream_features(Acc, _Host) -> #feature_sm{xmlns = ?NS_STREAM_MGMT_3}| Acc]. s2s_in_unauthenticated_packet(State, Pkt) when ?is_sm_packet(Pkt) -> - Err = #sm_failed{reason = 'unexpected-request', xmlns = ?NS_STREAM_MGMT_3}, + Err = #sm_failed{reason = 'unexpected-request', + xmlns = ?NS_STREAM_MGMT_3}, {stop, send(State, Err)}; s2s_in_unauthenticated_packet(State, _Pkt) -> State. -s2s_in_authenticated_packet(#{mgmt_state := inactive} = State, #sm_resume{} = Pkt) -> - State1 = handle_resume(State, Pkt), - {stop, State1}; +s2s_in_authenticated_packet(#{mgmt_state := inactive} = State, + #sm_resume{} = Pkt) -> + {stop, handle_resume(State, Pkt)}; s2s_in_authenticated_packet(#{mgmt_state := MgmtState} = State, Pkt) when ?is_sm_packet(Pkt) -> if MgmtState == active; MgmtState == pending -> @@ -349,13 +347,12 @@ s2s_in_handle_info(#{mgmt_state := pending, remote_server := RServer, s2s_in_handle_info(State, _Msg) -> State. -% Should we filter reasons? s2s_in_closed(#{mgmt_state := active} = State, _Reason) -> {stop, transition_to_pending(State)}; s2s_in_closed(State, _Reason) -> State. -% it isn't importand: we can delete it and delete from ejabberd_s2s_in module +% it isn't important: we can delete it and delete from ejabberd_s2s_in module % ejabberd_hooks:run_fold(..) s2s_in_terminate(#{mgmt_state := resumed, remote_server := RServer} = State, _Reason) -> @@ -513,17 +510,18 @@ transition_to_resume(#{mgmt_state := active, route_unacked_stanzas(State, Queue), State; transition_to_resume(#{mgmt_state := active, - remote_server := RServer, server_host := Server, + remote_server := RServer, mgmt_connection_timeout := Timeout} = State) -> State1 = mod_stream_mgmt:cancel_ack_timer(State), ?DEBUG("Try to connect to remote server ~s", [RServer]), - {ok, Pid} = - ejabberd_s2s:start_connection(jid:make(Server), - jid:make(RServer), - [{resume, State1}]), - erlang:start_timer(Timeout, Pid, connection_timeout), - State1; + case resume(Server, RServer, [{resume, State1}]) of + {ok, Pid} -> + erlang:start_timer(Timeout, Pid, connection_timeout), + State1; + _ -> + State1 + end; transition_to_resume(#{mgmt_state := resume, mod := Mod} = State) -> Mod:connect(self()), State; @@ -542,6 +540,22 @@ transition_to_pending(#{mgmt_state := active, transition_to_pending(State) -> State. +resume(From, To, Opts) -> + {ok, Pid} = ejabberd_s2s_out:start(From, To, Opts), + F = fun() -> + mnesia:write(#s2s{fromto = {From, To}, pid = Pid}), + Pid + end, + TRes = mnesia:transaction(F), + case TRes of + {atomic, Pid} -> + ejabberd_s2s_out:connect(Pid), + {ok, Pid}; + {aborted, _Reason} -> + ejabberd_s2s_out:stop(Pid), + error + end. + -spec resume_session(pid(), {binary(), integer()}) -> {resume, state()} | error. resume_session(Pid, {RServer, UniqueId}) -> ejabberd_s2s_in:call(Pid, {resume_session, RServer, UniqueId}, timer:seconds(15)). @@ -549,7 +563,7 @@ resume_session(Pid, {RServer, UniqueId}) -> -spec inherit_session_state({binary(), binary()}, list()) -> {ok, state()}| {error, binary()}. inherit_session_state(_ResumeId, []) -> - {error, <<"Previous session PID is dead">>}; + {error, <<"Previous session PID not found">>}; inherit_session_state({RServer, UniqueId} = ResumeId, [{_, Pid, _, _}|Specs]) when is_pid(Pid) -> try resume_session(Pid, {RServer, UniqueId}) of From 97f0278d0d9c93f6ea72a7db1d898ad09d084429 Mon Sep 17 00:00:00 2001 From: amuhar Date: Tue, 15 Aug 2017 15:39:57 +0300 Subject: [PATCH 13/25] change bounce_error/2 function --- src/mod_stream_mgmt_s2s.erl | 134 +++++++++++++++++++----------------- 1 file changed, 69 insertions(+), 65 deletions(-) diff --git a/src/mod_stream_mgmt_s2s.erl b/src/mod_stream_mgmt_s2s.erl index fd97d39faaa..d281bc14ad4 100644 --- a/src/mod_stream_mgmt_s2s.erl +++ b/src/mod_stream_mgmt_s2s.erl @@ -75,8 +75,8 @@ start(Host, _Opts) -> stop(Host) -> ejabberd_hooks:delete(s2s_out_init, Host, ?MODULE, s2s_out_stream_init, 50), - ejabberd_hooks:delete(s2s_out_authenticated_features, - Host, ?MODULE, s2s_out_stream_features, 50), + % ejabberd_hooks:delete(s2s_out_authenticated_features, + % Host, ?MODULE, s2s_out_stream_features, 50), ejabberd_hooks:delete(s2s_out_packet, Host, ?MODULE, s2s_out_packet, 50), ejabberd_hooks:delete(s2s_out_handle_recv, Host, ?MODULE, s2s_out_handle_recv, 50), @@ -88,7 +88,6 @@ stop(Host) -> Host, ?MODULE, s2s_out_closed, 50), ejabberd_hooks:delete(s2s_out_terminate, Host, ?MODULE, s2s_out_terminate, 50), - % delete after implementation of server part ejabberd_hooks:delete(s2s_out_established, Host, ?MODULE, s2s_out_established, 50), %% server part @@ -116,8 +115,7 @@ reload(_Host, _NewOpts, _OldOpts) -> depends(_Host, _Opts) -> []. %% client part -s2s_out_stream_init({ok, #{server_host := ServerHost} = State}, Opts) -> - +s2s_out_stream_init({ok, #{server_host := ServerHost} = State}, Opts) -> State1 = State#{mgmt_timeout => get_resume_timeout(ServerHost), mgmt_queue_type => get_queue_type(ServerHost), mgmt_max_queue => get_max_ack_queue(ServerHost), @@ -125,39 +123,41 @@ s2s_out_stream_init({ok, #{server_host := ServerHost} = State}, Opts) -> mgmt_connection_timeout => get_connection_timeout(ServerHost), mgmt_stanzas_out => 0, mgmt_stanzas_req => 0}, - + case proplists:get_value(resume, Opts) of OldState when OldState /= undefined -> - {ok, State1#{mgmt_old_state => OldState, mgmt_state => resume}}; + {ok, State1#{mgmt_state => connecting, mgmt_old_state => OldState}}; _ -> {ok, State1#{mgmt_state => inactive}} end; s2s_out_stream_init(Acc, _Opts) -> Acc. -s2s_out_stream_features(#{mgmt_state := MgmtState, - mgmt_timeout := Resume} = State, +s2s_out_stream_features(#{mgmt_timeout := Timeout, + mgmt_queue_type := QueueType} = State, #stream_features{sub_els = SubEls}) -> case check_stream_mgmt_support(SubEls) of Xmlns when Xmlns == ?NS_STREAM_MGMT_2; Xmlns == ?NS_STREAM_MGMT_3 -> - case MgmtState of - inactive -> - #{mgmt_queue_type := QueueType} = State, - State1 = - if Resume > 0 -> - send(State, #sm_enable{xmlns = Xmlns, - resume = true, - max = Resume}); - true -> - send(State, #sm_enable{xmlns = Xmlns}) - end, - State1#{mgmt_xmlns => Xmlns, - mgmt_state => wait_for_enabled, - mgmt_queue => p1_queue:new(QueueType)}; + case State of + #{mgmt_old_state := OldState} -> + #{mgmt_previd := Id} = OldState, + State1 = State#{mgmt_state => pending, + mgmt_xmlns => Xmlns}, + send(State1, #sm_resume{h = 0, + xmlns = Xmlns, + previd = Id}); _ -> - #{mgmt_old_state := #{mgmt_previd := Id}} = State, - send(State#{mgmt_xmlns => Xmlns, mgmt_state => pending}, - #sm_resume{h = 0, xmlns = Xmlns, previd = Id}) + Res = if Timeout > 0 -> + #sm_enable{xmlns = Xmlns, + resume = true, + max = Timeout}; + true -> + #sm_enable{xmlns = Xmlns} + end, + State1 = State#{mgmt_state => wait_for_enabled, + mgmt_xmlns => Xmlns, + mgmt_queue => p1_queue:new(QueueType)}, + send(State1, Res) end; _ -> State @@ -165,30 +165,34 @@ s2s_out_stream_features(#{mgmt_state := MgmtState, s2s_out_stream_features(State, _) -> State. -s2s_out_established(#{mgmt_state := resume} = State) -> - #{mgmt_old_state := #{mgmt_previd := Id}} = State, - State1 = send(State, #sm_resume{h = 0, xmlns = ?NS_STREAM_MGMT_3, previd = Id}), - State1#{mgmt_xmlns => ?NS_STREAM_MGMT_3, mgmt_state => pending}; -s2s_out_established(#{mgmt_timeout := Resume, +s2s_out_established(#{mgmt_state := connecting, + mgmt_old_state := OldState} = State) -> + #{mgmt_previd := Id} = OldState, + State1 = State#{mgmt_state => pending, + mgmt_xmlns => ?NS_STREAM_MGMT_3}, + send(State1, #sm_resume{h = 0, + xmlns = ?NS_STREAM_MGMT_3, + previd = Id}); +s2s_out_established(#{mgmt_timeout := Timeout, mgmt_queue_type := QueueType} = State) -> Xmlns = ?NS_STREAM_MGMT_3, - State1 = - if Resume > 0 -> - send(State, #sm_enable{xmlns = Xmlns, - resume = true, - max = Resume}); + Res = + if Timeout > 0 -> + #sm_enable{xmlns = Xmlns, + resume = true, + max = Timeout}; true -> - send(State, #sm_enable{xmlns = Xmlns}) + #sm_enable{xmlns = Xmlns} end, - State1#{mgmt_state => wait_for_enabled, - mgmt_xmlns => Xmlns, - mgmt_queue => p1_queue:new(QueueType)}; + State1 = State#{mgmt_state => wait_for_enabled, + mgmt_xmlns => Xmlns, + mgmt_queue => p1_queue:new(QueueType)}, + send(State1, Res); s2s_out_established(State) -> State. s2s_out_packet(#{mgmt_state := pending} = State, #sm_resumed{} = Pkt) -> - % нужно исправить {stop, handle_resumed(Pkt, State)}; s2s_out_packet(#{mgmt_state := MgmtState} = State, Pkt) when ?is_sm_packet(Pkt) -> @@ -234,9 +238,9 @@ s2s_out_handle_info(#{mgmt_ack_timer := TRef, remote_server := RServer, ?DEBUG("Timed out waiting for stream management " "acknowledgement of ~s", [RServer]), Mod:stop(State); -s2s_out_handle_info(#{mgmt_state := resume, +s2s_out_handle_info(#{mgmt_state := connecting, remote_server := RServer, mod := Mod} = State, - {timeout, _TRef, connection_timeout}) -> + {timeout, _TRef, connection_timeout}) -> ?DEBUG("Timed out waiting for connection " "establishment for resumption previous session with ~s", [RServer]), Mod:stop(State#{mgmt_state => timeout}); @@ -249,7 +253,7 @@ s2s_out_handle_recv(#{mgmt_state := wait_for_enabled, State#{mgmt_state => inactive}; s2s_out_handle_recv(#{mgmt_state := pending, remote_server := RServer, - mgmt_timeout := Resume, + mgmt_timeout := Timeout, mgmt_xmlns := Xmlns, mgmt_queue_type := QueueType, mgmt_old_state := OldState} = State, _El, #sm_failed{}) -> @@ -259,10 +263,10 @@ s2s_out_handle_recv(#{mgmt_state := pending, mgmt_queue => p1_queue:new(QueueType)}, State2 = maps:remove(mgmt_old_state, State1), State3 = - if Resume > 0 -> + if Timeout > 0 -> send(State2, #sm_enable{xmlns = Xmlns, resume = true, - max = Resume}); + max = Timeout}); true -> send(State2, #sm_enable{xmlns = Xmlns}) end, @@ -270,8 +274,7 @@ s2s_out_handle_recv(#{mgmt_state := pending, s2s_out_handle_recv(State, _El, _Pkt) -> State. -% check reason ? -s2s_out_closed(#{mgmt_state := resume} = State, _) -> +s2s_out_closed(#{mgmt_state := connecting} = State, _) -> {stop, transition_to_resume(State#{stream_state => connecting})}; s2s_out_closed(State, _) -> State. @@ -522,7 +525,7 @@ transition_to_resume(#{mgmt_state := active, _ -> State1 end; -transition_to_resume(#{mgmt_state := resume, mod := Mod} = State) -> +transition_to_resume(#{mgmt_state := connecting, mod := Mod} = State) -> Mod:connect(self()), State; transition_to_resume(State) -> @@ -619,7 +622,8 @@ handle_resume(#{lang := Lang, remote_server := RServer} = State, -spec handle_resumed(sm_resumed(), state()) -> state(). handle_resumed(#sm_resumed{h = H, previd = _Id}, - #{remote_server := RServer, mgmt_old_state := OldState} = State) -> + #{remote_server := RServer, + mgmt_old_state := OldState} = State) -> ResumedState = copy_state(OldState, State), #{mgmt_xmlns := Xmlns, mgmt_queue := Queue} = ResumedState, State1 = check_h_attribute(ResumedState, H), @@ -628,7 +632,7 @@ handle_resumed(#sm_resumed{h = H, previd = _Id}, send(State2, #sm_r{xmlns = Xmlns}). resend_unacked_stanzas(#{remote_server := RServer} = State, Queue) - when ?qlen(Queue) > 0 -> + when ?qlen(Queue) > 0 -> ?DEBUG("Resending ~B unacknowledged stanza(s) to ~s", [p1_queue:len(Queue), RServer]), p1_queue:foldl( @@ -640,7 +644,7 @@ resend_unacked_stanzas(State, _) -> State. route_unacked_stanzas(#{remote_server := RServer} = State, Queue) - when ?qlen(Queue) > 0 -> + when ?qlen(Queue) > 0 -> ?DEBUG("Re-rout ~B unacknowledged stanza(s) to ~s", [p1_queue:len(Queue), RServer]), p1_queue:foreach( @@ -651,7 +655,7 @@ route_unacked_stanzas(#{remote_server := RServer} = State, Queue) route_unacked_stanzas(_State, _Queue) -> ok. -bounce_errors(#{mgmt_state := resume}, Queue) +bounce_errors(_State, Queue) when ?qlen(Queue) > 0 -> p1_queue:foreach( fun({_, _, Pkt}) -> @@ -662,14 +666,19 @@ bounce_errors(_State, _) -> ok. -spec copy_state(state(), state()) -> state(). -copy_state(#{mgmt_timeout := Timeout, - mgmt_xmlns := Xmlns, +copy_state(#{mgmt_xmlns := Xmlns, mgmt_queue := Queue, mgmt_stanzas_out := NumStanzasOut, - mgmt_previd := Id} = _OldState, NewState) -> - NewState#{mgmt_timeout => Timeout, - mgmt_xmlns => Xmlns, - mgmt_queue => Queue, + mgmt_previd := Id} = _OldState, + #{mgmt_queue_type := QueueType} = NewState) -> + + Queue1 = case QueueType of + ram -> p1_queue:file_to_ram(Queue); + _ -> p1_queue:ram_to_file(Queue) + end, + + NewState#{mgmt_xmlns => Xmlns, + mgmt_queue => Queue1, mgmt_stanzas_out => NumStanzasOut, mgmt_previd => Id}. @@ -749,16 +758,11 @@ get_ack_timeout(Host) -> T -> timer:seconds(T) end. -% get_max_unacked_stanzas(Host) -> -% gen_mod:get_module_opt(Host, ?MODULE, max_unacked_stanzas, 0). - get_connection_timeout(Host) -> gen_mod:get_module_opt(Host, ?MODULE, connection_timeout, 120000). mod_opt_type(connection_timeout) -> fun(I) when is_integer(I), I >= 0 -> I end; -% mod_opt_type(max_unacked_stanzas) -> -% fun(I) when is_integer(I), I >= 0 -> I end; mod_opt_type(max_ack_queue) -> fun(I) when is_integer(I), I > 0 -> I; (infinity) -> infinity @@ -776,4 +780,4 @@ mod_opt_type(queue_type) -> (ram) -> ram end; mod_opt_type(_) -> [max_ack_queue, ack_timeout, resume_timeout, max_resume_timeout, - queue_type, connection_timeout]. %max_unacked_stanzas, + queue_type, connection_timeout]. From 9f563f4ce4db3c3ceb18234451555c5bd548ae25 Mon Sep 17 00:00:00 2001 From: amuhar Date: Tue, 15 Aug 2017 16:32:37 +0300 Subject: [PATCH 14/25] resume only if queue isn't empty --- src/mod_stream_mgmt_s2s.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mod_stream_mgmt_s2s.erl b/src/mod_stream_mgmt_s2s.erl index d281bc14ad4..10b61175c5b 100644 --- a/src/mod_stream_mgmt_s2s.erl +++ b/src/mod_stream_mgmt_s2s.erl @@ -279,7 +279,8 @@ s2s_out_closed(#{mgmt_state := connecting} = State, _) -> s2s_out_closed(State, _) -> State. -s2s_out_terminate(#{mgmt_state := active} = State, _Reason) -> +s2s_out_terminate(#{mgmt_state := active, mgmt_queue := Queue} = State, _Reason) + when ?qlen(Queue) > 0 -> transition_to_resume(State); s2s_out_terminate(#{mgmt_state := timeout, mgmt_old_state := OldState} = State, _Reason) -> From d7717bd9f29683db1456724a91dd9a46da6995e2 Mon Sep 17 00:00:00 2001 From: amuhar Date: Thu, 17 Aug 2017 01:32:03 +0300 Subject: [PATCH 15/25] change server part resumption --- src/mod_stream_mgmt_s2s.erl | 65 +++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 35 deletions(-) diff --git a/src/mod_stream_mgmt_s2s.erl b/src/mod_stream_mgmt_s2s.erl index 10b61175c5b..0d71ed18c0e 100644 --- a/src/mod_stream_mgmt_s2s.erl +++ b/src/mod_stream_mgmt_s2s.erl @@ -13,7 +13,8 @@ %% server part hooks -export([s2s_in_stream_started/1, s2s_in_stream_features/2, s2s_in_unauthenticated_packet/2, s2s_in_authenticated_packet/2, - s2s_in_handle_call/3, s2s_in_handle_info/2, + s2s_in_handle_info/2, + % s2s_in_handle_call/3, s2s_in_handle_info/2, s2s_in_closed/2, s2s_in_terminate/2]). -include("xmpp.hrl"). @@ -64,8 +65,8 @@ start(Host, _Opts) -> Host, ?MODULE, s2s_in_unauthenticated_packet, 50), ejabberd_hooks:add(s2s_in_authenticated_packet, Host, ?MODULE, s2s_in_authenticated_packet, 50), - ejabberd_hooks:add(s2s_in_handle_call, - Host, ?MODULE, s2s_in_handle_call, 50), + % ejabberd_hooks:add(s2s_in_handle_call, + % Host, ?MODULE, s2s_in_handle_call, 50), ejabberd_hooks:add(s2s_in_handle_info, Host, ?MODULE, s2s_in_handle_info, 50), ejabberd_hooks:add(s2s_in_closed, @@ -99,8 +100,8 @@ stop(Host) -> Host, ?MODULE, s2s_in_unauthenticated_packet, 50), ejabberd_hooks:delete(s2s_in_authenticated_packet, Host, ?MODULE, s2s_in_authenticated_packet, 50), - ejabberd_hooks:delete(s2s_in_handle_call, - Host, ?MODULE, s2s_in_handle_call, 50), + % ejabberd_hooks:delete(s2s_in_handle_call, + % Host, ?MODULE, s2s_in_handle_call, 50), ejabberd_hooks:delete(s2s_in_handle_info, Host, ?MODULE, s2s_in_handle_info, 50), ejabberd_hooks:delete(s2s_in_closed, @@ -333,23 +334,20 @@ s2s_in_authenticated_packet(#{mgmt_state := MgmtState} = State, Pkt) s2s_in_authenticated_packet(State, Pkt) -> mod_stream_mgmt:update_num_stanzas_in(State, Pkt). -s2s_in_handle_call(#{mgmt_state := pending, mod:= Mod, +s2s_in_handle_info(#{mgmt_state := pending, remote_server := RServer, + mod := Mod} = State, {timeout, _, pending_timeout}) -> + ?DEBUG("Timed out waiting for resumption of stream for ~s", [RServer]), + Mod:stop(State); +s2s_in_handle_info(#{mgmt_state := pending, mod:= Mod, remote_server := RServer, unique_id := UniqueId} = State, - {resume_session, RServer, UniqueId}, From) -> + {_, From, {resume_session, RServer, UniqueId}}) -> Mod:reply(From, {resume, State}), {stop, State#{mgmt_state => resumed}}; -s2s_in_handle_call(#{mod := Mod} = State, {resume_session, _, _}, From) -> +s2s_in_handle_info(#{mod := Mod} = State, {_, From, {resume_session, _, _}}) -> Mod:reply(From, error), {stop, State}; -s2s_in_handle_call(State, _Call, _From) -> - State. - -s2s_in_handle_info(#{mgmt_state := pending, remote_server := RServer, - mod := Mod} = State, {timeout, _, pending_timeout}) -> - ?DEBUG("Timed out waiting for resumption of stream for ~s", [RServer]), - Mod:stop(State); s2s_in_handle_info(State, _Msg) -> - State. + State. s2s_in_closed(#{mgmt_state := active} = State, _Reason) -> {stop, transition_to_pending(State)}; @@ -560,34 +558,29 @@ resume(From, To, Opts) -> error end. --spec resume_session(pid(), {binary(), integer()}) -> {resume, state()} | error. -resume_session(Pid, {RServer, UniqueId}) -> - ejabberd_s2s_in:call(Pid, {resume_session, RServer, UniqueId}, timer:seconds(15)). - --spec inherit_session_state({binary(), binary()}, list()) -> {ok, state()}| - {error, binary()}. -inherit_session_state(_ResumeId, []) -> +get_old_session_state(_ResumeId, []) -> {error, <<"Previous session PID not found">>}; -inherit_session_state({RServer, UniqueId} = ResumeId, [{_, Pid, _, _}|Specs]) - when is_pid(Pid) -> - try resume_session(Pid, {RServer, UniqueId}) of +get_old_session_state({RServer, UniqueId} = ResumeId, [{_, Pid, _, _}|Specs]) + when is_pid(Pid), Pid /= self() -> + try gen_fsm:sync_send_all_state_event(Pid, + {resume_session, RServer, UniqueId}) of {resume, OldState} -> ejabberd_s2s_in:stop(Pid), {ok, OldState}; error -> - inherit_session_state(ResumeId, Specs) + get_old_session_state(ResumeId, Specs) catch _:_ -> - inherit_session_state(ResumeId, Specs) + get_old_session_state(ResumeId, Specs) end; -inherit_session_state(ResumeId, [_H|L] ) -> - inherit_session_state(ResumeId, L). +get_old_session_state(ResumeId, [_H|L]) -> + get_old_session_state(ResumeId, L). handle_resume(#{lang := Lang, remote_server := RServer} = State, #sm_resume{previd = ResumeId, xmlns = Xmlns}) -> case misc:base64_to_term(ResumeId) of {term, {RServer, UniqueId}} -> - case inherit_session_state({RServer, UniqueId}, + case get_old_session_state({RServer, UniqueId}, supervisor:which_children(ejabberd_s2s_in_sup)) of {ok, OldState} -> #{mgmt_stanzas_in := H, @@ -619,7 +612,7 @@ handle_resume(#{lang := Lang, remote_server := RServer} = State, text = xmpp:mk_text(Msg, Lang), xmlns = Xmlns}, send(State, Err) - end. + end. -spec handle_resumed(sm_resumed(), state()) -> state(). handle_resumed(#sm_resumed{h = H, previd = _Id}, @@ -689,12 +682,14 @@ check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, when H > NumStanzasOut -> ?DEBUG("~s acknowledged ~B stanzas," "but only ~B were sent ", [RServer, H, NumStanzasOut]), - mod_stream_mgmt:mgmt_queue_drop(State#{mgmt_stanzas_out => H}, NumStanzasOut); + State; + % mod_stream_mgmt:mgmt_queue_drop(State#{mgmt_stanzas_out => H}, NumStanzasOut); check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, remote_server := RServer} = State, H) -> ?DEBUG("~s acknowledged ~B of ~B " "stanzas", [RServer, H, NumStanzasOut]), - mod_stream_mgmt:mgmt_queue_drop(State, H). + State. + % mod_stream_mgmt:mgmt_queue_drop(State, H). -spec add_resent_delay_info(state(), stanza(), erlang:timestamp()) -> stanza(). add_resent_delay_info(#{server_host := LServer}, El, Time) -> @@ -722,7 +717,7 @@ resend_rack(#{mgmt_ack_timer := _, mgmt_queue := Queue, mgmt_stanzas_out := NumStanzasOut, mgmt_stanzas_req := NumStanzasReq} = State) -> - State1 = mod_stream_mgmt:cancel_ack_timer(State), + State1 = State, %mod_stream_mgmt:cancel_ack_timer(State), case NumStanzasReq < NumStanzasOut andalso not p1_queue:is_empty(Queue) of true -> send_rack(State1); false -> State1 From ec46370d53b009bc077d13d128ad1fa491c48f33 Mon Sep 17 00:00:00 2001 From: amuhar Date: Thu, 17 Aug 2017 01:54:48 +0300 Subject: [PATCH 16/25] change server part resumption --- src/mod_stream_mgmt_s2s.erl | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/mod_stream_mgmt_s2s.erl b/src/mod_stream_mgmt_s2s.erl index 0d71ed18c0e..8533b0df797 100644 --- a/src/mod_stream_mgmt_s2s.erl +++ b/src/mod_stream_mgmt_s2s.erl @@ -682,14 +682,12 @@ check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, when H > NumStanzasOut -> ?DEBUG("~s acknowledged ~B stanzas," "but only ~B were sent ", [RServer, H, NumStanzasOut]), - State; - % mod_stream_mgmt:mgmt_queue_drop(State#{mgmt_stanzas_out => H}, NumStanzasOut); + mod_stream_mgmt:mgmt_queue_drop(State#{mgmt_stanzas_out => H}, NumStanzasOut); check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, remote_server := RServer} = State, H) -> ?DEBUG("~s acknowledged ~B of ~B " "stanzas", [RServer, H, NumStanzasOut]), - State. - % mod_stream_mgmt:mgmt_queue_drop(State, H). + mod_stream_mgmt:mgmt_queue_drop(State, H). -spec add_resent_delay_info(state(), stanza(), erlang:timestamp()) -> stanza(). add_resent_delay_info(#{server_host := LServer}, El, Time) -> @@ -717,7 +715,7 @@ resend_rack(#{mgmt_ack_timer := _, mgmt_queue := Queue, mgmt_stanzas_out := NumStanzasOut, mgmt_stanzas_req := NumStanzasReq} = State) -> - State1 = State, %mod_stream_mgmt:cancel_ack_timer(State), + State1 = mod_stream_mgmt:cancel_ack_timer(State), case NumStanzasReq < NumStanzasOut andalso not p1_queue:is_empty(Queue) of true -> send_rack(State1); false -> State1 From e2de29c1f2651a7d9b74c16c140fbc815f86942b Mon Sep 17 00:00:00 2001 From: amuhar Date: Fri, 18 Aug 2017 18:18:14 +0300 Subject: [PATCH 17/25] add queue for pending state --- src/mod_stream_mgmt_s2s.erl | 107 ++++++++++++++++++++++-------------- 1 file changed, 66 insertions(+), 41 deletions(-) diff --git a/src/mod_stream_mgmt_s2s.erl b/src/mod_stream_mgmt_s2s.erl index 8533b0df797..58b35176542 100644 --- a/src/mod_stream_mgmt_s2s.erl +++ b/src/mod_stream_mgmt_s2s.erl @@ -13,9 +13,7 @@ %% server part hooks -export([s2s_in_stream_started/1, s2s_in_stream_features/2, s2s_in_unauthenticated_packet/2, s2s_in_authenticated_packet/2, - s2s_in_handle_info/2, - % s2s_in_handle_call/3, s2s_in_handle_info/2, - s2s_in_closed/2, s2s_in_terminate/2]). + s2s_in_handle_info/2, s2s_in_closed/2, s2s_in_terminate/2]). -include("xmpp.hrl"). -include("logger.hrl"). @@ -65,8 +63,6 @@ start(Host, _Opts) -> Host, ?MODULE, s2s_in_unauthenticated_packet, 50), ejabberd_hooks:add(s2s_in_authenticated_packet, Host, ?MODULE, s2s_in_authenticated_packet, 50), - % ejabberd_hooks:add(s2s_in_handle_call, - % Host, ?MODULE, s2s_in_handle_call, 50), ejabberd_hooks:add(s2s_in_handle_info, Host, ?MODULE, s2s_in_handle_info, 50), ejabberd_hooks:add(s2s_in_closed, @@ -100,8 +96,6 @@ stop(Host) -> Host, ?MODULE, s2s_in_unauthenticated_packet, 50), ejabberd_hooks:delete(s2s_in_authenticated_packet, Host, ?MODULE, s2s_in_authenticated_packet, 50), - % ejabberd_hooks:delete(s2s_in_handle_call, - % Host, ?MODULE, s2s_in_handle_call, 50), ejabberd_hooks:delete(s2s_in_handle_info, Host, ?MODULE, s2s_in_handle_info, 50), ejabberd_hooks:delete(s2s_in_closed, @@ -127,7 +121,7 @@ s2s_out_stream_init({ok, #{server_host := ServerHost} = State}, Opts) -> case proplists:get_value(resume, Opts) of OldState when OldState /= undefined -> - {ok, State1#{mgmt_state => connecting, mgmt_old_state => OldState}}; + {ok, State1#{mgmt_state => connecting, mgmt_prev_session => OldState}}; _ -> {ok, State1#{mgmt_state => inactive}} end; @@ -140,9 +134,10 @@ s2s_out_stream_features(#{mgmt_timeout := Timeout, case check_stream_mgmt_support(SubEls) of Xmlns when Xmlns == ?NS_STREAM_MGMT_2; Xmlns == ?NS_STREAM_MGMT_3 -> case State of - #{mgmt_old_state := OldState} -> + #{mgmt_prev_session := OldState} -> #{mgmt_previd := Id} = OldState, State1 = State#{mgmt_state => pending, + mgmt_queue => p1_queue:new(QueueType), mgmt_xmlns => Xmlns}, send(State1, #sm_resume{h = 0, xmlns = Xmlns, @@ -156,8 +151,8 @@ s2s_out_stream_features(#{mgmt_timeout := Timeout, #sm_enable{xmlns = Xmlns} end, State1 = State#{mgmt_state => wait_for_enabled, - mgmt_xmlns => Xmlns, - mgmt_queue => p1_queue:new(QueueType)}, + mgmt_queue => p1_queue:new(QueueType), + mgmt_xmlns => Xmlns}, send(State1, Res) end; _ -> @@ -167,9 +162,11 @@ s2s_out_stream_features(State, _) -> State. s2s_out_established(#{mgmt_state := connecting, - mgmt_old_state := OldState} = State) -> + mgmt_queue_type := QueueType, + mgmt_prev_session := OldState} = State) -> #{mgmt_previd := Id} = OldState, State1 = State#{mgmt_state => pending, + mgmt_queue => p1_queue:new(QueueType), mgmt_xmlns => ?NS_STREAM_MGMT_3}, send(State1, #sm_resume{h = 0, xmlns = ?NS_STREAM_MGMT_3, @@ -187,8 +184,8 @@ s2s_out_established(#{mgmt_timeout := Timeout, end, State1 = State#{mgmt_state => wait_for_enabled, - mgmt_xmlns => Xmlns, - mgmt_queue => p1_queue:new(QueueType)}, + mgmt_queue => p1_queue:new(QueueType), + mgmt_xmlns => Xmlns}, send(State1, Res); s2s_out_established(State) -> State. @@ -209,7 +206,8 @@ s2s_out_packet(State, _Pkt) -> s2s_out_handle_send(#{mgmt_state := MgmtState, lang := Lang} = State, Pkt, SendResult) when MgmtState == active; - MgmtState == wait_for_enabled -> + MgmtState == wait_for_enabled; + MgmtState == pending -> case Pkt of _ when ?is_stanza(Pkt) -> Meta = xmpp:get_meta(Pkt), @@ -256,13 +254,24 @@ s2s_out_handle_recv(#{mgmt_state := pending, remote_server := RServer, mgmt_timeout := Timeout, mgmt_xmlns := Xmlns, - mgmt_queue_type := QueueType, - mgmt_old_state := OldState} = State, _El, #sm_failed{}) -> + mgmt_queue := Queue, + mgmt_prev_session := OldState} = State, _El, #sm_failed{}) -> ?DEBUG("Remote server ~s can't resume previous session", [RServer]), - #{mgmt_queue := Queue} = OldState, + + #{mgmt_queue := OldQueue, mgmt_stanzas_out := OldNumStanzasOut} = OldState, + + {NewNumStanzasOut, NewQueue} = + p1_queue:foldl( + fun({_, Time, Pkt}, {AccNum, AccQueue}) -> + Num = AccNum + 1, + {Num, p1_queue:in({Num, Time, Pkt}, AccQueue)} + end, {OldNumStanzasOut, OldQueue}, Queue), + State1 = State#{mgmt_state => wait_for_enabled, - mgmt_queue => p1_queue:new(QueueType)}, - State2 = maps:remove(mgmt_old_state, State1), + mgmt_queue => NewQueue, + mgmt_stanzas_out => NewNumStanzasOut}, + + State2 = maps:remove(mgmt_prev_session, State1), State3 = if Timeout > 0 -> send(State2, #sm_enable{xmlns = Xmlns, @@ -271,7 +280,7 @@ s2s_out_handle_recv(#{mgmt_state := pending, true -> send(State2, #sm_enable{xmlns = Xmlns}) end, - resend_unacked_stanzas(State3, Queue); + resend_unacked_stanzas(State3, OldNumStanzasOut); s2s_out_handle_recv(State, _El, _Pkt) -> State. @@ -284,20 +293,18 @@ s2s_out_terminate(#{mgmt_state := active, mgmt_queue := Queue} = State, _Reason) when ?qlen(Queue) > 0 -> transition_to_resume(State); s2s_out_terminate(#{mgmt_state := timeout, - mgmt_old_state := OldState} = State, _Reason) -> + mgmt_prev_session := OldState} = State, _Reason) -> #{mgmt_queue := Queue} = OldState, bounce_errors(State, Queue), State; s2s_out_terminate(#{mgmt_state := pending, - mgmt_old_state := OldState} = State, _Reason) -> + mgmt_prev_session := OldState} = State, _Reason) -> #{mgmt_queue := Queue} = OldState, route_unacked_stanzas(State, Queue), State; s2s_out_terminate(State, _Reason) -> State. -%% client part - %% server part s2s_in_stream_started(#{server_host := Host} = State) -> @@ -323,7 +330,7 @@ s2s_in_unauthenticated_packet(State, _Pkt) -> s2s_in_authenticated_packet(#{mgmt_state := inactive} = State, #sm_resume{} = Pkt) -> - {stop, handle_resume(State, Pkt)}; + {stop, handle_resume(Pkt, State)}; s2s_in_authenticated_packet(#{mgmt_state := MgmtState} = State, Pkt) when ?is_sm_packet(Pkt) -> if MgmtState == active; MgmtState == pending -> @@ -354,8 +361,6 @@ s2s_in_closed(#{mgmt_state := active} = State, _Reason) -> s2s_in_closed(State, _Reason) -> State. -% it isn't important: we can delete it and delete from ejabberd_s2s_in module -% ejabberd_hooks:run_fold(..) s2s_in_terminate(#{mgmt_state := resumed, remote_server := RServer} = State, _Reason) -> ?INFO_MSG("Closing former stream of resumed session for ~s", [RServer]), @@ -363,8 +368,6 @@ s2s_in_terminate(#{mgmt_state := resumed, s2s_in_terminate(State, _Reason) -> State. -%% server part end - %%%============================================================================= %%% Internal functions @@ -576,8 +579,8 @@ get_old_session_state({RServer, UniqueId} = ResumeId, [{_, Pid, _, _}|Specs]) get_old_session_state(ResumeId, [_H|L]) -> get_old_session_state(ResumeId, L). -handle_resume(#{lang := Lang, remote_server := RServer} = State, - #sm_resume{previd = ResumeId, xmlns = Xmlns}) -> +handle_resume(#sm_resume{previd = ResumeId, xmlns = Xmlns}, + #{lang := Lang, remote_server := RServer} = State) -> case misc:base64_to_term(ResumeId) of {term, {RServer, UniqueId}} -> case get_old_session_state({RServer, UniqueId}, @@ -617,20 +620,39 @@ handle_resume(#{lang := Lang, remote_server := RServer} = State, -spec handle_resumed(sm_resumed(), state()) -> state(). handle_resumed(#sm_resumed{h = H, previd = _Id}, #{remote_server := RServer, - mgmt_old_state := OldState} = State) -> + mgmt_queue := Queue, + mgmt_prev_session := OldState} = State) -> ResumedState = copy_state(OldState, State), - #{mgmt_xmlns := Xmlns, mgmt_queue := Queue} = ResumedState, + + #{mgmt_xmlns := Xmlns, + mgmt_queue := OldQueue, + mgmt_stanzas_out := OldNumStanzasOut} = ResumedState, + State1 = check_h_attribute(ResumedState, H), - State2 = resend_unacked_stanzas(State1#{mgmt_state => active}, Queue), + + {NewNumStanzasOut, NewQueue} = + p1_queue:foldl( + fun({_, Time, Pkt}, {AccNum, AccQueue}) -> + Num = AccNum + 1, + {Num, p1_queue:in({Num, Time, Pkt}, AccQueue)} + end, {OldNumStanzasOut, OldQueue}, Queue), + + State2 = State1#{mgmt_state => wait_for_enabled, + mgmt_queue => NewQueue, + mgmt_stanzas_out => NewNumStanzasOut}, + + State3 = resend_unacked_stanzas(State2#{mgmt_state => active}, + OldNumStanzasOut), ?DEBUG("Resumed session for ~s", [RServer]), - send(State2, #sm_r{xmlns = Xmlns}). + send(State3, #sm_r{xmlns = Xmlns}). -resend_unacked_stanzas(#{remote_server := RServer} = State, Queue) +resend_unacked_stanzas(#{remote_server := RServer, + mgmt_queue := Queue} = State, LastStanzaNum) when ?qlen(Queue) > 0 -> ?DEBUG("Resending ~B unacknowledged stanza(s) to ~s", [p1_queue:len(Queue), RServer]), p1_queue:foldl( - fun({_, Time, Pkt}, AccState) -> + fun({Num, Time, Pkt}, AccState) when Num =< LastStanzaNum -> NewPkt = add_resent_delay_info(AccState, Pkt, Time), send(AccState, xmpp:put_meta(NewPkt, mgmt_is_resent, true)) end, State, Queue); @@ -682,12 +704,14 @@ check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, when H > NumStanzasOut -> ?DEBUG("~s acknowledged ~B stanzas," "but only ~B were sent ", [RServer, H, NumStanzasOut]), - mod_stream_mgmt:mgmt_queue_drop(State#{mgmt_stanzas_out => H}, NumStanzasOut); + % mod_stream_mgmt:mgmt_queue_drop(State#{mgmt_stanzas_out => H}, NumStanzasOut); + State#{mgmt_stanzas_out => H}; check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, remote_server := RServer} = State, H) -> ?DEBUG("~s acknowledged ~B of ~B " "stanzas", [RServer, H, NumStanzasOut]), - mod_stream_mgmt:mgmt_queue_drop(State, H). + State. + % mod_stream_mgmt:mgmt_queue_drop(State, H). -spec add_resent_delay_info(state(), stanza(), erlang:timestamp()) -> stanza(). add_resent_delay_info(#{server_host := LServer}, El, Time) -> @@ -703,6 +727,7 @@ send(#{mod := Mod} = State, Pkt) -> Mod:send(State, Pkt). send_rack(#{mgmt_ack_timer := _} = State) -> + ?INFO_MSG("can't send request", []), State; send_rack(#{mgmt_xmlns := Xmlns, mgmt_stanzas_out := NumStanzasOut, @@ -715,7 +740,7 @@ resend_rack(#{mgmt_ack_timer := _, mgmt_queue := Queue, mgmt_stanzas_out := NumStanzasOut, mgmt_stanzas_req := NumStanzasReq} = State) -> - State1 = mod_stream_mgmt:cancel_ack_timer(State), + State1 = State, %mod_stream_mgmt:cancel_ack_timer(State), case NumStanzasReq < NumStanzasOut andalso not p1_queue:is_empty(Queue) of true -> send_rack(State1); false -> State1 From 00537861de7f9bedcd6af3f65e638c6c32db6ed1 Mon Sep 17 00:00:00 2001 From: amuhar Date: Fri, 18 Aug 2017 18:19:22 +0300 Subject: [PATCH 18/25] add queue for pending state --- src/mod_stream_mgmt_s2s.erl | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/mod_stream_mgmt_s2s.erl b/src/mod_stream_mgmt_s2s.erl index 58b35176542..8a770e62c59 100644 --- a/src/mod_stream_mgmt_s2s.erl +++ b/src/mod_stream_mgmt_s2s.erl @@ -704,14 +704,12 @@ check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, when H > NumStanzasOut -> ?DEBUG("~s acknowledged ~B stanzas," "but only ~B were sent ", [RServer, H, NumStanzasOut]), - % mod_stream_mgmt:mgmt_queue_drop(State#{mgmt_stanzas_out => H}, NumStanzasOut); - State#{mgmt_stanzas_out => H}; + mod_stream_mgmt:mgmt_queue_drop(State#{mgmt_stanzas_out => H}, NumStanzasOut); check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, remote_server := RServer} = State, H) -> ?DEBUG("~s acknowledged ~B of ~B " "stanzas", [RServer, H, NumStanzasOut]), - State. - % mod_stream_mgmt:mgmt_queue_drop(State, H). + mod_stream_mgmt:mgmt_queue_drop(State, H). -spec add_resent_delay_info(state(), stanza(), erlang:timestamp()) -> stanza(). add_resent_delay_info(#{server_host := LServer}, El, Time) -> @@ -740,7 +738,7 @@ resend_rack(#{mgmt_ack_timer := _, mgmt_queue := Queue, mgmt_stanzas_out := NumStanzasOut, mgmt_stanzas_req := NumStanzasReq} = State) -> - State1 = State, %mod_stream_mgmt:cancel_ack_timer(State), + State1 = mod_stream_mgmt:cancel_ack_timer(State), case NumStanzasReq < NumStanzasOut andalso not p1_queue:is_empty(Queue) of true -> send_rack(State1); false -> State1 From 245a4704adda6e084f1ec9e2ad879fc23638ae46 Mon Sep 17 00:00:00 2001 From: amuhar Date: Fri, 18 Aug 2017 18:28:18 +0300 Subject: [PATCH 19/25] tests for server part of implementation --- src/mod_stream_mgmt_s2s.erl | 1 - test/ejabberd_SUITE.erl | 9 ++-- test/sms2s_tests.erl | 85 +++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 test/sms2s_tests.erl diff --git a/src/mod_stream_mgmt_s2s.erl b/src/mod_stream_mgmt_s2s.erl index 8a770e62c59..39e6bac4eb5 100644 --- a/src/mod_stream_mgmt_s2s.erl +++ b/src/mod_stream_mgmt_s2s.erl @@ -725,7 +725,6 @@ send(#{mod := Mod} = State, Pkt) -> Mod:send(State, Pkt). send_rack(#{mgmt_ack_timer := _} = State) -> - ?INFO_MSG("can't send request", []), State; send_rack(#{mgmt_xmlns := Xmlns, mgmt_stanzas_out := NumStanzasOut, diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl index 97c56159ac6..55a7b000f33 100644 --- a/test/ejabberd_SUITE.erl +++ b/test/ejabberd_SUITE.erl @@ -320,10 +320,10 @@ init_per_testcase(TestCase, OrigConfig) -> Password = ?config(password, Config), ejabberd_auth:try_register(User, Server, Password), open_session(bind(auth(connect(Config)))); - _ when TestGroup == s2s_tests -> + _ when TestGroup == s2s_tests; TestGroup == sms2s_single -> auth(connect(starttls(connect(Config)))); - _ -> - open_session(bind(auth(connect(Config)))) + _ -> + open_session(bind(auth(connect(Config)))) end. end_per_testcase(_TestCase, _Config) -> @@ -520,7 +520,8 @@ s2s_tests() -> test_missing_to, test_invalid_from, bad_nonza, - codec_failure]}]. + codec_failure]}, + sms2s_tests:single_cases()]. groups() -> [{ldap, [sequence], ldap_tests()}, diff --git a/test/sms2s_tests.erl b/test/sms2s_tests.erl new file mode 100644 index 00000000000..b075fab2b8d --- /dev/null +++ b/test/sms2s_tests.erl @@ -0,0 +1,85 @@ +%%%------------------------------------------------------------------- +%%% Author : Anna Mukharram +%%%------------------------------------------------------------------- + +-module(sms2s_tests). + +%% API +-compile(export_all). + +-import(suite, [connect/1, send/2, recv/1, set_opt/3, + close_socket/1, disconnect/1]). + +-include("suite.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== + +single_cases() -> + {sms2s_single, [sequence], + [single_test(enable), + single_test(resume), + single_test(resume_failed)]}. + +enable(Config) -> + Server = ?config(server, Config), + ServerJID = jid:make(<<"">>, Server, <<"">>), + From = ?config(stream_from, Config), + FromJID = jid:make(<<"">>, From, <<"">>), + Msg = #message{from = FromJID, to = ServerJID, type = headline, + body = [#text{data = <<"body">>}]}, + ct:comment("Stream management with resumption is enabled"), + send(Config, #sm_enable{resume = true, xmlns = ?NS_STREAM_MGMT_3}), + #sm_enabled{id = ID, resume = true} = recv(Config), + ct:comment("Initial request; 'h' should be 0"), + send(Config, #sm_r{xmlns = ?NS_STREAM_MGMT_3}), + #sm_a{h = 0} = recv(Config), + ct:comment("Sending three messages and requesting again; 'h' should be 3"), + send(Config, Msg), + send(Config, Msg), + send(Config, Msg), + send(Config, #sm_r{xmlns = ?NS_STREAM_MGMT_3}), + #sm_a{h = 3} = recv(Config), + ct:comment("Closing socket"), + close_socket(Config), + {save_config, set_opt(sm_previd, ID, Config)}. + +resume(Config) -> + {_, SMConfig} = ?config(saved_config, Config), + ID = ?config(sm_previd, SMConfig), + ct:comment("Resuming the session"), + send(Config, #sm_resume{previd = ID, h = 0, xmlns = ?NS_STREAM_MGMT_3}), + #sm_resumed{previd = ID, h = 3} = recv(Config), + ct:comment("Checking if the server counts stanzas correctly"), + Server = ?config(server, Config), + ServerJID = jid:make(<<"">>, Server, <<"">>), + From = ?config(stream_from, Config), + FromJID = jid:make(<<"">>, From, <<"">>), + Msg = #message{from = FromJID, to = ServerJID, type = headline, + body = [#text{data = <<"body">>}]}, + send(Config, Msg), + send(Config, #sm_r{xmlns = ?NS_STREAM_MGMT_3}), + #sm_a{h = 4} = recv(Config), + ct:comment("Closing socket"), + close_socket(Config), + {save_config, set_opt(sm_previd, ID, Config)}. + +resume_failed(Config) -> + {_, SMConfig} = ?config(saved_config, Config), + ID = ?config(sm_previd, SMConfig), + ct:comment("Waiting for the session to time out"), + ct:sleep(30000), + ct:comment("Trying to resume timed out session"), + send(Config, #sm_resume{previd = ID, h = 0, xmlns = ?NS_STREAM_MGMT_3}), + #sm_failed{reason = 'item-not-found'} = recv(Config), + disconnect(Config). + + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +single_test(T) -> + list_to_atom("sms2s_" ++ atom_to_list(T)). + + From 7c70f74bbaf93613b2bbf50efbb0c719558d2df6 Mon Sep 17 00:00:00 2001 From: amuhar Date: Sun, 20 Aug 2017 17:48:37 +0300 Subject: [PATCH 20/25] change send_rack/1 --- src/mod_stream_mgmt_s2s.erl | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/mod_stream_mgmt_s2s.erl b/src/mod_stream_mgmt_s2s.erl index 39e6bac4eb5..3c8842899cd 100644 --- a/src/mod_stream_mgmt_s2s.erl +++ b/src/mod_stream_mgmt_s2s.erl @@ -522,7 +522,7 @@ transition_to_resume(#{mgmt_state := active, ?DEBUG("Try to connect to remote server ~s", [RServer]), case resume(Server, RServer, [{resume, State1}]) of {ok, Pid} -> - erlang:start_timer(Timeout, Pid, connection_timeout), + erlang:start_timer(timer:seconds(Timeout), Pid, connection_timeout), State1; _ -> State1 @@ -717,15 +717,17 @@ add_resent_delay_info(#{server_host := LServer}, El, Time) -> add_resent_delay_info(_State, El, _Time) -> El. -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% todo: import from mod_stream_mgmt.erl - -spec send(state(), xmpp_element()) -> state(). send(#{mod := Mod} = State, Pkt) -> Mod:send(State, Pkt). send_rack(#{mgmt_ack_timer := _} = State) -> State; +send_rack(#{mgmt_xmlns := Xmlns, + mgmt_stanzas_out := NumStanzasOut, + mgmt_ack_timeout := infinity} = State) -> + State1 = State#{mgmt_stanzas_req => NumStanzasOut}, + send(State1, #sm_r{xmlns = Xmlns}); send_rack(#{mgmt_xmlns := Xmlns, mgmt_stanzas_out := NumStanzasOut, mgmt_ack_timeout := AckTimeout} = State) -> @@ -769,13 +771,13 @@ get_max_ack_queue(Host) -> gen_mod:get_module_opt(Host, ?MODULE, max_ack_queue, 1000). get_ack_timeout(Host) -> - case gen_mod:get_module_opt(Host, ?MODULE, ack_timeout, 60) of % change default + case gen_mod:get_module_opt(Host, ?MODULE, ack_timeout, 60) of infinity -> infinity; T -> timer:seconds(T) end. get_connection_timeout(Host) -> - gen_mod:get_module_opt(Host, ?MODULE, connection_timeout, 120000). + gen_mod:get_module_opt(Host, ?MODULE, connection_timeout, 300). mod_opt_type(connection_timeout) -> fun(I) when is_integer(I), I >= 0 -> I end; From 3aaac27bab5516e7ad861c2f30d66586682d22a0 Mon Sep 17 00:00:00 2001 From: amuhar Date: Wed, 23 Aug 2017 16:08:03 +0300 Subject: [PATCH 21/25] send sm_failed with number of incoming messages --- src/ejabberd_s2s_in.erl | 7 +- src/mod_stream_mgmt_s2s.erl | 194 +++++++++++++++++++----------------- 2 files changed, 107 insertions(+), 94 deletions(-) diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl index 30aa6d74fca..a2f9064f564 100644 --- a/src/ejabberd_s2s_in.erl +++ b/src/ejabberd_s2s_in.erl @@ -176,9 +176,7 @@ handle_stream_start(_StreamStart, #{lserver := LServer} = State) -> send(State, xmpp:serr_host_unknown()); true -> ServerHost = ejabberd_router:host_of_route(LServer), - UniqueId = p1_time_compat:unique_integer(), - State1 = State#{server_host => ServerHost, - unique_id => UniqueId}, % for xep-0198 s2s + State1 = State#{server_host => ServerHost}, ejabberd_hooks:run_fold(s2s_in_stream_started, ServerHost, State1, []) end. @@ -187,7 +185,8 @@ handle_stream_end(Reason, #{server_host := LServer} = State) -> ejabberd_hooks:run_fold(s2s_in_closed, LServer, State1, [Reason]). handle_stream_established(State) -> - set_idle_timeout(State#{established => true}). + UniqueId = p1_time_compat:unique_integer(), % for xep-0198 s2s + set_idle_timeout(State#{established => true, unique_id => UniqueId}). handle_auth_success(RServer, Mech, _AuthModule, #{sockmod := SockMod, diff --git a/src/mod_stream_mgmt_s2s.erl b/src/mod_stream_mgmt_s2s.erl index 3c8842899cd..061da60cd06 100644 --- a/src/mod_stream_mgmt_s2s.erl +++ b/src/mod_stream_mgmt_s2s.erl @@ -27,8 +27,6 @@ is_record(Pkt, sm_a) or is_record(Pkt, sm_r)). -% replace to separate file - -record(s2s, {fromto = {<<"">>, <<"">>} :: {binary(), binary()} | '_', pid = self() :: pid() | '_' | '$1'}). @@ -55,6 +53,7 @@ start(Host, _Opts) -> ejabberd_hooks:add(s2s_out_established, Host, ?MODULE, s2s_out_established, 50), %% server part + ets_cache:new(sm_s2s), ejabberd_hooks:add(s2s_in_stream_started, Host, ?MODULE, s2s_in_stream_started, 50), ejabberd_hooks:add(s2s_in_post_auth_features, @@ -204,7 +203,8 @@ s2s_out_packet(#{mgmt_state := MgmtState} = State, Pkt) s2s_out_packet(State, _Pkt) -> State. -s2s_out_handle_send(#{mgmt_state := MgmtState, lang := Lang} = State, Pkt, SendResult) +s2s_out_handle_send(#{mgmt_state := MgmtState, + lang := Lang} = State, Pkt, SendResult) when MgmtState == active; MgmtState == wait_for_enabled; MgmtState == pending -> @@ -255,22 +255,26 @@ s2s_out_handle_recv(#{mgmt_state := pending, mgmt_timeout := Timeout, mgmt_xmlns := Xmlns, mgmt_queue := Queue, - mgmt_prev_session := OldState} = State, _El, #sm_failed{}) -> + mgmt_prev_session := OldState} = State, + _El, #sm_failed{h = H}) -> ?DEBUG("Remote server ~s can't resume previous session", [RServer]), - - #{mgmt_queue := OldQueue, mgmt_stanzas_out := OldNumStanzasOut} = OldState, - + #{mgmt_queue := OldQueue, + mgmt_stanzas_out := OldNumStanzasOut} = + case H of + undefined-> + OldState; + _ -> + check_h_attribute(OldState, H) + end, {NewNumStanzasOut, NewQueue} = p1_queue:foldl( fun({_, Time, Pkt}, {AccNum, AccQueue}) -> Num = AccNum + 1, {Num, p1_queue:in({Num, Time, Pkt}, AccQueue)} end, {OldNumStanzasOut, OldQueue}, Queue), - State1 = State#{mgmt_state => wait_for_enabled, mgmt_queue => NewQueue, mgmt_stanzas_out => NewNumStanzasOut}, - State2 = maps:remove(mgmt_prev_session, State1), State3 = if Timeout > 0 -> @@ -285,13 +289,13 @@ s2s_out_handle_recv(State, _El, _Pkt) -> State. s2s_out_closed(#{mgmt_state := connecting} = State, _) -> - {stop, transition_to_resume(State#{stream_state => connecting})}; + {stop, transition_to_connecting(State#{stream_state => connecting})}; s2s_out_closed(State, _) -> State. s2s_out_terminate(#{mgmt_state := active, mgmt_queue := Queue} = State, _Reason) when ?qlen(Queue) > 0 -> - transition_to_resume(State); + transition_to_connecting(State); s2s_out_terminate(#{mgmt_state := timeout, mgmt_prev_session := OldState} = State, _Reason) -> #{mgmt_queue := Queue} = OldState, @@ -365,10 +369,15 @@ s2s_in_terminate(#{mgmt_state := resumed, remote_server := RServer} = State, _Reason) -> ?INFO_MSG("Closing former stream of resumed session for ~s", [RServer]), State; +s2s_in_terminate(#{mgmt_state := pending, + server_host := Server, + mgmt_stanzas_in := H} = State, _Reason) -> + ResumeId = make_resume_id(State), + ets_cache:insert_new(sm_s2s, {Server, ResumeId}, H), + State; s2s_in_terminate(State, _Reason) -> State. - %%%============================================================================= %%% Internal functions %%%============================================================================= @@ -450,18 +459,18 @@ handle_enable(#{remote_server := RServer, true -> DefaultTimeout end, - Res = if Timeout > 0 -> - ?INFO_MSG("Stream management with " - "resumption enabled for ~s", [RServer]), - #sm_enabled{resume = true, - id = make_resume_id(State), - max = Timeout, xmlns = Xmlns}; - true -> - ?INFO_MSG("Stream management enabled for ~s", [RServer]), - #sm_enabled{xmlns = Xmlns} - end, State1 = State#{mgmt_state => active, mgmt_timeout => Timeout}, + Res = + if Timeout > 0 -> + ?INFO_MSG("Stream management with " + "resumption enabled for ~s", [RServer]), + #sm_enabled{resume = true, id = make_resume_id(State), + max = Timeout, xmlns = Xmlns}; + true -> + ?INFO_MSG("Stream management enabled for ~s", [RServer]), + #sm_enabled{xmlns = Xmlns} + end, send(State1, Res). -spec handle_enabled(state(), sm_enabled()) -> state(). @@ -508,16 +517,16 @@ handle_a(State, #sm_a{h = H}) -> make_resume_id(#{remote_server := RServer, unique_id := UniqueId}) -> misc:term_to_base64({RServer, UniqueId}). --spec transition_to_resume(state()) -> state(). -transition_to_resume(#{mgmt_state := active, - mgmt_queue := Queue, - mgmt_timeout := 0} = State) -> +-spec transition_to_connecting(state()) -> state(). +transition_to_connecting(#{mgmt_state := active, + mgmt_queue := Queue, + mgmt_timeout := 0} = State) -> route_unacked_stanzas(State, Queue), State; -transition_to_resume(#{mgmt_state := active, - server_host := Server, - remote_server := RServer, - mgmt_connection_timeout := Timeout} = State) -> +transition_to_connecting(#{mgmt_state := active, + server_host := Server, + remote_server := RServer, + mgmt_connection_timeout := Timeout} = State) -> State1 = mod_stream_mgmt:cancel_ack_timer(State), ?DEBUG("Try to connect to remote server ~s", [RServer]), case resume(Server, RServer, [{resume, State1}]) of @@ -527,10 +536,10 @@ transition_to_resume(#{mgmt_state := active, _ -> State1 end; -transition_to_resume(#{mgmt_state := connecting, mod := Mod} = State) -> +transition_to_connecting(#{mgmt_state := connecting, mod := Mod} = State) -> Mod:connect(self()), State; -transition_to_resume(State) -> +transition_to_connecting(State) -> State. -spec transition_to_pending(state()) -> state(). @@ -538,7 +547,8 @@ transition_to_pending(#{mgmt_state := active, mod := Mod, mgmt_timeout := 0} = State) -> Mod:stop(State); transition_to_pending(#{mgmt_state := active, - remote_server := RServer, mgmt_timeout := Timeout} = State) -> + remote_server := RServer, + mgmt_timeout := Timeout} = State) -> ?INFO_MSG("Waiting for resumption of stream for ~s", [RServer]), erlang:start_timer(timer:seconds(Timeout), self(), pending_timeout), State#{mgmt_state => pending}; @@ -561,61 +571,71 @@ resume(From, To, Opts) -> error end. -get_old_session_state(_ResumeId, []) -> - {error, <<"Previous session PID not found">>}; -get_old_session_state({RServer, UniqueId} = ResumeId, [{_, Pid, _, _}|Specs]) - when is_pid(Pid), Pid /= self() -> - try gen_fsm:sync_send_all_state_event(Pid, - {resume_session, RServer, UniqueId}) of - {resume, OldState} -> - ejabberd_s2s_in:stop(Pid), - {ok, OldState}; - error -> - get_old_session_state(ResumeId, Specs) - catch - _:_ -> - get_old_session_state(ResumeId, Specs) +get_old_session_state(#{server_host := Server}, ResumeId, []) -> + case ets_cache:lookup(sm_s2s, {Server, ResumeId}) of + {ok, H} -> + {error, <<"Previous session timed out">>, H}; + _ -> + {error, <<"Previous session PID not found">>} end; -get_old_session_state(ResumeId, [_H|L]) -> - get_old_session_state(ResumeId, L). - -handle_resume(#sm_resume{previd = ResumeId, xmlns = Xmlns}, - #{lang := Lang, remote_server := RServer} = State) -> +get_old_session_state(State, ResumeId, [{_, Pid, _,_}|Specs]) + when is_pid(Pid), Pid /= self() -> case misc:base64_to_term(ResumeId) of {term, {RServer, UniqueId}} -> - case get_old_session_state({RServer, UniqueId}, - supervisor:which_children(ejabberd_s2s_in_sup)) of - {ok, OldState} -> - #{mgmt_stanzas_in := H, - mgmt_timeout := Timeout, - mgmt_xmlns := AttrXmlns} = OldState, - - State1 = State#{mgmt_state => active, - mgmt_stanzas_in => H, - mgmt_timeout => Timeout, - mgmt_xmlns => AttrXmlns, - unique_id => UniqueId}, - - State2 = send(State1, #sm_resumed{previd = ResumeId, - h = H, - xmlns = AttrXmlns}), - ?INFO_MSG("Resumed session for ~s", [RServer]), - State2; - {error, Msg} -> - ?INFO_MSG("Cannot resume session for ~s: ~s", [RServer, Msg]), - Err = #sm_failed{reason = 'item-not-found', - text = xmpp:mk_text(Msg, Lang), - xmlns = Xmlns}, - send(State, Err) + try gen_fsm:sync_send_all_state_event(Pid, + {resume_session, RServer, UniqueId}) of + {resume, OldState} -> + ejabberd_s2s_in:stop(Pid), + {ok, OldState}; + error -> + get_old_session_state(State, ResumeId, Specs) + catch + _:_ -> + get_old_session_state(State, ResumeId, Specs) end; _ -> - Msg = <<"Invalid 'previd' value">>, + {error, <<"Invalid 'previd' value">>} + end; +get_old_session_state(State, ResumeId, [_H|L]) -> + get_old_session_state(State, ResumeId, L). + +handle_resume(#sm_resume{previd = ResumeId, xmlns = Xmlns}, + #{remote_server := RServer, lang := Lang} = State) -> + Res = case get_old_session_state(State, ResumeId, + supervisor:which_children(ejabberd_s2s_in_sup)) of + {ok, OldState} -> + {ok, OldState}; + {error, Err, InH} -> + {error, #sm_failed{reason = 'item-not-found', + text = xmpp:mk_text(Err, Lang), + h = InH, xmlns = Xmlns}, Err}; + {error, Err} -> + {error, #sm_failed{reason = 'item-not-found', + text = xmpp:mk_text(Err, Lang), + xmlns = Xmlns}, Err} + end, + case Res of + {ok, OldSessionState} -> + #{mgmt_stanzas_in := H, + mgmt_timeout := Timeout, + mgmt_xmlns := AttrXmlns, + unique_id := UniqueId} = OldSessionState, + + State1 = State#{mgmt_state => active, + mgmt_stanzas_in => H, + mgmt_timeout => Timeout, + mgmt_xmlns => AttrXmlns, + unique_id => UniqueId}, + + State2 = send(State1, #sm_resumed{previd = ResumeId, + h = H, + xmlns = AttrXmlns}), + ?INFO_MSG("Resumed session for ~s", [RServer]), + State2; + {error, El, Msg} -> ?INFO_MSG("Cannot resume session for ~s: ~s", [RServer, Msg]), - Err = #sm_failed{reason = 'item-not-found', - text = xmpp:mk_text(Msg, Lang), - xmlns = Xmlns}, - send(State, Err) - end. + send(State, El) + end. -spec handle_resumed(sm_resumed(), state()) -> state(). handle_resumed(#sm_resumed{h = H, previd = _Id}, @@ -623,26 +643,20 @@ handle_resumed(#sm_resumed{h = H, previd = _Id}, mgmt_queue := Queue, mgmt_prev_session := OldState} = State) -> ResumedState = copy_state(OldState, State), - #{mgmt_xmlns := Xmlns, mgmt_queue := OldQueue, mgmt_stanzas_out := OldNumStanzasOut} = ResumedState, - State1 = check_h_attribute(ResumedState, H), - {NewNumStanzasOut, NewQueue} = p1_queue:foldl( fun({_, Time, Pkt}, {AccNum, AccQueue}) -> Num = AccNum + 1, {Num, p1_queue:in({Num, Time, Pkt}, AccQueue)} end, {OldNumStanzasOut, OldQueue}, Queue), - - State2 = State1#{mgmt_state => wait_for_enabled, + State2 = State1#{mgmt_state => active, mgmt_queue => NewQueue, mgmt_stanzas_out => NewNumStanzasOut}, - - State3 = resend_unacked_stanzas(State2#{mgmt_state => active}, - OldNumStanzasOut), + State3 = resend_unacked_stanzas(State2, OldNumStanzasOut), ?DEBUG("Resumed session for ~s", [RServer]), send(State3, #sm_r{xmlns = Xmlns}). From bc2b3cf19a097250202c7207869319480f6912b4 Mon Sep 17 00:00:00 2001 From: amuhar Date: Thu, 24 Aug 2017 01:43:18 +0300 Subject: [PATCH 22/25] test resume failed --- test/sms2s_tests.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/sms2s_tests.erl b/test/sms2s_tests.erl index b075fab2b8d..8599ac24167 100644 --- a/test/sms2s_tests.erl +++ b/test/sms2s_tests.erl @@ -72,7 +72,7 @@ resume_failed(Config) -> ct:sleep(30000), ct:comment("Trying to resume timed out session"), send(Config, #sm_resume{previd = ID, h = 0, xmlns = ?NS_STREAM_MGMT_3}), - #sm_failed{reason = 'item-not-found'} = recv(Config), + #sm_failed{reason = 'item-not-found', h = 4} = recv(Config), disconnect(Config). From 1f10a89955d57915345bc7a541411ad9e4eb1303 Mon Sep 17 00:00:00 2001 From: amuhar Date: Thu, 24 Aug 2017 19:48:34 +0300 Subject: [PATCH 23/25] fix comment --- src/ejabberd_s2s.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl index cb4e5e5ecfc..6b9f3a03fb4 100644 --- a/src/ejabberd_s2s.erl +++ b/src/ejabberd_s2s.erl @@ -427,7 +427,7 @@ start_connection(From, To, Opts) -> MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode, Opts); true -> - %% We choose a connexion from the pool of opened ones. + %% We choose a connection from the pool of opened ones. {ok, choose_connection(From, L)} end end. From ee9902f5fd2061549a40ab5aa9fa30d670be381d Mon Sep 17 00:00:00 2001 From: amuhar Date: Fri, 25 Aug 2017 14:11:35 +0300 Subject: [PATCH 24/25] minor changes --- src/mod_stream_mgmt_s2s.erl | 13 +++++-------- test/ejabberd_SUITE_data/ejabberd.yml | 2 ++ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/mod_stream_mgmt_s2s.erl b/src/mod_stream_mgmt_s2s.erl index 061da60cd06..de996a2c11a 100644 --- a/src/mod_stream_mgmt_s2s.erl +++ b/src/mod_stream_mgmt_s2s.erl @@ -117,7 +117,6 @@ s2s_out_stream_init({ok, #{server_host := ServerHost} = State}, Opts) -> mgmt_connection_timeout => get_connection_timeout(ServerHost), mgmt_stanzas_out => 0, mgmt_stanzas_req => 0}, - case proplists:get_value(resume, Opts) of OldState when OldState /= undefined -> {ok, State1#{mgmt_state => connecting, mgmt_prev_session => OldState}}; @@ -413,7 +412,6 @@ negotiate_stream_mgmt(Pkt, State) -> _ -> {true, xmpp:get_ns(Pkt)} end, - case Pkt of #sm_enable{} when ServerPart -> handle_enable(State#{mgmt_xmlns => Xmlns}, Pkt); @@ -620,7 +618,6 @@ handle_resume(#sm_resume{previd = ResumeId, xmlns = Xmlns}, mgmt_timeout := Timeout, mgmt_xmlns := AttrXmlns, unique_id := UniqueId} = OldSessionState, - State1 = State#{mgmt_state => active, mgmt_stanzas_in => H, mgmt_timeout => Timeout, @@ -701,12 +698,10 @@ copy_state(#{mgmt_xmlns := Xmlns, mgmt_stanzas_out := NumStanzasOut, mgmt_previd := Id} = _OldState, #{mgmt_queue_type := QueueType} = NewState) -> - Queue1 = case QueueType of ram -> p1_queue:file_to_ram(Queue); _ -> p1_queue:ram_to_file(Queue) end, - NewState#{mgmt_xmlns => Xmlns, mgmt_queue => Queue1, mgmt_stanzas_out => NumStanzasOut, @@ -718,12 +713,14 @@ check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, when H > NumStanzasOut -> ?DEBUG("~s acknowledged ~B stanzas," "but only ~B were sent ", [RServer, H, NumStanzasOut]), - mod_stream_mgmt:mgmt_queue_drop(State#{mgmt_stanzas_out => H}, NumStanzasOut); + % mod_stream_mgmt:mgmt_queue_drop(State#{mgmt_stanzas_out => H}, NumStanzasOut); + State#{mgmt_stanzas_out => H}; check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, remote_server := RServer} = State, H) -> ?DEBUG("~s acknowledged ~B of ~B " "stanzas", [RServer, H, NumStanzasOut]), - mod_stream_mgmt:mgmt_queue_drop(State, H). + State. + % mod_stream_mgmt:mgmt_queue_drop(State, H). -spec add_resent_delay_info(state(), stanza(), erlang:timestamp()) -> stanza(). add_resent_delay_info(#{server_host := LServer}, El, Time) -> @@ -753,7 +750,7 @@ resend_rack(#{mgmt_ack_timer := _, mgmt_queue := Queue, mgmt_stanzas_out := NumStanzasOut, mgmt_stanzas_req := NumStanzasReq} = State) -> - State1 = mod_stream_mgmt:cancel_ack_timer(State), + State1 = State , %mod_stream_mgmt:cancel_ack_timer(State), case NumStanzasReq < NumStanzasOut andalso not p1_queue:is_empty(Queue) of true -> send_rack(State1); false -> State1 diff --git a/test/ejabberd_SUITE_data/ejabberd.yml b/test/ejabberd_SUITE_data/ejabberd.yml index a648cb4223c..0577b70ca27 100644 --- a/test/ejabberd_SUITE_data/ejabberd.yml +++ b/test/ejabberd_SUITE_data/ejabberd.yml @@ -488,6 +488,8 @@ Welcome to this XMPP server." mod_stream_mgmt: max_ack_queue: 10 resume_timeout: 3 + mod_stream_mgmt_s2s: + resume_timeout: 3 mod_time: [] mod_version: [] registration_timeout: infinity From 1548db3492d20ca9f9d6991c5387532bddcf64a1 Mon Sep 17 00:00:00 2001 From: amuhar Date: Sun, 27 Aug 2017 14:46:23 +0300 Subject: [PATCH 25/25] drop acknowledged stanzas from queue --- src/mod_stream_mgmt_s2s.erl | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/mod_stream_mgmt_s2s.erl b/src/mod_stream_mgmt_s2s.erl index de996a2c11a..18edd0c87bb 100644 --- a/src/mod_stream_mgmt_s2s.erl +++ b/src/mod_stream_mgmt_s2s.erl @@ -713,14 +713,12 @@ check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, when H > NumStanzasOut -> ?DEBUG("~s acknowledged ~B stanzas," "but only ~B were sent ", [RServer, H, NumStanzasOut]), - % mod_stream_mgmt:mgmt_queue_drop(State#{mgmt_stanzas_out => H}, NumStanzasOut); - State#{mgmt_stanzas_out => H}; + mod_stream_mgmt:mgmt_queue_drop(State#{mgmt_stanzas_out => H}, NumStanzasOut); check_h_attribute(#{mgmt_stanzas_out := NumStanzasOut, remote_server := RServer} = State, H) -> ?DEBUG("~s acknowledged ~B of ~B " "stanzas", [RServer, H, NumStanzasOut]), - State. - % mod_stream_mgmt:mgmt_queue_drop(State, H). + mod_stream_mgmt:mgmt_queue_drop(State, H). -spec add_resent_delay_info(state(), stanza(), erlang:timestamp()) -> stanza(). add_resent_delay_info(#{server_host := LServer}, El, Time) -> @@ -750,7 +748,7 @@ resend_rack(#{mgmt_ack_timer := _, mgmt_queue := Queue, mgmt_stanzas_out := NumStanzasOut, mgmt_stanzas_req := NumStanzasReq} = State) -> - State1 = State , %mod_stream_mgmt:cancel_ack_timer(State), + State1 = mod_stream_mgmt:cancel_ack_timer(State), case NumStanzasReq < NumStanzasOut andalso not p1_queue:is_empty(Queue) of true -> send_rack(State1); false -> State1