aboutsummaryrefslogblamecommitdiffstats
path: root/lib/ssl/test/ssl_session_cache_SUITE.erl
blob: b71b15b02871398b1eff6b39b729a4df2f018793 (plain) (tree)
1
2
3
4
5


                   
                                                        
  










                                                                           










                                                           
                                           
 
                    

                        
                              
                           
 





                                                 
                                                                      
                                                                      
                                                                      
 


                                
                                  
                           

                          
       



           
                          
                        
                         
             
                                       
                                    
                                               
                

                                          
 

                         
                             
 





                                     






                                                          
                                             


                                                  
                                                                 
                

                               
 
                                                   

                               



                                                   







                                                                        
           
 
                                              


                                                  
                                                                   
                



                                                                                        
                               
           
 





                                                         
                  


                                             



                                                           



                                                        

                                                                           
                               

                              
           
 
                                                                      

                                                                      




                                                          

                                                                     



                                                                        
                                                                        
                                                                    

                                                           
                                                                                   







                                                                      

                                            



                                    


                                                                                    
                                               
                                  
                                                                          
                                                                          









                                                                                  
                                                                  
                                                                        
                                                                            






                                      
                     
 

                                                                      
                                     

                                    
                                     
 
                                                      

                                                                             




                                                        
                              
                                                         
 
                                                              
 

                                  
 
                                                             
 

                                                                              



                                   
 
 
                                                                      

                                                       


                                                                      

                                                       


                                                            
                                                                      





























































                                                                                                                   




                                              

                                                                     








                                                                        
                                                                        


















                                                                      


                                                                      

                                           



                                                 



                                                                 











                                                
                                          








                                            
                                                              

                                                                  
                                                  












                                           

                                                                                   









                                           
                                                                    









                                            
                                                         















                                                        

                                                                         


























                                                                 

                                                                        












                                                                      



                                                                          
 
                                                                    




                                                                            

                                                                        



                                                      
                                                           
                    
                      
                                                                       


                                                                 
































                                                                      
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2010-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(ssl_session_cache_SUITE).

%% Note: This directive should only be used in test suites.
-compile(export_all).

-include_lib("common_test/include/ct.hrl").

-define(DELAY, 500).
-define(SLEEP, 500).
-define(TIMEOUT, 60000).
-define(LONG_TIMEOUT, 600000).
-define(MAX_TABLE_SIZE, 5).

-behaviour(ssl_session_cache_api).

%% For the session cache tests
-export([init/1, terminate/1, lookup/2, update/3,
	 delete/2, foldl/3, select_session/2]).

%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------

all() ->
    [session_cleanup,
     session_cache_process_list,
     session_cache_process_mnesia,
     client_unique_session,
     max_table_size,
     save_specific_session
     ].

groups() ->
    [].

init_per_suite(Config0) ->
    catch crypto:stop(),
    try crypto:start() of
	ok ->
	    ssl_test_lib:clean_start(),
	    %% make rsa certs using 
            ssl_test_lib:make_rsa_cert(Config0)
    catch _:_ ->
	    {skip, "Crypto did not start"}
    end.

end_per_suite(_Config) ->
    ssl:stop(),
    application:stop(crypto).

init_per_group(_GroupName, Config) ->
    Config.

end_per_group(_GroupName, Config) ->
    Config.

init_per_testcase(session_cache_process_list, Config) ->
    init_customized_session_cache(list, Config);

init_per_testcase(session_cache_process_mnesia, Config) ->
    mnesia:start(),
    init_customized_session_cache(mnesia, Config);

init_per_testcase(session_cleanup, Config) ->
    ssl:stop(),
    application:load(ssl),
    application:set_env(ssl, session_lifetime, 5),
    application:set_env(ssl, session_delay_cleanup_time, ?DELAY),
    ssl:start(),
    ct:timetrap({seconds, 20}),
    Config;

init_per_testcase(client_unique_session, Config) ->
    ct:timetrap({seconds, 40}),
    Config;
init_per_testcase(save_specific_session, Config) ->
    ssl_test_lib:clean_start(),
    ct:timetrap({seconds, 5}),
    Config;
init_per_testcase(max_table_size, Config) ->
    ssl:stop(),
    application:load(ssl),
    application:set_env(ssl, session_cache_server_max, ?MAX_TABLE_SIZE),
    application:set_env(ssl, session_cache_client_max, ?MAX_TABLE_SIZE),
    application:set_env(ssl, session_delay_cleanup_time, ?DELAY),	
    ssl:start(),			  
    ct:timetrap({seconds, 40}),
    Config.

