aboutsummaryrefslogblamecommitdiffstats
path: root/lib/ssh/test/ssh_dbg_SUITE.erl
blob: ab7918fa90ca1f5e99069bce59aae2108def31e1 (plain) (tree)







































                                                                           

                      






                                  




                                    






































                                                                      
                     


































































































































































































































































                                                                                                         














































                                                                                                     
































































                                                                                       
%%
%% %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.