%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2018-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% %% -module(ssh_dbg_SUITE). -include_lib("common_test/include/ct.hrl"). -include_lib("ssh/src/ssh.hrl"). -include("ssh_test_lib.hrl"). %% Note: This directive should only be used in test suites. -compile(export_all). %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{seconds,60}}]. all() -> [{group, dbg}, {group, circ_buf} ]. groups() -> [{dbg, [], [dbg_basic, dbg_alg_terminate, dbg_ssh_messages, dbg_connections, dbg_channels]}, {circ_buf, [], [cb_basic, cb_print, cb_macros_print ]} ]. %%-------------------------------------------------------------------- init_per_suite(Config) -> ?CHECK_CRYPTO(begin ssh:start(), Config end). end_per_suite(_Config) -> ssh:stop(). %%-------------------------------------------------------------------- init_per_testcase(_TC, Config) -> Config. end_per_testcase(_TC, Config) -> ssh_dbg:stop(), Config. %%-------------------------------------------------------------------- -define(USR, "foo"). -define(PWD, "bar"). -define(DBG_RECEIVE(ExpectPfx, Ref, C, Pid), receive {Ref, [_, C, ExpectPfx++_]} -> ok after 5000 -> ssh_dbg:stop(), ssh:stop_daemon(Pid), ct:fail("No '~s' debug message",[ExpectPfx]) end ). %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- dbg_basic(_Config) -> L0 = ssh_dbg:start(), true = is_pid(whereis(ssh_dbg)), true = is_list(L0), {ok,L0} = ssh_dbg:on(), {ok,L0} = ssh_dbg:on(), L1 = [hd(L0)], {ok,L1} = ssh_dbg:off(tl(L0)), {ok,L1} = ssh_dbg:go_on(), {ok,[]} = ssh_dbg:off(), {ok,[]} = ssh_dbg:off(), ok = ssh_dbg:stop(), undefined = whereis(ssh_dbg). %%-------------------------------------------------------------------- dbg_alg_terminate(Config) -> SystemDir = proplists:get_value(data_dir, Config), UserDir = proplists:get_value(priv_dir, Config), Ref = ssh_dbg_start(), {ok,[alg,connections,terminate]} = ssh_dbg:on([alg,terminate,connections]), {ok,[alg,terminate]} = ssh_dbg:off(connections), % just testing that terminate is not canceled Parent = self(), {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, {user_dir, UserDir}, {user_passwords, [{?USR,?PWD}]}, {connectfun, fun(_,_,_) -> Parent ! {daemon_c,Ref,self()} end}, {failfun, fun ssh_test_lib:failfun/2}]), C = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, {user_dir, UserDir}, {user,?USR}, {password,?PWD}, {user_interaction, false}]), %% Daemon connection ref (D): D = receive {daemon_c,Ref,D0} -> D0 end, ct:log("~p:~p~nC = ~p, D=~p",[?MODULE,?LINE, C, D]), ?DBG_RECEIVE("Negotiated algorithms:", Ref, C, Pid), ?DBG_RECEIVE("Negotiated algorithms:", Ref, D, Pid), ssh:close(C), ?DBG_RECEIVE("Connection Terminating:", Ref, C, Pid), ?DBG_RECEIVE("Connection Terminating:", Ref, D, Pid), stop_and_fail_if_unhandled_dbg_msgs(Ref, [C,D], Pid). %%-------------------------------------------------------------------- dbg_connections(Config) -> SystemDir = proplists:get_value(data_dir, Config), UserDir = proplists:get_value(priv_dir, Config), Ref = ssh_dbg_start(), {ok,[connections,terminate]} = ssh_dbg:on([connections, terminate]), {ok,[connections]} = ssh_dbg:off(terminate), % Just testing that terminate doesn't cancel connections Parent = self(), {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, {user_dir, UserDir}, {user_passwords, [{?USR,?PWD}]}, {connectfun, fun(_,_,_) -> Parent ! {daemon_c,Ref,self()} end}, {failfun, fun ssh_test_lib:failfun/2}]), ?DBG_RECEIVE("Starting LISTENER on ", Ref, _, Pid), C = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, {user_dir, UserDir}, {user,?USR}, {password,?PWD}, {user_interaction, false}]), %% Daemon connection ref (D): D = receive {daemon_c,Ref,D0} -> D0 end, ct:log("~p:~p~nC = ~p, D=~p",[?MODULE,?LINE, C, D]), ?DBG_RECEIVE("Starting server connection:", Ref, D, Pid), ?DBG_RECEIVE("Starting client connection:", Ref, C, Pid), ssh:close(C), ?DBG_RECEIVE("Connection Terminating:", Ref, C, Pid), ?DBG_RECEIVE("Connection Terminating:", Ref, D, Pid), stop_and_fail_if_unhandled_dbg_msgs(Ref, [C,D], Pid). %%-------------------------------------------------------------------- dbg_ssh_messages(Config) -> SystemDir = proplists:get_value(data_dir, Config), UserDir = proplists:get_value(priv_dir, Config), Parent = self(), Ref = make_ref(), {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, {user_dir, UserDir}, {user_passwords, [{?USR,?PWD}]}, {connectfun, fun(_,_,_) -> Parent ! {daemon_c,Ref,self()} end}, {failfun, fun ssh_test_lib:failfun/2}]), ssh_dbg_start(Ref), {ok,[ssh_messages]} = ssh_dbg:on([ssh_messages]), C = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, {user_dir, UserDir}, {user,?USR}, {password,?PWD}, {user_interaction, false}]), %% Daemon connection ref (D): D = receive {daemon_c,Ref,D0} -> D0 end, ct:log("~p:~p~nC = ~p, D=~p",[?MODULE,?LINE, C, D]), ?DBG_RECEIVE("Going to send hello message:", Ref, C, Pid), ?DBG_RECEIVE("Received hello message:", Ref, D, Pid), ?DBG_RECEIVE("Going to send hello message:", Ref, D, Pid), ?DBG_RECEIVE("Received hello message:", Ref, C, Pid), ?DBG_RECEIVE("Going to send SSH_MSG_KEXINIT:", Ref, C, Pid), ?DBG_RECEIVE("Received SSH_MSG_KEXINIT:", Ref, D, Pid), ?DBG_RECEIVE("Going to send SSH_MSG_KEXINIT:", Ref, D, Pid), ?DBG_RECEIVE("Received SSH_MSG_KEXINIT:", Ref, C, Pid), case atom_to_list( (ssh_connection_handler:alg(C))#alg.kex ) of "ecdh-"++_ -> ?DBG_RECEIVE("Going to send SSH_MSG_KEX_ECDH_INIT:", Ref, C, Pid), ?DBG_RECEIVE("Received SSH_MSG_KEX_ECDH_INIT:", Ref, D, Pid), ?DBG_RECEIVE("Going to send SSH_MSG_KEX_ECDH_REPLY:", Ref, D, Pid), ?DBG_RECEIVE("Received SSH_MSG_KEX_ECDH_REPLY:", Ref, C, Pid); "diffie-hellman-group-exchange-"++_ -> ?DBG_RECEIVE("Going to send SSH_MSG_KEX_DH_GEX_REQUEST:", Ref, C, Pid), ?DBG_RECEIVE("Received SSH_MSG_KEX_DH_GEX_REQUEST:", Ref, D, Pid), ?DBG_RECEIVE("Going to send SSH_MSG_KEX_DH_GEX_GROUP:", Ref, D, Pid), ?DBG_RECEIVE("Received SSH_MSG_KEX_DH_GEX_GROUP:", Ref, C, Pid), ?DBG_RECEIVE("Going to send SSH_MSG_KEX_DH_GEX_INIT:", Ref, C, Pid), ?DBG_RECEIVE("Received SSH_MSG_KEX_DH_GEX_INIT:", Ref, D, Pid), ?DBG_RECEIVE("Going to send SSH_MSG_KEX_DH_GEX_REPLY:", Ref, D, Pid), ?DBG_RECEIVE("Received SSH_MSG_KEX_DH_GEX_REPLY:", Ref, C, Pid); "diffie-hellman-group"++_ -> ?DBG_RECEIVE("Going to send SSH_MSG_KEXDH_INIT:", Ref, C, Pid), ?DBG_RECEIVE("Received SSH_MSG_KEXDH_INIT:", Ref, D, Pid), ?DBG_RECEIVE("Going to send SSH_MSG_KEXDH_REPLY:", Ref, D, Pid), ?DBG_RECEIVE("Received SSH_MSG_KEXDH_REPLY:", Ref, C, Pid) end, ?DBG_RECEIVE("Going to send SSH_MSG_NEWKEYS:", Ref, C, Pid), ?DBG_RECEIVE("Received SSH_MSG_NEWKEYS:", Ref, D, Pid), ?DBG_RECEIVE("Going to send SSH_MSG_NEWKEYS:", Ref, D, Pid), ?DBG_RECEIVE("Received SSH_MSG_NEWKEYS:", Ref, C, Pid), ?DBG_RECEIVE("Going to send SSH_MSG_SERVICE_REQUEST:", Ref, C, Pid), ?DBG_RECEIVE("Received SSH_MSG_SERVICE_REQUEST:", Ref, D, Pid), ?DBG_RECEIVE("Going to send SSH_MSG_SERVICE_ACCEPT:", Ref, D, Pid), ?DBG_RECEIVE("Received SSH_MSG_SERVICE_ACCEPT:", Ref, C, Pid), ?DBG_RECEIVE("Going to send SSH_MSG_USERAUTH_REQUEST:", Ref, C, Pid), ?DBG_RECEIVE("Received SSH_MSG_USERAUTH_REQUEST:", Ref, D, Pid), ?DBG_RECEIVE("Going to send SSH_MSG_USERAUTH_FAILURE:", Ref, D, Pid), ?DBG_RECEIVE("Received SSH_MSG_USERAUTH_FAILURE:", Ref, C, Pid), ?DBG_RECEIVE("Going to send SSH_MSG_USERAUTH_REQUEST:", Ref, C, Pid), ?DBG_RECEIVE("Received SSH_MSG_USERAUTH_REQUEST:", Ref, D, Pid), ?DBG_RECEIVE("Going to send SSH_MSG_USERAUTH_SUCCESS:", Ref, D, Pid), ?DBG_RECEIVE("Received SSH_MSG_USERAUTH_SUCCESS:", Ref, C, Pid), UnexpectedMsgs = dbg_SKIP(Ref, [S_R ++ P ++ ":" || P <- ["SSH_MSG_USERAUTH_REQUEST", "SSH_MSG_USERAUTH_INFO_REQUEST", "SSH_MSG_USERAUTH_INFO_RESPONSE", "SSH_MSG_USERAUTH_FAILURE", "SSH_MSG_EXT_INFO" ], S_R <- ["Going to send ", "Received " ] ]), ssh:close(C), stop_and_fail_if_unhandled_dbg_msgs(UnexpectedMsgs, Ref, [C,D], Pid). %%-------------------------------------------------------------------- dbg_channels(Config) -> SystemDir = proplists:get_value(data_dir, Config), UserDir = proplists:get_value(priv_dir, Config), Ref = ssh_dbg_start(), {ok,[channels,connections]} = ssh_dbg:on([connections, channels]), Parent = self(), TimeoutShell = fun() -> io:format("TimeoutShell started!~n",[]), timer:sleep(1000), Parent ! {daemon_channel,Ref,self()}, ct:log("~p TIMEOUT!",[self()]) end, {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, {user_dir, UserDir}, {user_passwords, [{?USR,?PWD}]}, {connectfun, fun(_,_,_) -> Parent ! {daemon_c,Ref,self()} end}, {shell, fun(_User) -> spawn(TimeoutShell) end }, {failfun, fun ssh_test_lib:failfun/2}]), ?DBG_RECEIVE("Starting LISTENER on ", Ref, _, Pid), C = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, {user_dir, UserDir}, {user,?USR}, {password,?PWD}, {user_interaction, false}]), {ok, Ch0} = ssh_connection:session_channel(C, infinity), ok = ssh_connection:shell(C, Ch0), %% Daemon connection ref (D): D = receive {daemon_c,Ref,D0} -> D0 end, %% Daemon channel (Dch): Dch = receive {daemon_channel,Ref,Dch0} -> Dch0 end, ct:log("~p:~p~nC = ~p, D=~p, Dch=~p~n~s",[?MODULE,?LINE, C, D, Dch, ssh_info:string()]), ?DBG_RECEIVE("Starting server connection:", Ref, D, Pid), ?DBG_RECEIVE("Starting client connection:", Ref, C, Pid), ?DBG_RECEIVE("Server Channel Starting:", Ref, _, Pid), ?DBG_RECEIVE("Server Channel Terminating:", Ref, _, Pid), stop_and_fail_if_unhandled_dbg_msgs(Ref, [C,D], Pid). %%-------------------------------------------------------------------- cb_basic(_Config) -> %% Check that the circular buffer is disabled at start: [] = ssh_dbg:cbuf_list(), disabled = ssh_dbg:cbuf_in(anything), [] = ssh_dbg:cbuf_list(), %% Start it and enter three values, first is duplicated; ok = ssh_dbg:cbuf_start(3), ok = ssh_dbg:cbuf_in(v1), ok = ssh_dbg:cbuf_in(v1), ok = ssh_dbg:cbuf_in(v2), ok = ssh_dbg:cbuf_in(v3), [{v3,_,1}, {v2,_,1}, {v1,_,2}] = ssh_dbg:cbuf_list(), %% Check that a fourth value erase the first entered: ok = ssh_dbg:cbuf_in(v4), [{v4,_,1}, {v3,_,1}, {v2,_,1}] = ssh_dbg:cbuf_list(), %% Check that entering a value that is in the tail but not in the head is treated as a new value: ok = ssh_dbg:cbuf_in(v2), [{v2,_,1}, {v4,_,1}, {v3,_,1}] = ssh_dbg:cbuf_list(), %% Stop and check that the buffer is returned: [{v2,_,1}, {v4,_,1}, {v3,_,1}] = ssh_dbg:cbuf_stop_clear(), %% Stopping a stopped buffer returns empty: [] = ssh_dbg:cbuf_stop_clear(), %% Check that a value can't be entered in a stopped buffer: disabled = ssh_dbg:cbuf_in(v2). %%-------------------------------------------------------------------- cb_print(_Config) -> ssh_dbg:cbuf_start(), [begin ssh_dbg:cbuf_in(V), ct:log("Enter ~p",[V]) end || V <- lists:seq(1,10)], ct:log("~s",[ssh_dbg:fmt_cbuf_items()]), ssh_dbg:cbuf_stop_clear(). %%-------------------------------------------------------------------- cb_macros_print(_Config) -> ssh_dbg:cbuf_start(), [begin V = {test,V0}, ?CIRC_BUF_IN(V), ct:log("Enter ~p",[V]) end || V0 <- lists:seq(1,5)], ct:log("~s",[ssh_dbg:fmt_cbuf_items()]), ssh_dbg:cbuf_stop_clear(). %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- ssh_dbg_start() -> ssh_dbg_start(make_ref()). ssh_dbg_start(Ref) -> Parent = self(), [_|_] = ssh_dbg:start(fun(_F,A) -> Parent ! {Ref,A} end), Ref. %%-------------------------------------------------------------------- queued_msgs(Ref, Conns) -> queued_msgs(Ref, Conns, []). queued_msgs(Ref, Conns, Acc) -> receive {Ref, [_, C, _]=Msg} -> case is_list(Conns) andalso lists:member(C, Conns) of true -> queued_msgs(Ref, [Msg|Acc]); false -> queued_msgs(Ref, Conns, Acc) end after 0 -> lists:reverse(Acc) end. %%-------------------------------------------------------------------- stop_and_fail_if_unhandled_dbg_msgs(Ref, Conns, DaemonPid) -> stop_and_fail_if_unhandled_dbg_msgs(queued_msgs(Ref,Conns), Ref, Conns, DaemonPid). stop_and_fail_if_unhandled_dbg_msgs(Msgs, _Ref, _Conns, DaemonPid) -> ssh:stop_daemon(DaemonPid), case Msgs of [] -> ok; _ -> ct:log("Unexpected messages:~n~p",[Msgs]), ct:fail("Unexpected messages") end. %%-------------------------------------------------------------------- dbg_SKIP(Ref, Prefixes) -> dbg_SKIP(Ref, Prefixes, []). dbg_SKIP(Ref, Prefixes, UnexpectedAcc) -> receive {Ref, [_, _C, Msg]=M} -> case lists:any( fun(Pfx) -> lists:prefix(Pfx, Msg) end, Prefixes) of true -> ct:log("Skip:~n~p", [M]), dbg_SKIP(Ref, Prefixes, UnexpectedAcc); false -> dbg_SKIP(Ref, Prefixes, [Msg|UnexpectedAcc]) end after 0 -> lists:reverse(UnexpectedAcc) end.