init_customized_session_cache(Type, Config) ->
    ssl:stop(),
    application:load(ssl),
    application:set_env(ssl, session_cb, ?MODULE),
    application:set_env(ssl, session_cb_init_args, [{type, Type}]),
    ssl:start(),
    catch (end_per_testcase(list_to_atom("session_cache_process" ++ atom_to_list(Type)),
	   Config)),
    ets:new(ssl_test, [named_table, public, set]),
    ets:insert(ssl_test, {type, Type}),
    ct:timetrap({seconds, 20}),
    Config.

end_per_testcase(session_cache_process_list, Config) ->
    application:unset_env(ssl, session_cb),
    end_per_testcase(default_action, Config);
end_per_testcase(session_cache_process_mnesia, Config) ->
    application:unset_env(ssl, session_cb),
    application:unset_env(ssl, session_cb_init_args),
    mnesia:kill(),
    ssl:stop(),
    ssl:start(),
    end_per_testcase(default_action, Config);
end_per_testcase(session_cleanup, Config) ->
    application:unset_env(ssl, session_delay_cleanup_time),
    application:unset_env(ssl, session_lifetime),
    end_per_testcase(default_action, Config);
end_per_testcase(max_table_size, Config) ->
    application:unset_env(ssl, session_cach_server_max),
    application:unset_env(ssl, session_cach_client_max),
    end_per_testcase(default_action, Config);
end_per_testcase(Case, Config) when Case == session_cache_process_list;
				    Case == session_cache_process_mnesia ->
    catch ets:delete(ssl_test),
    Config;
end_per_testcase(_, Config) ->
    Config.

%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
client_unique_session() ->
    [{doc, "Test session table does not grow when client "
      "sets up many connections"}].
client_unique_session(Config) when is_list(Config) ->
    process_flag(trap_exit, true),
    ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
    ServerOpts = proplists:get_value(server_rsa_opts, Config),
    {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
    Server =
	ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
				   {from, self()},
				   {mfa, {ssl_test_lib, no_result, []}},
				   {tcp_options, [{active, false}]},
				   {options, ServerOpts}]),
    Port = ssl_test_lib:inet_port(Server),
    LastClient = clients_start(Server, ClientNode, Hostname, Port, ClientOpts, 20),
    receive 
	{LastClient, {ok, _}} ->
	    ok
    end,
    {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)),
    [_, _,_, _, Prop] = StatusInfo,
    State = ssl_test_lib:state(Prop),
    ClientCache = element(2, State),

    1 = ssl_session_cache:size(ClientCache),
  
    ssl_test_lib:close(Server, 500),
    ssl_test_lib:close(LastClient).
		
session_cleanup() ->
    [{doc, "Test that sessions are cleand up eventually, so that the session table "
     "does not grow and grow ..."}].
session_cleanup(Config) when is_list(Config) ->
    process_flag(trap_exit, true),
    ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
    ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
    {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),

    Server =
	ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
				   {from, self()},
				   {mfa, {ssl_test_lib, session_info_result, []}},
				   {options, ServerOpts}]),
    Port = ssl_test_lib:inet_port(Server),
    Client =
	ssl_test_lib:start_client([{node, ClientNode},
				   {port, Port}, {host, Hostname},
				   {mfa, {ssl_test_lib, no_result, []}},
				   {from, self()},  {options, ClientOpts}]),
    SessionInfo =
	receive
	    {Server, Info} ->
		Info
	end,

    %% Make sure session is registered
    ct:sleep(?SLEEP),

    {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)),
    [_, _,_, _, Prop] = StatusInfo,
    State = ssl_test_lib:state(Prop),
    ClientCache = element(2, State),
    ServerCache = element(3, State),
    SessionTimer = element(7, State),

    Id = proplists:get_value(session_id, SessionInfo),
    CSession = ssl_session_cache:lookup(ClientCache, {{Hostname, Port}, Id}),
    SSession = ssl_session_cache:lookup(ServerCache, {Port, Id}),

    true = CSession =/= undefined,
    true = SSession =/= undefined,

    %% Make sure session has expired and been cleaned up
    check_timer(SessionTimer),
    ct:sleep(?DELAY *2),  %% Delay time + some extra time

    {ServerDelayTimer, ClientDelayTimer} = get_delay_timers(),

    check_timer(ServerDelayTimer),
    check_timer(ClientDelayTimer),

    ct:sleep(?SLEEP),  %% Make sure clean has had time to run

    undefined = ssl_session_cache:lookup(ClientCache, {{Hostname, Port}, Id}),
    undefined = ssl_session_cache:lookup(ServerCache, {Port, Id}),

    process_flag(trap_exit, false),
    ssl_test_lib:close(Server),
    ssl_test_lib:close(Client).


%%--------------------------------------------------------------------
session_cache_process_list() ->
    [{doc,"Test reuse of sessions (short handshake)"}].
session_cache_process_list(Config) when is_list(Config) ->
    session_cache_process(list,Config).
