From 600a56059a879a1714cd7f93bcb19f955fe91bca Mon Sep 17 00:00:00 2001 From: Fredrik Gustafsson Date: Tue, 30 Oct 2012 11:41:06 +0100 Subject: Timeout after 1h of idle on connection, which exits the connection --- lib/ssh/src/ssh_connection_manager.erl | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/lib/ssh/src/ssh_connection_manager.erl b/lib/ssh/src/ssh_connection_manager.erl index 422d9356d5..2d0830857f 100644 --- a/lib/ssh/src/ssh_connection_manager.erl +++ b/lib/ssh/src/ssh_connection_manager.erl @@ -62,6 +62,7 @@ latest_channel_id = 0, opts, channel_args, + timer_ref, % timerref connected }). @@ -203,6 +204,7 @@ init([client, Opts]) -> ChannelPid = proplists:get_value(channel_pid, Opts), self() ! {start_connection, client, [Parent, Address, Port, SocketOpts, Options]}, + TimerRef = erlang:send_after(3600000, self(), {'EXIT', [], "Timeout"}), {ok, #state{role = client, client = ChannelPid, connection_state = #connection{channel_cache = Cache, @@ -211,6 +213,7 @@ init([client, Opts]) -> connection_supervisor = Parent, requests = []}, opts = Opts, + timer_ref = TimerRef, connected = false}}. %%-------------------------------------------------------------------- @@ -358,7 +361,7 @@ handle_call({open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data}, recv_packet_size = MaxPacketSize}, ssh_channel:cache_update(Cache, Channel), State = add_request(true, ChannelId, From, State1), - {noreply, State}; + {noreply, remove_timer_ref(State)}; handle_call({send_window, ChannelId}, _From, #state{connection_state = @@ -403,6 +406,7 @@ handle_call({close, ChannelId}, _, send_msg({connection_reply, Pid, ssh_connection:channel_close_msg(Id)}), ssh_channel:cache_update(Cache, Channel#channel{sent_close = true}), + erlang:send_after(3600000, self(), {check_cache, [], []}), {reply, ok, State}; undefined -> {reply, ok, State} @@ -523,7 +527,10 @@ handle_info({start_connection, client, Pid ! {self(), not_connected, Reason}, {stop, {shutdown, normal}, State} end; - +handle_info({check_cache, _ , _}, + #state{connection_state = + #connection{channel_cache = Cache}} = State) -> + {noreply, check_cache(State, Cache)}; handle_info({ssh_cm, _Sender, Msg}, State0) -> %% Backwards compatibility! State = cm_message(Msg, State0), @@ -580,6 +587,24 @@ code_change(_OldVsn, State, _Extra) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- +check_cache(State, Cache) -> + %% Check the number of entries in Cache + case proplists:get_value(size, ets:info(Cache)) of + 0 -> + TimerRef = erlang:send_after(3600000, self(), {'EXIT', [], "Timeout"}), + State#state{timer_ref=TimerRef}; + _ -> + State + end. +remove_timer_ref(State) -> + case State#state.timer_ref of + undefined -> + State; + _ -> + TimerRef = State#state.timer_ref, + erlang:cancel_timer(TimerRef), + State#state{timer_ref = undefined} + end. channel_data(Id, Type, Data, Connection0, ConnectionPid, From, State) -> case ssh_connection:channel_data(Id, Type, Data, Connection0, ConnectionPid, From) of @@ -677,7 +702,7 @@ handle_channel_down(ChannelPid, #state{connection_state = (_,Acc) -> Acc end, [], Cache), - {{replies, []}, State}. + {{replies, []}, check_cache(State, Cache)}. update_sys(Cache, Channel, Type, ChannelPid) -> ssh_channel:cache_update(Cache, -- cgit v1.2.3 From 7e6bfd3101d25d4a8061ab7a59002740ee021376 Mon Sep 17 00:00:00 2001 From: Fredrik Gustafsson Date: Tue, 30 Oct 2012 17:25:34 +0100 Subject: Option idle_time introduced, it will trigger the timer and if it is not given the timer_ref entry is infinity --- lib/ssh/src/ssh.erl | 13 +++++++++++- lib/ssh/src/ssh_connection_manager.erl | 36 ++++++++++++++++++++++++---------- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 3395f73884..db753e0e4e 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -79,7 +79,7 @@ connect(Host, Port, Options, Timeout) -> DisableIpv6 = proplists:get_value(ip_v6_disabled, SshOptions, false), Inet = inetopt(DisableIpv6), do_connect(Host, Port, [Inet | SocketOptions], - [{host, Host} | SshOptions], Timeout, DisableIpv6) + [{host, Host} | fix_idle_time(SshOptions)], Timeout, DisableIpv6) end. do_connect(Host, Port, SocketOptions, SshOptions, Timeout, DisableIpv6) -> @@ -237,6 +237,13 @@ shell(Host, Port, Options) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- +fix_idle_time(SshOptions) -> + case proplists:get_value(idle_time, SshOptions) of + undefined -> + [{idle_time, infinity}|SshOptions]; + _ -> + SshOptions + end. start_daemon(Host, Port, Options, Inet) -> case handle_options(Options) of {error, _Reason} = Error -> @@ -342,6 +349,8 @@ handle_option([{exec, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{auth_methods, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); +handle_option([{idle_time, _} = Opt | Rest], SocketOptions, SshOptions) -> + handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, [handle_inet_option(Opt) | SocketOptions], SshOptions). @@ -407,6 +416,8 @@ handle_ssh_option({shell, {Module, Function, _}} = Opt) when is_atom(Module), Opt; handle_ssh_option({shell, Value} = Opt) when is_function(Value) -> Opt; +handle_ssh_option({idle_time, Value} = Opt) when is_integer(Value), Value > 0 -> + Opt; handle_ssh_option(Opt) -> throw({error, {eoptions, Opt}}). diff --git a/lib/ssh/src/ssh_connection_manager.erl b/lib/ssh/src/ssh_connection_manager.erl index 2d0830857f..2e62312423 100644 --- a/lib/ssh/src/ssh_connection_manager.erl +++ b/lib/ssh/src/ssh_connection_manager.erl @@ -62,7 +62,7 @@ latest_channel_id = 0, opts, channel_args, - timer_ref, % timerref + idle_timer_ref, % timerref connected }). @@ -204,7 +204,8 @@ init([client, Opts]) -> ChannelPid = proplists:get_value(channel_pid, Opts), self() ! {start_connection, client, [Parent, Address, Port, SocketOpts, Options]}, - TimerRef = erlang:send_after(3600000, self(), {'EXIT', [], "Timeout"}), + TimerRef = get_idle_time(Options), + {ok, #state{role = client, client = ChannelPid, connection_state = #connection{channel_cache = Cache, @@ -213,7 +214,7 @@ init([client, Opts]) -> connection_supervisor = Parent, requests = []}, opts = Opts, - timer_ref = TimerRef, + idle_timer_ref = TimerRef, connected = false}}. %%-------------------------------------------------------------------- @@ -406,7 +407,13 @@ handle_call({close, ChannelId}, _, send_msg({connection_reply, Pid, ssh_connection:channel_close_msg(Id)}), ssh_channel:cache_update(Cache, Channel#channel{sent_close = true}), - erlang:send_after(3600000, self(), {check_cache, [], []}), + SshOpts = proplists:get_value(ssh_opts, State#state.opts), + case proplists:get_value(idle_time, SshOpts) of + infinity -> + ok; + _IdleTime -> + erlang:send_after(5000, self(), {check_cache, [], []}) + end, {reply, ok, State}; undefined -> {reply, ok, State} @@ -587,23 +594,32 @@ code_change(_OldVsn, State, _Extra) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- +get_idle_time(SshOptions) -> + case proplists:get_value(idle_time, SshOptions) of + infinity -> + infinity; + IdleTime -> + TimerRef = erlang:send_after(IdleTime, self(), {'EXIT', [], "Timeout"}), + TimerRef + end. check_cache(State, Cache) -> %% Check the number of entries in Cache case proplists:get_value(size, ets:info(Cache)) of 0 -> - TimerRef = erlang:send_after(3600000, self(), {'EXIT', [], "Timeout"}), - State#state{timer_ref=TimerRef}; + Opts = proplists:get_value(ssh_opts, State#state.opts), + TimerRef = erlang:send_after(proplists:get_value(idle_time, Opts), self(), {'EXIT', [], "Timeout"}), + State#state{idle_timer_ref=TimerRef}; _ -> State end. remove_timer_ref(State) -> - case State#state.timer_ref of - undefined -> + case State#state.idle_timer_ref of + infinity -> State; _ -> - TimerRef = State#state.timer_ref, + TimerRef = State#state.idle_timer_ref, erlang:cancel_timer(TimerRef), - State#state{timer_ref = undefined} + State#state{idle_timer_ref = undefined} end. channel_data(Id, Type, Data, Connection0, ConnectionPid, From, State) -> case ssh_connection:channel_data(Id, Type, Data, Connection0, -- cgit v1.2.3 From b788929349f4eec4980dd2181d92f7df3687db85 Mon Sep 17 00:00:00 2001 From: Fredrik Gustafsson Date: Wed, 31 Oct 2012 09:34:18 +0100 Subject: handle no idle-timer on check cache --- lib/ssh/src/ssh_connection_manager.erl | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/ssh/src/ssh_connection_manager.erl b/lib/ssh/src/ssh_connection_manager.erl index 2e62312423..6e2fe9da74 100644 --- a/lib/ssh/src/ssh_connection_manager.erl +++ b/lib/ssh/src/ssh_connection_manager.erl @@ -607,8 +607,15 @@ check_cache(State, Cache) -> case proplists:get_value(size, ets:info(Cache)) of 0 -> Opts = proplists:get_value(ssh_opts, State#state.opts), - TimerRef = erlang:send_after(proplists:get_value(idle_time, Opts), self(), {'EXIT', [], "Timeout"}), - State#state{idle_timer_ref=TimerRef}; + case proplists:get_value(idle_time, Opts) of + infinity -> + State; + undefined -> + State; + Time -> + TimerRef = erlang:send_after(Time, self(), {'EXIT', [], "Timeout"}), + State#state{idle_timer_ref=TimerRef} + end; _ -> State end. -- cgit v1.2.3 From 8e8b61c63431096cc8fdd4eaea7d7ed33c143847 Mon Sep 17 00:00:00 2001 From: Fredrik Gustafsson Date: Wed, 31 Oct 2012 10:45:16 +0100 Subject: Check cache on channel exec --- lib/ssh/src/ssh_connection_manager.erl | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/ssh/src/ssh_connection_manager.erl b/lib/ssh/src/ssh_connection_manager.erl index 6e2fe9da74..9a96542505 100644 --- a/lib/ssh/src/ssh_connection_manager.erl +++ b/lib/ssh/src/ssh_connection_manager.erl @@ -234,6 +234,13 @@ handle_call({request, ChannelPid, ChannelId, Type, Data}, From, State0) -> %% channel is sent later when reply arrives from the connection %% handler. lists:foreach(fun send_msg/1, Replies), + SshOpts = proplists:get_value(ssh_opts, State0#state.opts), + case proplists:get_value(idle_time, SshOpts) of + infinity -> + ok; + _IdleTime -> + erlang:send_after(5000, self(), {check_cache, [], []}) + end, {noreply, State}; handle_call({request, ChannelId, Type, Data}, From, State0) -> @@ -613,18 +620,24 @@ check_cache(State, Cache) -> undefined -> State; Time -> - TimerRef = erlang:send_after(Time, self(), {'EXIT', [], "Timeout"}), - State#state{idle_timer_ref=TimerRef} + case State#state.idle_timer_ref of + undefined -> + TimerRef = erlang:send_after(Time, self(), {'EXIT', [], "Timeout"}), + State#state{idle_timer_ref=TimerRef}; + _ -> + State + end end; _ -> State end. remove_timer_ref(State) -> case State#state.idle_timer_ref of - infinity -> + infinity -> %% If the timer is not activated State; - _ -> - TimerRef = State#state.idle_timer_ref, + undefined -> %% If we already has cancelled the timer + State; + TimerRef -> %% Timer is active erlang:cancel_timer(TimerRef), State#state{idle_timer_ref = undefined} end. -- cgit v1.2.3 From 9c9e054f56b9bf05dfca8697e5df318e2ce6a3cd Mon Sep 17 00:00:00 2001 From: Fredrik Gustafsson Date: Wed, 31 Oct 2012 14:51:42 +0100 Subject: Doc about idle_time option to ssh:connect --- lib/ssh/doc/src/ssh.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml index 0133250979..07f657da70 100644 --- a/lib/ssh/doc/src/ssh.xml +++ b/lib/ssh/doc/src/ssh.xml @@ -184,7 +184,10 @@ (simply passed on to the transport protocol).

-

Determines if SSH shall use IPv6 or not.

+

Determines if SSH shall use IPv6 or not.

+ + +

Sets a timeout on connection when not used and no channels are active, default is infinity

-- cgit v1.2.3 From 104d902c2e325060ab66abc05a449194792dee7a Mon Sep 17 00:00:00 2001 From: Fredrik Gustafsson Date: Mon, 5 Nov 2012 11:39:12 +0100 Subject: Not start the idle timer on connect --- lib/ssh/src/ssh_connection_manager.erl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/ssh/src/ssh_connection_manager.erl b/lib/ssh/src/ssh_connection_manager.erl index 9a96542505..1384740bfa 100644 --- a/lib/ssh/src/ssh_connection_manager.erl +++ b/lib/ssh/src/ssh_connection_manager.erl @@ -605,9 +605,8 @@ get_idle_time(SshOptions) -> case proplists:get_value(idle_time, SshOptions) of infinity -> infinity; - IdleTime -> - TimerRef = erlang:send_after(IdleTime, self(), {'EXIT', [], "Timeout"}), - TimerRef + _IdleTime -> %% We dont want to set the timeout on first connect + undefined end. check_cache(State, Cache) -> %% Check the number of entries in Cache -- cgit v1.2.3 From 6a17960f0610ebaa332cf7f2037d01df47f5f5d8 Mon Sep 17 00:00:00 2001 From: Fredrik Gustafsson Date: Mon, 5 Nov 2012 12:40:02 +0100 Subject: Added testcase for idle timer --- lib/ssh/test/ssh_basic_SUITE.erl | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl index 7a641c92c1..61b91e3a25 100644 --- a/lib/ssh/test/ssh_basic_SUITE.erl +++ b/lib/ssh/test/ssh_basic_SUITE.erl @@ -42,6 +42,7 @@ all() -> {group, dsa_pass_key}, {group, rsa_pass_key}, {group, internal_error}, + idle_time, daemon_already_started, server_password_option, server_userpassword_option, @@ -234,7 +235,28 @@ exec_compressed(Config) when is_list(Config) -> ssh:stop_daemon(Pid). %%-------------------------------------------------------------------- - +idle_time(doc) -> + ["Idle timeout test"]; +idle_time(Config) -> + SystemDir = filename:join(?config(priv_dir, Config), system), + UserDir = ?config(priv_dir, Config), + + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, + {user_dir, UserDir}, + {failfun, fun ssh_test_lib:failfun/2}]), + ConnectionRef = + ssh:connect(Host, Port, [{silently_accept_hosts, true}, + {user_dir, UserDir}, + {user_interaction, false}, + {idle_time, 3000}]), + {ok, Id} = ssh_connection:session_channel(ConnectionRef, 1000), + ssh_connection:close(ConnectionRef, Id), + receive + after 10000 -> + {error,channel_closed} = ssh_connection:session_channel(ConnectionRef, 1000) + end, + ssh:stop_daemon(Pid). +%%-------------------------------------------------------------------- shell(doc) -> ["Test that ssh:shell/2 works"]; shell(Config) when is_list(Config) -> -- cgit v1.2.3 From f83a7daeb2b7c913688f62dfd78d7f68bcdb542b Mon Sep 17 00:00:00 2001 From: Fredrik Gustafsson Date: Mon, 5 Nov 2012 13:04:49 +0100 Subject: Fixed doc --- lib/ssh/doc/src/ssh.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml index 07f657da70..30d4f45a32 100644 --- a/lib/ssh/doc/src/ssh.xml +++ b/lib/ssh/doc/src/ssh.xml @@ -187,7 +187,7 @@

