|
111 | 111 | stop_listener/1, |
112 | 112 | trap_exit_connect/1, |
113 | 113 | trap_exit_daemon/1, |
| 114 | + handler_down_before_open/1, |
114 | 115 | ssh_exec_echo/2 % called as an MFA |
115 | 116 | ]). |
116 | 117 |
|
@@ -182,7 +183,8 @@ all() -> |
182 | 183 | stop_listener, |
183 | 184 | no_sensitive_leak, |
184 | 185 | start_subsystem_on_closed_channel, |
185 | | - max_channels_option |
| 186 | + max_channels_option, |
| 187 | + handler_down_before_open |
186 | 188 | ]. |
187 | 189 | groups() -> |
188 | 190 | [{openssh, [], payload() ++ ptty() ++ sock()}]. |
@@ -1295,7 +1297,7 @@ simple_eval(Inp) -> {simple_eval,Inp}. |
1295 | 1297 |
|
1296 | 1298 | do_start_shell_exec_fun(Fun, Command, Expect, ExpectType, Config) -> |
1297 | 1299 | DefaultReceiveFun = |
1298 | | - fun(ConnectionRef, ChannelId, Expect, ExpectType) -> |
| 1300 | + fun(ConnectionRef, ChannelId, _Expect, _ExpectType) -> |
1299 | 1301 | receive |
1300 | 1302 | {ssh_cm, ConnectionRef, {data, ChannelId, ExpectType, Expect}} -> |
1301 | 1303 | ok |
@@ -1944,6 +1946,138 @@ max_channels_option(Config) when is_list(Config) -> |
1944 | 1946 | ssh:close(ConnectionRef), |
1945 | 1947 | ssh:stop_daemon(Pid). |
1946 | 1948 |
|
| 1949 | +handler_down_before_open(Config) -> |
| 1950 | + %% Start echo subsystem with a delay in init() - until a signal is received |
| 1951 | + %% One client opens a channel on the connection |
| 1952 | + %% the other client requests the echo subsystem on the second channel and then immediately goes down |
| 1953 | + %% the test monitors the client and when receiving 'DOWN' signals 'echo' to proceed |
| 1954 | + %% a) there should be no crash after 'channel-open-confirmation' |
| 1955 | + %% b) there should be proper 'channel-close' exchange |
| 1956 | + %% c) the 'exec' channel should not be affected after the 'echo' channel goes down |
| 1957 | + PrivDir = proplists:get_value(priv_dir, Config), |
| 1958 | + UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth |
| 1959 | + file:make_dir(UserDir), |
| 1960 | + SysDir = proplists:get_value(data_dir, Config), |
| 1961 | + Parent = self(), |
| 1962 | + EchoSS_spec = {ssh_echo_server, [8, [{dbg, true}, {parent, Parent}]]}, |
| 1963 | + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, |
| 1964 | + {user_dir, UserDir}, |
| 1965 | + {password, "morot"}, |
| 1966 | + {exec, fun ssh_exec_echo/1}, |
| 1967 | + {subsystems, [{"echo_n",EchoSS_spec}]}]), |
| 1968 | + ct:log("~p:~p connect", [?MODULE,?LINE]), |
| 1969 | + ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, |
| 1970 | + {user, "foo"}, |
| 1971 | + {password, "morot"}, |
| 1972 | + {user_interaction, false}, |
| 1973 | + {user_dir, UserDir}]), |
| 1974 | + ct:log("~p:~p connected", [?MODULE,?LINE]), |
| 1975 | + |
| 1976 | + ExecChannelPid = |
| 1977 | + spawn( |
| 1978 | + fun() -> |
| 1979 | + {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity), |
| 1980 | + |
| 1981 | + %% This is to get peer's connection handler PID ({conn_peer ...} below) and suspend it |
| 1982 | + {ok, ChannelId1} = ssh_connection:session_channel(ConnectionRef, infinity), |
| 1983 | + ssh_connection:subsystem(ConnectionRef, ChannelId1, "echo_n", infinity), |
| 1984 | + ssh_connection:close(ConnectionRef, ChannelId1), |
| 1985 | + receive |
| 1986 | + {ssh_cm, ConnectionRef, {closed, 1}} -> ok |
| 1987 | + end, |
| 1988 | + |
| 1989 | + Parent ! {self(), channelId, ChannelId0}, |
| 1990 | + Result = receive |
| 1991 | + cmd -> |
| 1992 | + ct:log("~p:~p Channel ~p executing", [?MODULE, ?LINE, ChannelId0]), |
| 1993 | + success = ssh_connection:exec(ConnectionRef, ChannelId0, "testing", infinity), |
| 1994 | + Expect = <<"echo testing\n">>, |
| 1995 | + ExpSz = size(Expect), |
| 1996 | + receive |
| 1997 | + {ssh_cm, ConnectionRef, {data, ChannelId0, 0, |
| 1998 | + <<Expect:ExpSz/binary, _/binary>>}} = R -> |
| 1999 | + ct:log("~p:~p Got expected ~p",[?MODULE,?LINE, R]), |
| 2000 | + ok; |
| 2001 | + Other -> |
| 2002 | + ct:log("~p:~p Got unexpected ~p~nExpect: ~p~n", |
| 2003 | + [?MODULE,?LINE, Other, {ssh_cm, ConnectionRef, |
| 2004 | + {data, ChannelId0, 0, Expect}}]), |
| 2005 | + {fail, "Unexpected data"} |
| 2006 | + after 5000 -> |
| 2007 | + {fail, "Exec Timeout"} |
| 2008 | + end; |
| 2009 | + stop -> {fail, "Stopped"} |
| 2010 | + end, |
| 2011 | + Parent ! {self(), Result} |
| 2012 | + end), |
| 2013 | + try |
| 2014 | + receive |
| 2015 | + {ExecChannelPid, channelId, ExId} -> |
| 2016 | + ct:log("~p:~p Channel that should stay: ~p pid ~p", |
| 2017 | + [?MODULE, ?LINE, ExId, ExecChannelPid]), |
| 2018 | + %% This is sent by the echo subsystem as a reaction to channel1 above |
| 2019 | + ConnPeer = receive {conn_peer, CM} -> CM end, |
| 2020 | + %% The sole purpose of this channel is to go down |
| 2021 | + %% before the opening procedure is complete |
| 2022 | + DownChannelPid = spawn( |
| 2023 | + fun() -> |
| 2024 | + ct:log("~p:~p open channel (incomplete)",[?MODULE,?LINE]), |
| 2025 | + Parent ! {self(), channelId, ok}, |
| 2026 | + %% This is to prevent the peer from answering our 'channel-open' in time |
| 2027 | + sys:suspend(ConnPeer), |
| 2028 | + {ok, _} = ssh_connection:session_channel(ConnectionRef, infinity) |
| 2029 | + end), |
| 2030 | + MonRef = erlang:monitor(process, DownChannelPid), |
| 2031 | + receive |
| 2032 | + {DownChannelPid, channelId, ok} -> |
| 2033 | + ct:log("~p:~p Channel handler that won't continue: pid ~p", |
| 2034 | + [?MODULE, ?LINE, DownChannelPid]), |
| 2035 | + ensure_channels(ConnectionRef, 2), |
| 2036 | + channel_down_sequence(DownChannelPid, ExecChannelPid, |
| 2037 | + ExId, MonRef, ConnectionRef, ConnPeer) |
| 2038 | + end |
| 2039 | + end, |
| 2040 | + ensure_channels(ConnectionRef, 0) |
| 2041 | + after |
| 2042 | + ssh:close(ConnectionRef), |
| 2043 | + ssh:stop_daemon(Pid) |
| 2044 | + end. |
| 2045 | + |
| 2046 | +ensure_channels(ConnRef, Expected) -> |
| 2047 | + {ok, ChannelList} = ssh_connection_handler:info(ConnRef), |
| 2048 | + do_ensure_channels(ConnRef, Expected, length(ChannelList)). |
| 2049 | + |
| 2050 | +do_ensure_channels(_ConnRef, NumExpected, NumExpected) -> |
| 2051 | + ok; |
| 2052 | +do_ensure_channels(ConnRef, NumExpected, _ChannelListLen) -> |
| 2053 | + ct:sleep(100), |
| 2054 | + {ok, ChannelList} = ssh_connection_handler:info(ConnRef), |
| 2055 | + do_ensure_channels(ConnRef, NumExpected, length(ChannelList)). |
| 2056 | + |
| 2057 | +channel_down_sequence(DownChannelPid, ExecChannelPid, ExecChannelId, MonRef, ConnRef, Peer) -> |
| 2058 | + ct:log("~p:~p sending order to ~p to go down", [?MODULE, ?LINE, DownChannelPid]), |
| 2059 | + exit(DownChannelPid, die), |
| 2060 | + receive {'DOWN', MonRef, _, _, _} -> ok end, |
| 2061 | + ct:log("~p:~p order executed, sending order to ~p to proceed", [?MODULE, ?LINE, Peer]), |
| 2062 | + %% Resume the peer connection to let it clean up among its channels |
| 2063 | + sys:resume(Peer), |
| 2064 | + ensure_channels(ConnRef, 1), |
| 2065 | + ExecChannelPid ! cmd, |
| 2066 | + try |
| 2067 | + receive |
| 2068 | + {ExecChannelPid, ok} -> |
| 2069 | + ct:log("~p:~p expected exec result: ~p", [?MODULE, ?LINE, ok]), |
| 2070 | + ok; |
| 2071 | + {ExecChannelPid, Result} -> |
| 2072 | + ct:log("~p:~p Unexpected exec result: ~p", [?MODULE, ?LINE, Result]), |
| 2073 | + {fail, "Unexpected exec result"} |
| 2074 | + after 5000 -> |
| 2075 | + {fail, "Exec result timeout"} |
| 2076 | + end |
| 2077 | + after |
| 2078 | + ssh_connection:close(ConnRef, ExecChannelId) |
| 2079 | + end. |
| 2080 | + |
1947 | 2081 | %%-------------------------------------------------------------------- |
1948 | 2082 | %% Internal functions ------------------------------------------------ |
1949 | 2083 | %%-------------------------------------------------------------------- |
|
0 commit comments