%%--------------------------------------------------------------------
session_cache_process_mnesia() ->
    [{doc,"Test reuse of sessions (short handshake)"}].
session_cache_process_mnesia(Config) when is_list(Config) ->
    session_cache_process(mnesia,Config).

%%--------------------------------------------------------------------
save_specific_session() ->
    [{doc, "Test that we can save a specific client session"
     }].
save_specific_session(Config) when is_list(Config) ->
    process_flag(trap_exit, true),
    ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
    ServerOpts = proplists:get_value(server_rsa_opts, Config),
    {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
    Server =
	ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
				   {from, self()},
				   {mfa, {ssl_test_lib, no_result, []}},
				   {tcp_options, [{active, false}]},
				   {options, ServerOpts}]),
    Port = ssl_test_lib:inet_port(Server),
    
    Client1 = ssl_test_lib:start_client([{node, ClientNode},
                                         {port, Port}, {host, Hostname},
                                         {mfa, {ssl_test_lib, session_id, []}},
                                         {from, self()},  {options, ClientOpts}]),
    Server ! listen,
    
    Client2 = ssl_test_lib:start_client([{node, ClientNode},
                                         {port, Port}, {host, Hostname},
                                         {mfa, {ssl_test_lib, session_id, []}},
                                         {from, self()},  {options, [{reuse_sessions, save} | ClientOpts]}]),    
    SessionID1 =
        receive 
            {Client1, S1} ->
                S1
        end,
    
    SessionID2 =
        receive 
            {Client2, S2} ->
                S2
        end,
    
    true = SessionID1 =/= SessionID2,

    {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)),
    [_, _,_, _, Prop] = StatusInfo,
    State = ssl_test_lib:state(Prop),
    ClientCache = element(2, State),
    2 = ssl_session_cache:size(ClientCache),

    Server ! listen,

    Client3 = ssl_test_lib:start_client([{node, ClientNode},
                                         {port, Port}, {host, Hostname},
                                         {mfa, {ssl_test_lib, session_id, []}},
                                         {from, self()},  {options, [{reuse_session, SessionID2} | ClientOpts]}]), 
    receive 
        {Client3, SessionID2} ->
            ok;
        {Client3, SessionID3}->
            ct:fail({got, SessionID3, expected, SessionID2});
        Other ->
            ct:fail({got,Other})
    end.

%%--------------------------------------------------------------------

max_table_size() ->
    [{doc,"Test max limit on session table"}].
max_table_size(Config) when is_list(Config) ->
    process_flag(trap_exit, true),
    ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
    ServerOpts = proplists:get_value(server_rsa_verify_opts, Config),
    {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
    Server =
	ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
				   {from, self()},
				   {mfa, {ssl_test_lib, no_result, []}},
				   {tcp_options, [{active, false}]},
				   {options, ServerOpts}]),
    Port = ssl_test_lib:inet_port(Server),
    LastClient = clients_start(Server, 
			    ClientNode, Hostname, Port, ClientOpts, 20),
    receive 
	{LastClient, {ok, _}} ->
	    ok
    end,
    ct:sleep(1000),
    {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)),
    [_, _,_, _, Prop] = StatusInfo,
    State = ssl_test_lib:state(Prop),
    ClientCache = element(2, State),	
    ServerCache = element(3, State),
    N = ssl_session_cache:size(ServerCache),
    M = ssl_session_cache:size(ClientCache),
    ct:pal("~p",[{N, M}]),			
    ssl_test_lib:close(Server, 500),
    ssl_test_lib:close(LastClient),
    true = N =< ?MAX_TABLE_SIZE,
    true = M =< ?MAX_TABLE_SIZE.

%%--------------------------------------------------------------------
%%% Session cache API callbacks
%%--------------------------------------------------------------------

init(Opts) ->
    case proplists:get_value(type, Opts) of
	list ->
	    spawn(fun() -> session_loop([]) end);
	mnesia ->
	    mnesia:start(),
	    Name = atom_to_list(proplists:get_value(role, Opts)),
	    TabName = list_to_atom(Name ++ "sess_cache"),
	    {atomic,ok} = mnesia:create_table(TabName, []),
	    TabName
    end.

session_cb() ->
    [{type, Type}] = ets:lookup(ssl_test, type),
    Type.

terminate(Cache) ->
    case session_cb() of
	list ->
	    Cache ! terminate;
	mnesia ->
	    catch {atomic,ok} =
		mnesia:delete_table(Cache)
    end.

lookup(Cache, Key) ->
    case session_cb() of
	list ->
	    Cache ! {self(), lookup, Key},
	    receive {Cache, Res} -> Res end;
	mnesia ->
	    case mnesia:transaction(fun() ->
					    mnesia:read(Cache,
							Key, read)
				    end) of
		{atomic, [{Cache, Key, Value}]} ->
		    Value;
		_ ->
		    undefined
	    end
	end.