Determines if SSH shall use IPv6 or not.

-

Sets a timeout on connection when not used and no channels are active, default is infinity

+

Sets a timeout on connection when no channels are active, default is infinity

-- cgit v1.2.3 From 7e1454df69201236ccf2f0955ca1ddce727f07c3 Mon Sep 17 00:00:00 2001 From: Fredrik Gustafsson Date: Tue, 6 Nov 2012 15:10:22 +0100 Subject: Use same connect as the rest of testcases --- lib/ssh/test/ssh_basic_SUITE.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl index 61b91e3a25..0ae7874c4b 100644 --- a/lib/ssh/test/ssh_basic_SUITE.erl +++ b/lib/ssh/test/ssh_basic_SUITE.erl @@ -245,7 +245,7 @@ idle_time(Config) -> {user_dir, UserDir}, {failfun, fun ssh_test_lib:failfun/2}]), ConnectionRef = - ssh:connect(Host, Port, [{silently_accept_hosts, true}, + ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, {user_dir, UserDir}, {user_interaction, false}, {idle_time, 3000}]), -- cgit v1.2.3 From 49fef147845655e44f91cd4fdf4be92162e65710 Mon Sep 17 00:00:00 2001 From: Fredrik Gustafsson Date: Wed, 7 Nov 2012 13:41:26 +0100 Subject: New setup in testing idle_time --- lib/ssh/test/ssh_basic_SUITE.erl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl index 0ae7874c4b..5fec7f0cd7 100644 --- a/lib/ssh/test/ssh_basic_SUITE.erl +++ b/lib/ssh/test/ssh_basic_SUITE.erl @@ -42,15 +42,15 @@ all() -> {group, dsa_pass_key}, {group, rsa_pass_key}, {group, internal_error}, - idle_time, + {group, idle_time}, daemon_already_started, server_password_option, server_userpassword_option, close]. groups() -> - [{dsa_key, [], [send, exec, exec_compressed, shell, known_hosts]}, - {rsa_key, [], [send, exec, exec_compressed, shell, known_hosts]}, + [{dsa_key, [], [send, exec, exec_compressed, shell, known_hosts, idle_time]}, + {rsa_key, [], [send, exec, exec_compressed, shell, known_hosts, idle_time]}, {dsa_pass_key, [], [pass_phrase]}, {rsa_pass_key, [], [pass_phrase]}, {internal_error, [], [internal_error]} @@ -240,15 +240,14 @@ idle_time(doc) -> idle_time(Config) -> SystemDir = filename:join(?config(priv_dir, Config), system), UserDir = ?config(priv_dir, Config), - + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, {user_dir, UserDir}, {failfun, fun ssh_test_lib:failfun/2}]), ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, {user_dir, UserDir}, - {user_interaction, false}, - {idle_time, 3000}]), + {user_interaction, false}]), {ok, Id} = ssh_connection:session_channel(ConnectionRef, 1000), ssh_connection:close(ConnectionRef, Id), receive -- cgit v1.2.3