update(Cache, Key, Value) ->
    case session_cb() of
	list ->
	    Cache ! {update, Key, Value};
	mnesia ->
	    {atomic, ok} =
		mnesia:transaction(fun() ->
					   mnesia:write(Cache,
							{Cache, Key, Value}, write)
				   end)
    end.

delete(Cache, Key) ->
    case session_cb() of
	list ->
	    Cache ! {delete, Key};
	mnesia ->
	    {atomic, ok} =
		mnesia:transaction(fun() ->
					   mnesia:delete(Cache, Key)
				   end)
    end.

foldl(Fun, Acc, Cache) ->
    case session_cb() of
	list ->
	    Cache ! {self(),foldl,Fun,Acc},
	    receive {Cache, Res} -> Res end;
	mnesia ->
	    Foldl = fun() ->
			    mnesia:foldl(Fun, Acc, Cache)
		    end,
	    {atomic, Res} = mnesia:transaction(Foldl),
	    Res
    end.

select_session(Cache, PartialKey) ->
    case session_cb() of
	list ->
	    Cache ! {self(),select_session, PartialKey},
	    receive
		{Cache, Res} ->
		    Res
	    end;
	mnesia ->
	    Sel = fun() ->
			  mnesia:select(Cache,
					[{{Cache,{PartialKey,'_'}, '$1'},
					  [],['$1']}])
		  end,
	    {atomic, Res} = mnesia:transaction(Sel),
	    Res
    end.

session_loop(Sess) ->
    receive
	terminate ->
	    ok;
	{Pid, lookup, Key} ->
	    case lists:keysearch(Key,1,Sess) of
		{value, {Key,Value}} ->
		    Pid ! {self(), Value};
		_ ->
		    Pid ! {self(), undefined}
	    end,
	    session_loop(Sess);
	{update, Key, Value} ->
	    NewSess = [{Key,Value}| lists:keydelete(Key,1,Sess)],
	    session_loop(NewSess);
	{delete, Key} ->
	    session_loop(lists:keydelete(Key,1,Sess));
	{Pid,foldl,Fun,Acc} ->
	    Res = lists:foldl(Fun, Acc,Sess),
	    Pid ! {self(), Res},
	    session_loop(Sess);
	{Pid,select_session,PKey} ->
	    Sel = fun({{PKey0, _Id},Session}, Acc) when PKey == PKey0 ->
			  [Session | Acc];
		     (_,Acc) ->
			  Acc
		  end,
	    Sessions = lists:foldl(Sel, [], Sess),
	    Pid ! {self(), Sessions},
	    session_loop(Sess)
    end.

%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------

session_cache_process(_Type,Config) when is_list(Config) ->
    ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
    ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
    ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config).


clients_start(_Server, ClientNode, Hostname, Port, ClientOpts, 0) ->
    %% Make sure session is registered
    ct:sleep(?SLEEP * 2),
    ssl_test_lib:start_client([{node, ClientNode},
			       {port, Port}, {host, Hostname},
			       {mfa, {?MODULE, connection_info_result, []}},
			       {from, self()},  {options, ClientOpts}]);
clients_start(Server, ClientNode, Hostname, Port, ClientOpts, N) ->
    spawn_link(ssl_test_lib, start_client, 
	       [[{node, ClientNode},
		 {port, Port}, {host, Hostname},
		 {mfa, {ssl_test_lib, no_result, []}},
		 {from, self()},  {options, ClientOpts}]]),
    Server ! listen,
    wait_for_server(),
    clients_start(Server, ClientNode, Hostname, Port, ClientOpts, N-1).
	
connection_info_result(Socket) ->
    ssl:connection_information(Socket, [protocol, cipher_suite]).

check_timer(Timer) ->
    case erlang:read_timer(Timer) of
	false ->
	    {status, _, _, _} = sys:get_status(whereis(ssl_manager)),
	    timer:sleep(?SLEEP),
	    {status, _, _, _} = sys:get_status(whereis(ssl_manager)),
	    ok;
	Int ->
	    ct:sleep(Int),
	    check_timer(Timer)
    end.

get_delay_timers() ->
    {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)),
    [_, _,_, _, Prop] = StatusInfo,
    State = ssl_test_lib:state(Prop),
    case element(8, State) of
	{undefined, undefined} ->
	    ct:sleep(?SLEEP),
	    get_delay_timers();
	{undefined, _} ->
	    ct:sleep(?SLEEP),
	    get_delay_timers();
	{_, undefined} ->
	    ct:sleep(?SLEEP),
	    get_delay_timers();
	DelayTimers ->
	    DelayTimers
    end.
    
wait_for_server() ->
    ct:sleep(100).