diff options
Diffstat (limited to 'lib')
28 files changed, 1728 insertions, 317 deletions
diff --git a/lib/common_test/src/ct_master.erl b/lib/common_test/src/ct_master.erl index 042c5ba267..99bec3ea09 100644 --- a/lib/common_test/src/ct_master.erl +++ b/lib/common_test/src/ct_master.erl @@ -696,8 +696,9 @@ status(MasterPid,Event) -> log(To,Heading,Str,Args) -> if To == all ; To == tty -> - Str1 = ["=== ",Heading," ===\n",io_lib:format(Str,Args),"\n"], - io:format(Str1,[]); + Chars = ["=== ",Heading," ===\n", + io_lib:format(Str,Args),"\n"], + io:put_chars(Chars); true -> ok end, diff --git a/lib/common_test/src/ct_master_logs.erl b/lib/common_test/src/ct_master_logs.erl index 9e61d5b16f..d76288feef 100644 --- a/lib/common_test/src/ct_master_logs.erl +++ b/lib/common_test/src/ct_master_logs.erl @@ -134,7 +134,7 @@ init(Parent,LogDir,Nodes) -> io:format(CtLogFd,int_header(),[log_timestamp(now()),"Test Nodes\n"]), io:format(CtLogFd,"~s\n",[NodeStr]), - io:format(CtLogFd,int_footer()++"\n",[]), + io:put_chars(CtLogFd,[int_footer(),"\n"]), NodeDirIxFd = open_nodedir_index(RunDirAbs,Time), Parent ! {started,self(),{Time,RunDirAbs}}, @@ -202,24 +202,21 @@ loop(State) -> open_ct_master_log(Dir) -> FullName = filename:join(Dir,?ct_master_log_name), {ok,Fd} = file:open(FullName,[write]), - io:format(Fd,header("Common Test Master Log", {[],[1,2],[]}),[]), + io:put_chars(Fd,header("Common Test Master Log", {[],[1,2],[]})), %% maybe add config info here later - io:format(Fd, config_table([]), []), - io:format(Fd, - "<style>\n" - "div.ct_internal { background:lightgrey; color:black }\n" - "div.default { background:lightgreen; color:black }\n" - "</style>\n", - []), - io:format(Fd, - xhtml("<br><h2>Progress Log</h2>\n<pre>\n", - "<br /><h2>Progress Log</h2>\n<pre>\n"), - []), + io:put_chars(config_table([])), + io:put_chars(Fd, + "<style>\n" + "div.ct_internal { background:lightgrey; color:black }\n" + "div.default { background:lightgreen; color:black }\n" + "</style>\n"), + io:put_chars(Fd, + xhtml("<br><h2>Progress Log</h2>\n<pre>\n", + "<br /><h2>Progress Log</h2>\n<pre>\n")), Fd. close_ct_master_log(Fd) -> - io:format(Fd,"</pre>",[]), - io:format(Fd,footer(),[]), + io:put_chars(Fd,["</pre>",footer()]), file:close(Fd). config_table(Vars) -> @@ -248,20 +245,20 @@ int_footer() -> open_nodedir_index(Dir,StartTime) -> FullName = filename:join(Dir,?nodedir_index_name), {ok,Fd} = file:open(FullName,[write]), - io:format(Fd,nodedir_index_header(StartTime),[]), + io:put_chars(Fd,nodedir_index_header(StartTime)), Fd. print_nodedir(Node,RunDir,Fd) -> Index = filename:join(RunDir,"index.html"), - io:format(Fd, - ["<tr>\n" - "<td align=center>",atom_to_list(Node),"</td>\n", - "<td align=left><a href=\"",Index,"\">",Index,"</a></td>\n", - "</tr>\n"],[]), + io:put_chars(Fd, + ["<tr>\n" + "<td align=center>",atom_to_list(Node),"</td>\n", + "<td align=left><a href=\"",Index,"\">",Index,"</a></td>\n", + "</tr>\n"]), ok. close_nodedir_index(Fd) -> - io:format(Fd,index_footer(),[]), + io:put_chars(Fd,index_footer()), file:close(Fd). nodedir_index_header(StartTime) -> diff --git a/lib/common_test/src/ct_snmp.erl b/lib/common_test/src/ct_snmp.erl index 8fe63e8ed1..02f849201d 100644 --- a/lib/common_test/src/ct_snmp.erl +++ b/lib/common_test/src/ct_snmp.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2010. All Rights Reserved. +%% Copyright Ericsson AB 2004-2012. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -250,10 +250,8 @@ stop(Config) -> %%% %%% @doc Issues a synchronous snmp get request. get_values(Agent, Oids, MgrAgentConfName) -> - [Uid, AgentIp, AgentUdpPort | _] = - agent_conf(Agent, MgrAgentConfName), - {ok, SnmpReply, _} = - snmpm:g(Uid, AgentIp, AgentUdpPort, Oids), + [Uid | _] = agent_conf(Agent, MgrAgentConfName), + {ok, SnmpReply, _} = snmpm:sync_get2(Uid, target_name(Agent), Oids), SnmpReply. %%% @spec get_next_values(Agent, Oids, MgrAgentConfName) -> SnmpReply @@ -265,10 +263,8 @@ get_values(Agent, Oids, MgrAgentConfName) -> %%% %%% @doc Issues a synchronous snmp get next request. get_next_values(Agent, Oids, MgrAgentConfName) -> - [Uid, AgentIp, AgentUdpPort | _] = - agent_conf(Agent, MgrAgentConfName), - {ok, SnmpReply, _} = - snmpm:gn(Uid, AgentIp, AgentUdpPort, Oids), + [Uid | _] = agent_conf(Agent, MgrAgentConfName), + {ok, SnmpReply, _} = snmpm:sync_get_next2(Uid, target_name(Agent), Oids), SnmpReply. %%% @spec set_values(Agent, VarsAndVals, MgrAgentConfName, Config) -> SnmpReply @@ -282,13 +278,11 @@ get_next_values(Agent, Oids, MgrAgentConfName) -> %%% @doc Issues a synchronous snmp set request. set_values(Agent, VarsAndVals, MgrAgentConfName, Config) -> PrivDir = ?config(priv_dir, Config), - [Uid, AgentIp, AgentUdpPort | _] = - agent_conf(Agent, MgrAgentConfName), + [Uid | _] = agent_conf(Agent, MgrAgentConfName), Oids = lists:map(fun({Oid, _, _}) -> Oid end, VarsAndVals), - {ok, SnmpGetReply, _} = - snmpm:g(Uid, AgentIp, AgentUdpPort, Oids), - {ok, SnmpSetReply, _} = - snmpm:s(Uid, AgentIp, AgentUdpPort, VarsAndVals), + TargetName = target_name(Agent), + {ok, SnmpGetReply, _} = snmpm:sync_get2(Uid, TargetName, Oids), + {ok, SnmpSetReply, _} = snmpm:sync_set2(Uid, TargetName, VarsAndVals), case SnmpSetReply of {noError, 0, _} when PrivDir /= false -> log(PrivDir, Agent, SnmpGetReply, VarsAndVals); @@ -348,7 +342,7 @@ register_agents(MgrAgentConfName, ManagedAgents) -> NewSnmpVals = lists:keyreplace(managed_agents, 1, SnmpVals, {managed_agents, ManagedAgents}), ct_config:update_config(MgrAgentConfName, {snmp, NewSnmpVals}), - setup_managed_agents(ManagedAgents). + setup_managed_agents(MgrAgentConfName,ManagedAgents). %%% @spec register_usm_users(MgrAgentConfName, UsmUsers) -> ok | {error, Reason} %%% @@ -486,9 +480,8 @@ setup_agent(true, AgentConfName, SnmpConfName, file:make_dir(DbDir), snmp_config:write_agent_snmp_files(ConfDir, Vsns, ManagerIP, TrapUdp, AgentIP, AgentUdp, SysName, - atom_to_list(NotifType), - SecType, Passwd, AgentEngineID, - AgentMaxMsgSize), + NotifType, SecType, Passwd, + AgentEngineID, AgentMaxMsgSize), override_default_configuration(Config, AgentConfName), @@ -497,7 +490,8 @@ setup_agent(true, AgentConfName, SnmpConfName, {verbosity, trace}]}, {agent_type, master}, {agent_verbosity, trace}, - {net_if, [{verbosity, trace}]}], + {net_if, [{verbosity, trace}]}, + {versions, Vsns}], ct:get_config({SnmpConfName,agent})), application:set_env(snmp, agent, SnmpEnv). %%%--------------------------------------------------------------------------- @@ -535,7 +529,7 @@ manager_register(true, MgrAgentConfName) -> setup_usm_users(UsmUsers, EngineID), setup_users(Users), - setup_managed_agents(Agents). + setup_managed_agents(MgrAgentConfName,Agents). %%%--------------------------------------------------------------------------- setup_users(Users) -> @@ -543,10 +537,11 @@ setup_users(Users) -> snmpm:register_user(Id, Module, Data) end, Users). %%%--------------------------------------------------------------------------- -setup_managed_agents([]) -> +setup_managed_agents(_,[]) -> ok; -setup_managed_agents([{_, [Uid, AgentIp, AgentUdpPort, AgentConf]} | +setup_managed_agents(AgentConfName, + [{AgentName, [Uid, AgentIp, AgentUdpPort, AgentConf0]} | Rest]) -> NewAgentIp = case AgentIp of IpTuple when is_tuple(IpTuple) -> @@ -556,12 +551,19 @@ setup_managed_agents([{_, [Uid, AgentIp, AgentUdpPort, AgentConf]} | [IpTuple|_] = Hostent#hostent.h_addr_list, IpTuple end, - ok = snmpm:register_agent(Uid, NewAgentIp, AgentUdpPort), - lists:foreach(fun({Item, Val}) -> - snmpm:update_agent_info(Uid, NewAgentIp, - AgentUdpPort, Item, Val) - end, AgentConf), - setup_managed_agents(Rest). + AgentConf = + case lists:keymember(engine_id,1,AgentConf0) of + true -> + AgentConf0; + false -> + DefaultEngineID = ct:get_config({AgentConfName,agent_engine_id}, + ?AGENT_ENGINE_ID), + [{engine_id,DefaultEngineID}|AgentConf0] + end, + ok = snmpm:register_agent(Uid, target_name(AgentName), + [{address,NewAgentIp},{port,AgentUdpPort} | + AgentConf]), + setup_managed_agents(AgentConfName,Rest). %%%--------------------------------------------------------------------------- setup_usm_users(UsmUsers, EngineID)-> lists:foreach(fun({UsmUser, Conf}) -> @@ -769,3 +771,8 @@ override_vacm(Config, VacmConf) -> File = filename:join(Dir,"vacm.conf"), file:delete(File), snmp_config:write_agent_vacm_config(Dir, "", VacmConf). + +%%%--------------------------------------------------------------------------- + +target_name(Agent) -> + atom_to_list(Agent). diff --git a/lib/common_test/test/Makefile b/lib/common_test/test/Makefile index 3526ef4421..7691920993 100644 --- a/lib/common_test/test/Makefile +++ b/lib/common_test/test/Makefile @@ -52,7 +52,8 @@ MODULES= \ ct_auto_compile_SUITE \ ct_verbosity_SUITE \ ct_shell_SUITE \ - ct_system_error_SUITE + ct_system_error_SUITE \ + ct_snmp_SUITE ERL_FILES= $(MODULES:%=%.erl) diff --git a/lib/common_test/test/ct_snmp_SUITE.erl b/lib/common_test/test/ct_snmp_SUITE.erl new file mode 100644 index 0000000000..848752b816 --- /dev/null +++ b/lib/common_test/test/ct_snmp_SUITE.erl @@ -0,0 +1,141 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2012. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%%%------------------------------------------------------------------- +%%% File: ct_snmp_SUITE +%%% +%%% Description: +%%% Test ct_snmp module +%%% +%%%------------------------------------------------------------------- +-module(ct_snmp_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("common_test/include/ct_event.hrl"). + +-define(eh, ct_test_support_eh). + +%%-------------------------------------------------------------------- +%% TEST SERVER CALLBACK FUNCTIONS +%%-------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +%% Description: Since Common Test starts another Test Server +%% instance, the tests need to be performed on a separate node (or +%% there will be clashes with logging processes etc). +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + Config1 = ct_test_support:init_per_suite(Config), + Config1. + +end_per_suite(Config) -> + ct_test_support:end_per_suite(Config). + +init_per_testcase(TestCase, Config) -> + ct_test_support:init_per_testcase(TestCase, Config). + +end_per_testcase(TestCase, Config) -> + ct_test_support:end_per_testcase(TestCase, Config). + +suite() -> [{ct_hooks,[ts_install_cth]}]. + +all() -> + [ + default + ]. + +%%-------------------------------------------------------------------- +%% TEST CASES +%%-------------------------------------------------------------------- + +%%%----------------------------------------------------------------- +%%% +default(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Suite = filename:join(DataDir, "snmp1_SUITE"), + CfgFile = filename:join(DataDir, "snmp.cfg"), + {Opts,ERPid} = setup([{suite,Suite},{config,CfgFile}, + {label,default}], Config), + + ok = execute(default, Opts, ERPid, Config). + + +%%%----------------------------------------------------------------- +%%% HELP FUNCTIONS +%%%----------------------------------------------------------------- + +setup(Test, Config) -> + Opts0 = ct_test_support:get_opts(Config), + Level = ?config(trace_level, Config), + EvHArgs = [{cbm,ct_test_support},{trace_level,Level}], + Opts = Opts0 ++ [{event_handler,{?eh,EvHArgs}}|Test], + ERPid = ct_test_support:start_event_receiver(Config), + {Opts,ERPid}. + +execute(Name, Opts, ERPid, Config) -> + ok = ct_test_support:run(Opts, Config), + Events = ct_test_support:get_events(ERPid, Config), + + ct_test_support:log_events(Name, + reformat(Events, ?eh), + ?config(priv_dir, Config), + Opts), + + TestEvents = events_to_check(Name,Config), + ct_test_support:verify_events(TestEvents, Events, Config). + +reformat(Events, EH) -> + ct_test_support:reformat(Events, EH). + +%%%----------------------------------------------------------------- +%%% TEST EVENTS +%%%----------------------------------------------------------------- +events_to_check(_TestName,Config) -> + {module,_} = code:load_abs(filename:join(?config(data_dir,Config), + snmp1_SUITE)), + TCs = get_tcs(), + code:purge(snmp1_SUITE), + code:delete(snmp1_SUITE), + + OneTest = + [{?eh,start_logging,{'DEF','RUNDIR'}}] ++ + [{?eh,tc_done,{snmp1_SUITE,TC,ok}} || TC <- TCs] ++ + [{?eh,stop_logging,[]}], + + %% 2 tests (ct:run_test + script_start) is default + OneTest ++ OneTest. + + +get_tcs() -> + All = snmp1_SUITE:all(), + Groups = + try snmp1_SUITE:groups() + catch error:undef -> [] + end, + flatten_tcs(All,Groups). + +flatten_tcs([H|T],Groups) when is_atom(H) -> + [H|flatten_tcs(T,Groups)]; +flatten_tcs([{group,Group}|T],Groups) -> + TCs = proplists:get_value(Group,Groups), + flatten_tcs(TCs ++ T,Groups); +flatten_tcs([],_) -> + []. diff --git a/lib/common_test/test/ct_snmp_SUITE_data/snmp.cfg b/lib/common_test/test/ct_snmp_SUITE_data/snmp.cfg new file mode 100644 index 0000000000..b0ac0e6a96 --- /dev/null +++ b/lib/common_test/test/ct_snmp_SUITE_data/snmp.cfg @@ -0,0 +1,20 @@ +%% -*- erlang -*- +{snmp, [{start_agent,true}, + {users,[{user_name,[snmp1_SUITE,[]]}]}, + {managed_agents,[{agent_name, [user_name, {127,0,0,1}, 4000, + [{engine_id,"ct_snmp-test-engine"}, + {version,v2}]]}]}, + {engine_id,"ct_snmp-test-engine"}, + {agent_vsns,[v2]} + ]}. +{snmp_app,[{manager, [{config, [{verbosity, silence}]}, + {server,[{verbosity,silence}]}, + {net_if,[{verbosity,silence}]}, + {versions,[v2]} + ]}, + {agent, [{config, [{verbosity, silence}]}, + {net_if,[{verbosity,silence}]}, + {mib_server,[{verbosity,silence}]}, + {local_db,[{verbosity,silence}]}, + {agent_verbosity,silence} + ]}]}. diff --git a/lib/common_test/test/ct_snmp_SUITE_data/snmp1_SUITE.erl b/lib/common_test/test/ct_snmp_SUITE_data/snmp1_SUITE.erl new file mode 100644 index 0000000000..dcc5c5378b --- /dev/null +++ b/lib/common_test/test/ct_snmp_SUITE_data/snmp1_SUITE.erl @@ -0,0 +1,152 @@ +%%-------------------------------------------------------------------- +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2012. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%---------------------------------------------------------------------- +%% File: ct_snmp_SUITE.erl +%% +%% Description: +%% This file contains the test cases for the ct_snmp API. +%% +%% @author Support +%% @doc Test of SNMP support in common_test +%% @end +%%---------------------------------------------------------------------- +%%---------------------------------------------------------------------- +-module(snmp1_SUITE). +-include_lib("common_test/include/ct.hrl"). +-include_lib("snmp/include/STANDARD-MIB.hrl"). +-include_lib("snmp/include/snmp_types.hrl"). + +-compile(export_all). + +%% Default timetrap timeout (set in init_per_testcase). +-define(default_timeout, ?t:minutes(1)). + +%% SNMP user stuff +-behaviour(snmpm_user). +-export([handle_error/3, + handle_agent/5, + handle_pdu/4, + handle_trap/3, + handle_inform/3, + handle_report/3]). + + +suite() -> + [{require, snmp_mgr_agent, snmp}, + {require, snmp_app_cfg, snmp_app}]. + +all() -> + [start_stop, + {group,get_set}]. + + +groups() -> + [{get_set,[get_values,get_next_values,set_values]}]. + +init_per_group(get_set, Config) -> + ok = ct_snmp:start(Config,snmp_mgr_agent,snmp_app_cfg), + Config. + +end_per_group(get_set, Config) -> + ok = ct_snmp:stop(Config), + Config. + +init_per_testcase(_Case, Config) -> + Dog = test_server:timetrap(?default_timeout), + [{watchdog, Dog}|Config]. + +end_per_testcase(_Case, Config) -> + Dog=?config(watchdog, Config), + test_server:timetrap_cancel(Dog), + ok. + +init_per_suite(Config) -> + Config. + +end_per_suite(Config) -> + Config. + +break(_Config) -> + test_server:break(""), + ok. + +start_stop(Config) -> + ok = ct_snmp:start(Config,snmp_mgr_agent,snmp_app_cfg), + timer:sleep(1000), + {snmp,_,_} = lists:keyfind(snmp,1,application:which_applications()), + [_|_] = filelib:wildcard("*/*.conf",?config(priv_dir,Config)), + + ok = ct_snmp:stop(Config), + timer:sleep(1000), + false = lists:keyfind(snmp,1,application:which_applications()), + [] = filelib:wildcard("*/*.conf",?config(priv_dir,Config)), + ok. + +get_values(_Config) -> + Oids1 = [?sysDescr_instance, ?sysName_instance], + {noError,_,V1} = ct_snmp:get_values(agent_name,Oids1,snmp_mgr_agent), + [#varbind{oid=?sysDescr_instance,value="Erlang SNMP agent"}, + #varbind{oid=?sysName_instance,value="ct_test"}] = V1, + ok. + +get_next_values(_Config) -> + Oids2 = [?system], + {noError,_,V2} = ct_snmp:get_next_values(agent_name,Oids2,snmp_mgr_agent), + [#varbind{oid=?sysDescr_instance,value="Erlang SNMP agent"}] = V2, + ok. + +set_values(Config) -> + Oid3 = ?sysName_instance, + NewName = "ct_test changed by " ++ atom_to_list(?MODULE), + VarsAndVals = [{Oid3,s,NewName}], + {noError,_,_} = + ct_snmp:set_values(agent_name,VarsAndVals,snmp_mgr_agent,Config), + + Oids4 = [?sysName_instance], + {noError,_,V4} = ct_snmp:get_values(agent_name,Oids4,snmp_mgr_agent), + [#varbind{oid=?sysName_instance,value=NewName}] = V4, + + ok. + + +%%%----------------------------------------------------------------- +%%% SNMP Manager User callback +handle_error(ReqId, Reason, UserData) -> + erlang:display({handle_error,ReqId, Reason, UserData}), + ignore. + +handle_agent(Addr, Port, Type, SnmpInfo, UserData) -> + erlang:display({handle_agent,Addr, Port, Type, SnmpInfo, UserData}), + ignore. + +handle_pdu(TargetName, ReqId, SnmpPduInfo, UserData) -> + erlang:display({handle_pdu,TargetName, ReqId, SnmpPduInfo, UserData}), + ignore. + +handle_trap(TargetName, SnmpTrapInfo, UserData) -> + erlang:display({handle_trap,TargetName, SnmpTrapInfo, UserData}), + ignore. + +handle_inform(TargetName, SnmpInformInfo, UserData) -> + erlang:display({handle_inform,TargetName, SnmpInformInfo, UserData}), + ignore. + +handle_report(TargetName, SnmpReportInfo, UserData) -> + erlang:display({handle_report,TargetName, SnmpReportInfo, UserData}), + ignore. diff --git a/lib/common_test/test/ct_testspec_1_SUITE.erl b/lib/common_test/test/ct_testspec_1_SUITE.erl index b7e19f25dd..6a4a4acd80 100644 --- a/lib/common_test/test/ct_testspec_1_SUITE.erl +++ b/lib/common_test/test/ct_testspec_1_SUITE.erl @@ -58,7 +58,7 @@ end_per_testcase(TestCase, Config) -> suite() -> [{ct_hooks,[ts_install_cth]}]. -all() -> +all() -> [all_suites, skip_all_suites, suite, skip_suite, all_testcases, skip_all_testcases, testcase, skip_testcase, all_groups, skip_all_groups, group, @@ -67,23 +67,23 @@ all() -> skip_group_testcase, topgroup, subgroup, skip_subgroup, subgroup_all_testcases, skip_subgroup_all_testcases, subgroup_testcase, skip_subgroup_testcase, - sub_skipped_by_top, testcase_in_multiple_groups, - order_of_tests_in_multiple_dirs_no_merge_tests, - order_of_tests_in_multiple_suites_no_merge_tests, - order_of_suites_in_multiple_dirs_no_merge_tests, - order_of_groups_in_multiple_dirs_no_merge_tests, - order_of_groups_in_multiple_suites_no_merge_tests, - order_of_tests_in_multiple_dirs, - order_of_tests_in_multiple_suites, - order_of_suites_in_multiple_dirs, - order_of_groups_in_multiple_dirs, - order_of_groups_in_multiple_suites, - order_of_tests_in_multiple_suites_with_skip_no_merge_tests, - order_of_tests_in_multiple_suites_with_skip, + sub_skipped_by_top, testcase_many_groups, + order_of_tests_many_dirs_no_merge_tests, + order_of_tests_many_suites_no_merge_tests, + order_of_suites_many_dirs_no_merge_tests, + order_of_groups_many_dirs_no_merge_tests, + order_of_groups_many_suites_no_merge_tests, + order_of_tests_many_dirs, + order_of_tests_many_suites, + order_of_suites_many_dirs, + order_of_groups_many_dirs, + order_of_groups_many_suites, + order_of_tests_many_suites_with_skip_no_merge_tests, + order_of_tests_many_suites_with_skip, all_plus_one_tc_no_merge_tests, all_plus_one_tc]. -groups() -> +groups() -> []. init_per_group(_GroupName, Config) -> @@ -373,19 +373,19 @@ sub_skipped_by_top(Config) when is_list(Config) -> %%%----------------------------------------------------------------- %%% -testcase_in_multiple_groups(Config) when is_list(Config) -> +testcase_many_groups(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), TestDir = filename:join(DataDir, "groups_1"), TestSpec = [{cases,TestDir,groups_12_SUITE,[testcase_1a,testcase_1b]}, {skip_cases,TestDir,groups_12_SUITE,[testcase_1b],"SKIPPED!"}], - setup_and_execute(testcase_in_multiple_groups, TestSpec, Config). + setup_and_execute(testcase_many_groups, TestSpec, Config). %%%----------------------------------------------------------------- %%% -order_of_tests_in_multiple_dirs_no_merge_tests(Config) when is_list(Config) -> +order_of_tests_many_dirs_no_merge_tests(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), TestDir1 = filename:join(DataDir, "groups_1"), @@ -395,13 +395,13 @@ order_of_tests_in_multiple_dirs_no_merge_tests(Config) when is_list(Config) -> {cases,TestDir2,groups_22_SUITE,[testcase_1]}, {cases,TestDir1,groups_12_SUITE,[testcase_1b]}], - setup_and_execute(order_of_tests_in_multiple_dirs_no_merge_tests, + setup_and_execute(order_of_tests_many_dirs_no_merge_tests, TestSpec, Config). %%%----------------------------------------------------------------- %%% -order_of_tests_in_multiple_suites_no_merge_tests(Config) when is_list(Config) -> +order_of_tests_many_suites_no_merge_tests(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), TestDir1 = filename:join(DataDir, "groups_1"), @@ -410,13 +410,13 @@ order_of_tests_in_multiple_suites_no_merge_tests(Config) when is_list(Config) -> {cases,TestDir1,groups_11_SUITE,[testcase_1]}, {cases,TestDir1,groups_12_SUITE,[testcase_1b]}], - setup_and_execute(order_of_tests_in_multiple_suites_no_merge_tests, + setup_and_execute(order_of_tests_many_suites_no_merge_tests, TestSpec, Config). %%%----------------------------------------------------------------- %%% -order_of_suites_in_multiple_dirs_no_merge_tests(Config) when is_list(Config) -> +order_of_suites_many_dirs_no_merge_tests(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), TestDir1 = filename:join(DataDir, "groups_1"), @@ -426,13 +426,13 @@ order_of_suites_in_multiple_dirs_no_merge_tests(Config) when is_list(Config) -> {suites,TestDir2,groups_22_SUITE}, {suites,TestDir1,groups_11_SUITE}], - setup_and_execute(order_of_suites_in_multiple_dirs_no_merge_tests, + setup_and_execute(order_of_suites_many_dirs_no_merge_tests, TestSpec, Config). %%%----------------------------------------------------------------- %%% -order_of_groups_in_multiple_dirs_no_merge_tests(Config) when is_list(Config) -> +order_of_groups_many_dirs_no_merge_tests(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), TestDir1 = filename:join(DataDir, "groups_1"), @@ -442,13 +442,13 @@ order_of_groups_in_multiple_dirs_no_merge_tests(Config) when is_list(Config) -> {groups,TestDir2,groups_22_SUITE,test_group_1a}, {groups,TestDir1,groups_12_SUITE,test_group_1b}], - setup_and_execute(order_of_groups_in_multiple_dirs_no_merge_tests, + setup_and_execute(order_of_groups_many_dirs_no_merge_tests, TestSpec, Config). %%%----------------------------------------------------------------- %%% -order_of_groups_in_multiple_suites_no_merge_tests(Config) +order_of_groups_many_suites_no_merge_tests(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), @@ -458,13 +458,13 @@ order_of_groups_in_multiple_suites_no_merge_tests(Config) {groups,TestDir1,groups_11_SUITE,test_group_1a}, {groups,TestDir1,groups_12_SUITE,test_group_1b}], - setup_and_execute(order_of_groups_in_multiple_suites_no_merge_tests, + setup_and_execute(order_of_groups_many_suites_no_merge_tests, TestSpec, Config). %%%----------------------------------------------------------------- %%% -order_of_tests_in_multiple_suites_with_skip_no_merge_tests(Config) +order_of_tests_many_suites_with_skip_no_merge_tests(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), @@ -477,14 +477,14 @@ order_of_tests_in_multiple_suites_with_skip_no_merge_tests(Config) {skip_cases,TestDir1,groups_12_SUITE,[testcase_1b],"Skip it"}], setup_and_execute( - order_of_tests_in_multiple_suites_with_skip_no_merge_tests, + order_of_tests_many_suites_with_skip_no_merge_tests, TestSpec, Config). %%%----------------------------------------------------------------- %%% -order_of_tests_in_multiple_dirs(Config) when is_list(Config) -> +order_of_tests_many_dirs(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), TestDir1 = filename:join(DataDir, "groups_1"), @@ -493,13 +493,13 @@ order_of_tests_in_multiple_dirs(Config) when is_list(Config) -> {cases,TestDir2,groups_22_SUITE,[testcase_1]}, {cases,TestDir1,groups_12_SUITE,[testcase_1b]}], - setup_and_execute(order_of_tests_in_multiple_dirs, + setup_and_execute(order_of_tests_many_dirs, TestSpec, Config). %%%----------------------------------------------------------------- %%% -order_of_tests_in_multiple_suites(Config) when is_list(Config) -> +order_of_tests_many_suites(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), TestDir1 = filename:join(DataDir, "groups_1"), @@ -507,13 +507,13 @@ order_of_tests_in_multiple_suites(Config) when is_list(Config) -> {cases,TestDir1,groups_11_SUITE,[testcase_1]}, {cases,TestDir1,groups_12_SUITE,[testcase_1b]}], - setup_and_execute(order_of_tests_in_multiple_suites, + setup_and_execute(order_of_tests_many_suites, TestSpec, Config). %%%----------------------------------------------------------------- %%% -order_of_suites_in_multiple_dirs(Config) when is_list(Config) -> +order_of_suites_many_dirs(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), TestDir1 = filename:join(DataDir, "groups_1"), @@ -522,13 +522,13 @@ order_of_suites_in_multiple_dirs(Config) when is_list(Config) -> {suites,TestDir2,groups_22_SUITE}, {suites,TestDir1,groups_11_SUITE}], - setup_and_execute(order_of_suites_in_multiple_dirs, + setup_and_execute(order_of_suites_many_dirs, TestSpec, Config). %%%----------------------------------------------------------------- %%% -order_of_groups_in_multiple_dirs(Config) when is_list(Config) -> +order_of_groups_many_dirs(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), TestDir1 = filename:join(DataDir, "groups_1"), @@ -537,13 +537,13 @@ order_of_groups_in_multiple_dirs(Config) when is_list(Config) -> {groups,TestDir2,groups_22_SUITE,test_group_1a}, {groups,TestDir1,groups_12_SUITE,test_group_1b}], - setup_and_execute(order_of_groups_in_multiple_dirs, + setup_and_execute(order_of_groups_many_dirs, TestSpec, Config). %%%----------------------------------------------------------------- %%% -order_of_groups_in_multiple_suites(Config) when is_list(Config) -> +order_of_groups_many_suites(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), TestDir1 = filename:join(DataDir, "groups_1"), @@ -551,13 +551,13 @@ order_of_groups_in_multiple_suites(Config) when is_list(Config) -> {groups,TestDir1,groups_11_SUITE,test_group_1a}, {groups,TestDir1,groups_12_SUITE,test_group_1b}], - setup_and_execute(order_of_groups_in_multiple_suites, + setup_and_execute(order_of_groups_many_suites, TestSpec, Config). %%%----------------------------------------------------------------- %%% -order_of_tests_in_multiple_suites_with_skip(Config) when is_list(Config) -> +order_of_tests_many_suites_with_skip(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), TestDir1 = filename:join(DataDir, "groups_1"), @@ -567,7 +567,7 @@ order_of_tests_in_multiple_suites_with_skip(Config) when is_list(Config) -> {cases,TestDir1,groups_11_SUITE,[testcase_2]}, {skip_cases,TestDir1,groups_12_SUITE,[testcase_1b],"Skip it!"}], - setup_and_execute(order_of_tests_in_multiple_suites_with_skip, + setup_and_execute(order_of_tests_many_suites_with_skip, TestSpec, Config). %%%----------------------------------------------------------------- @@ -1204,10 +1204,10 @@ test_events(sub_skipped_by_top) -> {negative,{?eh,tc_start,'_'},{?eh,stop_logging,'_'}} ]; -test_events(testcase_in_multiple_groups) -> +test_events(testcase_many_groups) -> []; -test_events(order_of_tests_in_multiple_dirs_no_merge_tests) -> +test_events(order_of_tests_many_dirs_no_merge_tests) -> [{?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,tc_start,{groups_12_SUITE,testcase_1a}}, {?eh,tc_done, {groups_12_SUITE,testcase_1a, @@ -1219,7 +1219,7 @@ test_events(order_of_tests_in_multiple_dirs_no_merge_tests) -> {failed,{error,{test_case_failed,no_group_data}}}}}, {?eh,stop_logging,[]} ]; -test_events(order_of_tests_in_multiple_suites_no_merge_tests) -> +test_events(order_of_tests_many_suites_no_merge_tests) -> [{?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,tc_start,{groups_12_SUITE,testcase_1a}}, {?eh,tc_done,{groups_12_SUITE,testcase_1a,'_'}}, @@ -1229,7 +1229,7 @@ test_events(order_of_tests_in_multiple_suites_no_merge_tests) -> {?eh,tc_done,{groups_12_SUITE,testcase_1b,'_'}}, {?eh,stop_logging,[]} ]; -test_events(order_of_suites_in_multiple_dirs_no_merge_tests) -> +test_events(order_of_suites_many_dirs_no_merge_tests) -> [{?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,tc_start,{groups_12_SUITE,init_per_suite}}, {?eh,tc_done,{groups_12_SUITE,init_per_suite,'_'}}, @@ -1244,7 +1244,7 @@ test_events(order_of_suites_in_multiple_dirs_no_merge_tests) -> {?eh,tc_start,{groups_11_SUITE,end_per_suite}}, {?eh,tc_done,{groups_11_SUITE,end_per_suite,'_'}}, {?eh,stop_logging,[]}]; -test_events(order_of_groups_in_multiple_dirs_no_merge_tests) -> +test_events(order_of_groups_many_dirs_no_merge_tests) -> [{?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,tc_start, {groups_12_SUITE,{init_per_group,test_group_1a,'_'}}}, @@ -1257,7 +1257,7 @@ test_events(order_of_groups_in_multiple_dirs_no_merge_tests) -> {?eh,tc_done, {groups_12_SUITE,{end_per_group,test_group_1b,'_'},'_'}}, {?eh,stop_logging,[]}]; -test_events(order_of_groups_in_multiple_suites_no_merge_tests) -> +test_events(order_of_groups_many_suites_no_merge_tests) -> [{?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,tc_start, {groups_12_SUITE,{init_per_group,test_group_1a,'_'}}}, @@ -1270,7 +1270,7 @@ test_events(order_of_groups_in_multiple_suites_no_merge_tests) -> {?eh,tc_done, {groups_12_SUITE,{end_per_group,test_group_1b,'_'},'_'}}, {?eh,stop_logging,[]}]; -test_events(order_of_tests_in_multiple_suites_with_skip_no_merge_tests) -> +test_events(order_of_tests_many_suites_with_skip_no_merge_tests) -> [{?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,tc_start,{groups_12_SUITE,testcase_1a}}, {?eh,tc_done,{groups_12_SUITE,testcase_1a,'_'}}, @@ -1282,7 +1282,7 @@ test_events(order_of_tests_in_multiple_suites_with_skip_no_merge_tests) -> {?eh,stop_logging,[]} ]; -test_events(order_of_tests_in_multiple_dirs) -> +test_events(order_of_tests_many_dirs) -> [{?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,tc_start,{groups_12_SUITE,testcase_1a}}, {?eh,tc_done, @@ -1296,7 +1296,7 @@ test_events(order_of_tests_in_multiple_dirs) -> {?eh,tc_done,{groups_22_SUITE,testcase_1,ok}}, {?eh,stop_logging,[]} ]; -test_events(order_of_tests_in_multiple_suites) -> +test_events(order_of_tests_many_suites) -> [{?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,tc_start,{groups_12_SUITE,testcase_1a}}, {?eh,tc_done,{groups_12_SUITE,testcase_1a,'_'}}, @@ -1308,7 +1308,7 @@ test_events(order_of_tests_in_multiple_suites) -> {?eh,tc_done,{groups_11_SUITE,testcase_1,ok}}, {?eh,stop_logging,[]} ]; -test_events(order_of_suites_in_multiple_dirs) -> +test_events(order_of_suites_many_dirs) -> [{?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,tc_start,{groups_12_SUITE,init_per_suite}}, {?eh,tc_done,{groups_12_SUITE,init_per_suite,'_'}}, @@ -1325,7 +1325,7 @@ test_events(order_of_suites_in_multiple_dirs) -> {?eh,tc_start,{groups_22_SUITE,end_per_suite}}, {?eh,tc_done,{groups_22_SUITE,end_per_suite,'_'}}, {?eh,stop_logging,[]}]; -test_events(order_of_groups_in_multiple_dirs) -> +test_events(order_of_groups_many_dirs) -> [{?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,tc_start, {groups_12_SUITE,{init_per_group,test_group_1a,'_'}}}, @@ -1338,7 +1338,7 @@ test_events(order_of_groups_in_multiple_dirs) -> {?eh,tc_done, {groups_22_SUITE,{end_per_group,test_group_1a,'_'},'_'}}, {?eh,stop_logging,[]}]; -test_events(order_of_groups_in_multiple_suites) -> +test_events(order_of_groups_many_suites) -> [{?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,tc_start, {groups_12_SUITE,{init_per_group,test_group_1a,'_'}}}, @@ -1352,7 +1352,7 @@ test_events(order_of_groups_in_multiple_suites) -> {?eh,stop_logging,[]}]; -test_events(order_of_tests_in_multiple_suites_with_skip) -> +test_events(order_of_tests_many_suites_with_skip) -> [{?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,tc_start,{groups_12_SUITE,testcase_1a}}, {?eh,tc_done,{groups_12_SUITE,testcase_1a,'_'}}, diff --git a/lib/mnesia/test/mnesia_recovery_test.erl b/lib/mnesia/test/mnesia_recovery_test.erl index 625e6e824c..c4910a4b11 100644 --- a/lib/mnesia/test/mnesia_recovery_test.erl +++ b/lib/mnesia/test/mnesia_recovery_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% Copyright Ericsson AB 1996-2012. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -99,21 +99,21 @@ groups() -> async_dirty_post_kill_coord_node, async_dirty_post_kill_coord_pid]}, {asym_trans, [], - [asym_trans_kill_part_ask, - asym_trans_kill_part_commit_vote, - asym_trans_kill_part_pre_commit, - asym_trans_kill_part_log_commit, - asym_trans_kill_part_do_commit, - asym_trans_kill_coord_got_votes, - asym_trans_kill_coord_pid_got_votes, - asym_trans_kill_coord_log_commit_rec, - asym_trans_kill_coord_pid_log_commit_rec, - asym_trans_kill_coord_log_commit_dec, - asym_trans_kill_coord_pid_log_commit_dec, - asym_trans_kill_coord_rec_acc_pre_commit_log_commit, - asym_trans_kill_coord_pid_rec_acc_pre_commit_log_commit, - asym_trans_kill_coord_rec_acc_pre_commit_done_commit, - asym_trans_kill_coord_pid_rec_acc_pre_commit_done_commit]}, + [asymtrans_part_ask, + asymtrans_part_commit_vote, + asymtrans_part_pre_commit, + asymtrans_part_log_commit, + asymtrans_part_do_commit, + asymtrans_coord_got_votes, + asymtrans_coord_pid_got_votes, + asymtrans_coord_log_commit_rec, + asymtrans_coord_pid_log_commit_rec, + asymtrans_coord_log_commit_dec, + asymtrans_coord_pid_log_commit_dec, + asymtrans_coord_rec_acc_pre_commit_log_commit, + asymtrans_coord_pid_rec_acc_pre_commit_log_commit, + asymtrans_coord_rec_acc_pre_commit_done_commit, + asymtrans_coord_pid_rec_acc_pre_commit_done_commit]}, {after_corrupt_files, [], [after_corrupt_files_decision_log_head, after_corrupt_files_decision_log_tail, @@ -978,8 +978,8 @@ do_async_dirty([Tab], _Fahter) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -asym_trans_kill_part_ask(suite) -> []; -asym_trans_kill_part_ask(Config) when is_list(Config) -> +asymtrans_part_ask(suite) -> []; +asymtrans_part_ask(Config) when is_list(Config) -> ?is_debug_compiled, Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), [Coord, Part1, Part2] = Nodes, @@ -989,8 +989,8 @@ asym_trans_kill_part_ask(Config) when is_list(Config) -> kill_after_debug_point(Part1, {Part1, {mnesia_tm, doit_ask_commit}}, TransFun, [Tab1, Tab2], Nodes). -asym_trans_kill_part_commit_vote(suite) -> []; -asym_trans_kill_part_commit_vote(Config) when is_list(Config) -> +asymtrans_part_commit_vote(suite) -> []; +asymtrans_part_commit_vote(Config) when is_list(Config) -> ?is_debug_compiled, Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), [Coord, Part1, Part2] = Nodes, @@ -1000,8 +1000,8 @@ asym_trans_kill_part_commit_vote(Config) when is_list(Config) -> kill_after_debug_point(Part1, {Part1, {mnesia_tm, commit_participant, vote_yes}}, TransFun, [Tab1, Tab2], Nodes). -asym_trans_kill_part_pre_commit(suite) -> []; -asym_trans_kill_part_pre_commit(Config) when is_list(Config) -> +asymtrans_part_pre_commit(suite) -> []; +asymtrans_part_pre_commit(Config) when is_list(Config) -> ?is_debug_compiled, Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), [Coord, Part1, Part2] = Nodes, @@ -1011,8 +1011,8 @@ asym_trans_kill_part_pre_commit(Config) when is_list(Config) -> kill_after_debug_point(Part1, {Part1, {mnesia_tm, commit_participant, pre_commit}}, TransFun, [Tab1, Tab2], Nodes). -asym_trans_kill_part_log_commit(suite) -> []; -asym_trans_kill_part_log_commit(Config) when is_list(Config) -> +asymtrans_part_log_commit(suite) -> []; +asymtrans_part_log_commit(Config) when is_list(Config) -> ?is_debug_compiled, Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), [Coord, Part1, Part2] = Nodes, @@ -1022,8 +1022,8 @@ asym_trans_kill_part_log_commit(Config) when is_list(Config) -> kill_after_debug_point(Part1, {Part1, {mnesia_tm, commit_participant, log_commit}}, TransFun, [Tab1, Tab2], Nodes). -asym_trans_kill_part_do_commit(suite) -> []; -asym_trans_kill_part_do_commit(Config) when is_list(Config) -> +asymtrans_part_do_commit(suite) -> []; +asymtrans_part_do_commit(Config) when is_list(Config) -> ?is_debug_compiled, Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), [Coord, Part1, Part2] = Nodes, @@ -1033,8 +1033,8 @@ asym_trans_kill_part_do_commit(Config) when is_list(Config) -> kill_after_debug_point(Part1, {Part1, {mnesia_tm, commit_participant, do_commit}}, TransFun, [Tab1, Tab2], Nodes). -asym_trans_kill_coord_got_votes(suite) -> []; -asym_trans_kill_coord_got_votes(Config) when is_list(Config) -> +asymtrans_coord_got_votes(suite) -> []; +asymtrans_coord_got_votes(Config) when is_list(Config) -> ?is_debug_compiled, Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), [Coord, Part1, Part2] = Nodes, @@ -1044,8 +1044,8 @@ asym_trans_kill_coord_got_votes(Config) when is_list(Config) -> kill_after_debug_point(Coord, {Coord, {mnesia_tm, multi_commit_asym_got_votes}}, TransFun, [Tab1, Tab2], Nodes). -asym_trans_kill_coord_pid_got_votes(suite) -> []; -asym_trans_kill_coord_pid_got_votes(Config) when is_list(Config) -> +asymtrans_coord_pid_got_votes(suite) -> []; +asymtrans_coord_pid_got_votes(Config) when is_list(Config) -> ?is_debug_compiled, Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), [Coord, Part1, Part2] = Nodes, @@ -1055,8 +1055,8 @@ asym_trans_kill_coord_pid_got_votes(Config) when is_list(Config) -> kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, multi_commit_asym_got_votes}}, TransFun, [Tab1, Tab2], Nodes). -asym_trans_kill_coord_log_commit_rec(suite) -> []; -asym_trans_kill_coord_log_commit_rec(Config) when is_list(Config) -> +asymtrans_coord_log_commit_rec(suite) -> []; +asymtrans_coord_log_commit_rec(Config) when is_list(Config) -> ?is_debug_compiled, Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), [Coord, Part1, Part2] = Nodes, @@ -1066,8 +1066,8 @@ asym_trans_kill_coord_log_commit_rec(Config) when is_list(Config) -> kill_after_debug_point(Coord, {Coord, {mnesia_tm, multi_commit_asym_log_commit_rec}}, TransFun, [Tab1, Tab2], Nodes). -asym_trans_kill_coord_pid_log_commit_rec(suite) -> []; -asym_trans_kill_coord_pid_log_commit_rec(Config) when is_list(Config) -> +asymtrans_coord_pid_log_commit_rec(suite) -> []; +asymtrans_coord_pid_log_commit_rec(Config) when is_list(Config) -> ?is_debug_compiled, Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), [Coord, Part1, Part2] = Nodes, @@ -1077,8 +1077,8 @@ asym_trans_kill_coord_pid_log_commit_rec(Config) when is_list(Config) -> kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, multi_commit_asym_log_commit_rec}}, TransFun, [Tab1, Tab2], Nodes). -asym_trans_kill_coord_log_commit_dec(suite) -> []; -asym_trans_kill_coord_log_commit_dec(Config) when is_list(Config) -> +asymtrans_coord_log_commit_dec(suite) -> []; +asymtrans_coord_log_commit_dec(Config) when is_list(Config) -> ?is_debug_compiled, Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), [Coord, Part1, Part2] = Nodes, @@ -1088,8 +1088,8 @@ asym_trans_kill_coord_log_commit_dec(Config) when is_list(Config) -> kill_after_debug_point(Coord, {Coord, {mnesia_tm, multi_commit_asym_log_commit_dec}}, TransFun, [Tab1, Tab2], Nodes). -asym_trans_kill_coord_pid_log_commit_dec(suite) -> []; -asym_trans_kill_coord_pid_log_commit_dec(Config) when is_list(Config) -> +asymtrans_coord_pid_log_commit_dec(suite) -> []; +asymtrans_coord_pid_log_commit_dec(Config) when is_list(Config) -> ?is_debug_compiled, Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), [Coord, Part1, Part2] = Nodes, @@ -1099,8 +1099,8 @@ asym_trans_kill_coord_pid_log_commit_dec(Config) when is_list(Config) -> kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, multi_commit_asym_log_commit_dec}}, TransFun, [Tab1, Tab2], Nodes). -asym_trans_kill_coord_rec_acc_pre_commit_log_commit(suite) -> []; -asym_trans_kill_coord_rec_acc_pre_commit_log_commit(Config) when is_list(Config) -> +asymtrans_coord_rec_acc_pre_commit_log_commit(suite) -> []; +asymtrans_coord_rec_acc_pre_commit_log_commit(Config) when is_list(Config) -> ?is_debug_compiled, Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), [Coord, Part1, Part2] = Nodes, @@ -1110,8 +1110,8 @@ asym_trans_kill_coord_rec_acc_pre_commit_log_commit(Config) when is_list(Config) kill_after_debug_point(Coord, {Coord, {mnesia_tm, rec_acc_pre_commit_log_commit}}, TransFun, [Tab1, Tab2], Nodes). -asym_trans_kill_coord_pid_rec_acc_pre_commit_log_commit(suite) -> []; -asym_trans_kill_coord_pid_rec_acc_pre_commit_log_commit(Config) when is_list(Config) -> +asymtrans_coord_pid_rec_acc_pre_commit_log_commit(suite) -> []; +asymtrans_coord_pid_rec_acc_pre_commit_log_commit(Config) when is_list(Config) -> ?is_debug_compiled, Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), [Coord, Part1, Part2] = Nodes, @@ -1121,8 +1121,8 @@ asym_trans_kill_coord_pid_rec_acc_pre_commit_log_commit(Config) when is_list(Con kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, rec_acc_pre_commit_log_commit}}, TransFun, [Tab1, Tab2], Nodes). -asym_trans_kill_coord_rec_acc_pre_commit_done_commit(suite) -> []; -asym_trans_kill_coord_rec_acc_pre_commit_done_commit(Config) when is_list(Config) -> +asymtrans_coord_rec_acc_pre_commit_done_commit(suite) -> []; +asymtrans_coord_rec_acc_pre_commit_done_commit(Config) when is_list(Config) -> ?is_debug_compiled, Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), [Coord, Part1, Part2] = Nodes, @@ -1132,8 +1132,8 @@ asym_trans_kill_coord_rec_acc_pre_commit_done_commit(Config) when is_list(Config kill_after_debug_point(Coord, {Coord, {mnesia_tm, rec_acc_pre_commit_done_commit}}, TransFun, [Tab1, Tab2], Nodes). -asym_trans_kill_coord_pid_rec_acc_pre_commit_done_commit(suite) -> []; -asym_trans_kill_coord_pid_rec_acc_pre_commit_done_commit(Config) when is_list(Config) -> +asymtrans_coord_pid_rec_acc_pre_commit_done_commit(suite) -> []; +asymtrans_coord_pid_rec_acc_pre_commit_done_commit(Config) when is_list(Config) -> ?is_debug_compiled, Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]), [Coord, Part1, Part2] = Nodes, diff --git a/lib/percept/src/percept.app.src b/lib/percept/src/percept.app.src index c70fede721..7b20093ece 100644 --- a/lib/percept/src/percept.app.src +++ b/lib/percept/src/percept.app.src @@ -17,14 +17,26 @@ %% %CopyrightEnd% %% -{application,percept, - [{description, "PERCEPT Erlang Concurrency Profiling Tool"}, - {vsn, "%VSN%"}, - {modules, [percept,percept_db,percept_html,percept_graph,percept_analyzer]}, - {registered, [percept_db,percept_port]}, - {applications, [kernel,stdlib]}, - {env, []} - ]}. - +{application,percept, [ + {description, "PERCEPT Erlang Concurrency Profiling Tool"}, + {vsn, "%VSN%"}, + {modules, [ + egd, + egd_font, + egd_png, + egd_primitives, + egd_render, + percept, + percept_analyzer, + percept_db, + percept_graph, + percept_html, + percept_image + ]}, + {registered, [percept_db,percept_port]}, + {applications, [kernel,stdlib]}, + {env,[]} +]}. +%% vim: syntax=erlang diff --git a/lib/reltool/test/reltool_server_SUITE.erl b/lib/reltool/test/reltool_server_SUITE.erl index 773b11583e..8d71865508 100644 --- a/lib/reltool/test/reltool_server_SUITE.erl +++ b/lib/reltool/test/reltool_server_SUITE.erl @@ -1084,9 +1084,14 @@ create_slim(Config) -> RootDir = code:root_dir(), Erl = filename:join([RootDir, "bin", "erl"]), - Args = "-boot_var RELTOOL_EXT_LIB " ++ TargetLibDir ++ - " -boot " ++ filename:join(TargetRelVsnDir,RelName) ++ - " -sasl releases_dir \\\"" ++ TargetRelDir ++ "\\\"", + EscapedQuote = + case os:type() of + {win32,_} -> "\\\""; + _ -> "\"" + end, + Args = ["-boot_var", "RELTOOL_EXT_LIB", TargetLibDir, + "-boot", filename:join(TargetRelVsnDir,RelName), + "-sasl", "releases_dir", EscapedQuote++TargetRelDir++EscapedQuote], {ok, Node} = ?msym({ok, _}, start_node(?NODE_NAME, Erl, Args)), ?msym(RootDir, rpc:call(Node, code, root_dir, [])), ?msym([{RelName,RelVsn,_,permanent}], @@ -2412,11 +2417,13 @@ mod_path(Node,Mod) -> start_node(Name, ErlPath) -> start_node(Name, ErlPath, []). -start_node(Name, ErlPath, Args) -> +start_node(Name, ErlPath, Args0) -> FullName = full_node_name(Name), - CmdLine = mk_node_cmdline(Name, ErlPath, Args), - io:format("Starting node ~p: ~s~n", [FullName, CmdLine]), - case open_port({spawn, CmdLine}, []) of + Args = mk_node_args(Name, Args0), + io:format("Starting node ~p: ~s~n", + [FullName, lists:flatten([[X," "] || X <- [ErlPath|Args]])]), + %io:format("open_port({spawn_executable, ~p}, [{args,~p}])~n",[ErlPath,Args]), + case open_port({spawn_executable, ErlPath}, [{args,Args}]) of Port when is_port(Port) -> unlink(Port), erlang:port_close(Port), @@ -2433,23 +2440,21 @@ stop_node(Node) -> spawn(Node, fun () -> halt() end), receive {nodedown, Node} -> ok end. -mk_node_cmdline(Name, Prog, Args) -> - Static = "-detached -noinput", +mk_node_args(Name, Args) -> Pa = filename:dirname(code:which(?MODULE)), NameSw = case net_kernel:longnames() of - false -> "-sname "; - true -> "-name "; + false -> "-sname"; + true -> "-name"; _ -> exit(not_distributed_node) end, {ok, Pwd} = file:get_cwd(), NameStr = atom_to_list(Name), - Prog ++ " " - ++ Static ++ " " - ++ NameSw ++ " " ++ NameStr ++ " " - ++ "-pa " ++ Pa ++ " " - ++ "-env ERL_CRASH_DUMP " ++ Pwd ++ "/erl_crash_dump." ++ NameStr ++ " " - ++ "-setcookie " ++ atom_to_list(erlang:get_cookie()) - ++ " " ++ Args. + ["-detached", "-noinput", + NameSw, NameStr, + "-pa", Pa, + "-env", "ERL_CRASH_DUMP", Pwd ++ "/erl_crash_dump." ++ NameStr, + "-setcookie", atom_to_list(erlang:get_cookie()) + | Args]. full_node_name(PreName) -> HostSuffix = lists:dropwhile(fun ($@) -> false; (_) -> true end, diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index c2a7c63cbe..e3b8ebfb79 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -441,7 +441,7 @@ handle_msg(#ssh_msg_channel_window_adjust{recipient_channel = ChannelId, {SendList, Channel} = %% TODO: Datatype 0 ? update_send_window(Channel0#channel{send_window_size = Size + Add}, - 0, <<>>, Connection), + 0, undefined, Connection), Replies = lists:map(fun({Type, Data}) -> {connection_reply, ConnectionPid, @@ -1073,14 +1073,15 @@ request_reply_or_data(#channel{local_id = ChannelId, user = ChannelPid}, false -> {{channel_data, ChannelPid, Reply}, Connection} end. +update_send_window(Channel, _, undefined, + #connection{channel_cache = Cache}) -> + do_update_send_window(Channel, Channel#channel.send_buf, Cache); -update_send_window(Channel0, DataType, Data, - #connection{channel_cache = Cache}) -> - Buf0 = if Data == <<>> -> - Channel0#channel.send_buf; - true -> - Channel0#channel.send_buf ++ [{DataType, Data}] - end, +update_send_window(Channel, DataType, Data, + #connection{channel_cache = Cache}) -> + do_update_send_window(Channel, Channel#channel.send_buf ++ [{DataType, Data}], Cache). + +do_update_send_window(Channel0, Buf0, Cache) -> {Buf1, NewSz, Buf2} = get_window(Buf0, Channel0#channel.send_packet_size, Channel0#channel.send_window_size), diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl index 2ceaa9daa5..c224e5b800 100644 --- a/lib/ssh/test/ssh_basic_SUITE.erl +++ b/lib/ssh/test/ssh_basic_SUITE.erl @@ -110,12 +110,13 @@ all() -> {group, rsa_pass_key}, {group, internal_error}, daemon_already_started, - server_password_option, server_userpassword_option, + server_password_option, + server_userpassword_option, close]. groups() -> - [{dsa_key, [], [exec, exec_compressed, shell, known_hosts]}, - {rsa_key, [], [exec, exec_compressed, shell, known_hosts]}, + [{dsa_key, [], [send, exec, exec_compressed, shell, known_hosts]}, + {rsa_key, [], [send, exec, exec_compressed, shell, known_hosts]}, {dsa_pass_key, [], [pass_phrase]}, {rsa_pass_key, [], [pass_phrase]}, {internal_error, [], [internal_error]} @@ -532,6 +533,31 @@ internal_error(Config) when is_list(Config) -> ssh:stop_daemon(Pid). %%-------------------------------------------------------------------- +send(doc) -> + ["Test ssh_connection:send/3"]; + +send(suite) -> + []; + +send(Config) when is_list(Config) -> + process_flag(trap_exit, true), + 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}]), + {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity), + ok = ssh_connection:send(ConnectionRef, ChannelId, <<"Data">>), + ok = ssh_connection:send(ConnectionRef, ChannelId, << >>), + ssh:stop_daemon(Pid). + + +%%-------------------------------------------------------------------- close(doc) -> ["Simulate that we try to close an already closed connection"]; diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index 5098d26a3a..f0eac76264 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -79,7 +79,9 @@ {keyfile, path()} | {password, string()} | {cacerts, [der_encoded()]} | {cacertfile, path()} | |{dh, der_encoded()} | {dhfile, path()} | {ciphers, ciphers()} | - {ssl_imp, ssl_imp()}| {reuse_sessions, boolean()} | {reuse_session, fun()} + {ssl_imp, ssl_imp()} | {reuse_sessions, boolean()} | {reuse_session, fun()} + {next_protocols_advertised, list(binary()} | + {client_preferred_next_protocols, binary(), client | server, list(binary())} </c></p> <p><c>transportoption() = {CallbackModule, DataTag, ClosedTag} @@ -301,8 +303,29 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} | when possible. </item> + <tag>{client_preferred_next_protocols, Precedence:: server | client, ClientPrefs::[binary()]} + {client_preferred_next_protocols, Precedence:: server | client, ClientPrefs::[binary()] , Default :: binary()}}</tag> + + <item> <p>Indicates the client will try to perform Next Protocol + Negotiation.</p> + + <p>If precedence is server the negaotiated protocol will be the + first protocol that appears on the server advertised list that is + also on the clients preference list.</p> + + <p>If the precedence is client the negaotiated protocol will be the + first protocol that appears on the clients preference list that is + also on the server advertised list.</p> + + <p> If the client does not support any of the servers advertised + protocols or the server does not advertise any protocols the + client will fallback to the first protocol in its list or if a + default is supplied it will fallback to that instead. If the + server does not support next protocol renegotiation the + connection will be aborted if no default protocol is supplied.</p> + </item> </taglist> - </section> + </section> <section> <title>SSL OPTION DESCRIPTIONS - SERVER SIDE</title> @@ -353,6 +376,14 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} | SuggestedSessionId is a binary(), PeerCert is a DER encoded certificate, Compression is an enumeration integer and CipherSuite is of type ciphersuite(). + </item> + + <tag>{next_protocols_advertised, Protocols :: list(binary())}</tag> + <item>The list of protocols to send to the client if the client indicates + it supports the Next Protocol extension. The client may select a protocol + that is not on this list. The list of protocols must not contain an empty + binary. If the server negotiates a Next Protocol it can be accessed + using <c>negotiated_next_protocol/1</c> method. </item> </taglist> @@ -766,8 +797,23 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} | ssl application.</p> </desc> </func> + <func> + <name>negotiated_next_protocol(Socket) -> {ok, Protocol} | {error, next_protocol_not_negotiated}</name> + <fsummary>Returns the Next Protocol negotiated.</fsummary> + <type> + <v>Socket = sslsocket()</v> + <v>Protocol = binary()</v> + </type> + <desc> + <p> + Returns the Next Protocol negotiated. + </p> + </desc> + </func> + + </funcs> - + <section> <title>SEE ALSO</title> <p><seealso marker="kernel:inet">inet(3) </seealso> and diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 40d933a256..9a562aa5a8 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -31,13 +31,15 @@ controlling_process/2, listen/2, pid/1, peername/1, peercert/1, recv/2, recv/3, send/2, getopts/2, setopts/2, sockname/1, versions/0, session_info/1, format_error/1, - renegotiate/1, prf/5, clear_pem_cache/0, random_bytes/1]). + renegotiate/1, prf/5, clear_pem_cache/0, random_bytes/1, negotiated_next_protocol/1]). + -deprecated({pid, 1, next_major_release}). -include("ssl_internal.hrl"). -include("ssl_record.hrl"). -include("ssl_cipher.hrl"). +-include("ssl_handshake.hrl"). -include_lib("public_key/include/public_key.hrl"). @@ -65,7 +67,9 @@ {keyfile, path()} | {password, string()} | {cacerts, [Der::binary()]} | {cacertfile, path()} | {dh, Der::binary()} | {dhfile, path()} | {ciphers, ciphers()} | {ssl_imp, ssl_imp()} | {reuse_sessions, boolean()} | - {reuse_session, fun()} | {hibernate_after, integer()|undefined}. + {reuse_session, fun()} | {hibernate_after, integer()|undefined} | + {next_protocols_advertised, list(binary())} | + {client_preferred_next_protocols, binary(), client | server, list(binary())}. -type verify_type() :: verify_none | verify_peer. -type path() :: string(). @@ -314,6 +318,14 @@ suite_definition(S) -> {KeyExchange, Cipher, Hash}. %%-------------------------------------------------------------------- +-spec negotiated_next_protocol(#sslsocket{}) -> {ok, binary()} | {error, reason()}. +%% +%% Description: Returns the next protocol that has been negotiated. If no +%% protocol has been negotiated will return {error, next_protocol_not_negotiated} +%%-------------------------------------------------------------------- +negotiated_next_protocol(#sslsocket{fd = new_ssl, pid = Pid}) -> + ssl_connection:negotiated_next_protocol(Pid). + -spec cipher_suites() -> [erl_cipher_suite()]. -spec cipher_suites(erlang | openssl) -> [erl_cipher_suite()] | [string()]. @@ -594,7 +606,9 @@ handle_options(Opts0, _Role) -> renegotiate_at = handle_option(renegotiate_at, Opts, ?DEFAULT_RENEGOTIATE_AT), debug = handle_option(debug, Opts, []), hibernate_after = handle_option(hibernate_after, Opts, undefined), - erl_dist = handle_option(erl_dist, Opts, false) + erl_dist = handle_option(erl_dist, Opts, false), + next_protocols_advertised = handle_option(next_protocols_advertised, Opts, undefined), + next_protocol_selector = make_next_protocol_selector(handle_option(client_preferred_next_protocols, Opts, undefined)) }, CbInfo = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed, tcp_error}), @@ -603,7 +617,8 @@ handle_options(Opts0, _Role) -> depth, cert, certfile, key, keyfile, password, cacerts, cacertfile, dh, dhfile, ciphers, debug, reuse_session, reuse_sessions, ssl_imp, - cb_info, renegotiate_at, secure_renegotiate, hibernate_after, erl_dist], + cb_info, renegotiate_at, secure_renegotiate, hibernate_after, erl_dist, next_protocols_advertised, + client_preferred_next_protocols], SockOpts = lists:foldl(fun(Key, PropList) -> proplists:delete(Key, PropList) @@ -728,12 +743,64 @@ validate_option(hibernate_after, undefined) -> undefined; validate_option(hibernate_after, Value) when is_integer(Value), Value >= 0 -> Value; -validate_option(erl_dist,Value) when Value == true; +validate_option(erl_dist,Value) when Value == true; Value == false -> Value; +validate_option(client_preferred_next_protocols = Opt, {Precedence, PreferredProtocols} = Value) + when is_list(PreferredProtocols) -> + case ssl_record:highest_protocol_version([]) of + {3,0} -> + throw({error, {eoptions, {not_supported_in_sslv3, {Opt, Value}}}}); + _ -> + validate_binary_list(client_preferred_next_protocols, PreferredProtocols), + validate_npn_ordering(Precedence), + {Precedence, PreferredProtocols, ?NO_PROTOCOL} + end; +validate_option(client_preferred_next_protocols = Opt, {Precedence, PreferredProtocols, Default} = Value) + when is_list(PreferredProtocols), is_binary(Default), + byte_size(Default) > 0, byte_size(Default) < 256 -> + case ssl_record:highest_protocol_version([]) of + {3,0} -> + throw({error, {eoptions, {not_supported_in_sslv3, {Opt, Value}}}}); + _ -> + validate_binary_list(client_preferred_next_protocols, PreferredProtocols), + validate_npn_ordering(Precedence), + Value + end; + +validate_option(client_preferred_next_protocols, undefined) -> + undefined; +validate_option(next_protocols_advertised = Opt, Value) when is_list(Value) -> + case ssl_record:highest_protocol_version([]) of + {3,0} -> + throw({error, {eoptions, {not_supported_in_sslv3, {Opt, Value}}}}); + _ -> + validate_binary_list(next_protocols_advertised, Value), + Value + end; + +validate_option(next_protocols_advertised, undefined) -> + undefined; validate_option(Opt, Value) -> throw({error, {eoptions, {Opt, Value}}}). - + +validate_npn_ordering(client) -> + ok; +validate_npn_ordering(server) -> + ok; +validate_npn_ordering(Value) -> + throw({error, {eoptions, {client_preferred_next_protocols, {invalid_precedence, Value}}}}). + +validate_binary_list(Opt, List) -> + lists:foreach( + fun(Bin) when is_binary(Bin), + byte_size(Bin) > 0, + byte_size(Bin) < 256 -> + ok; + (Bin) -> + throw({error, {eoptions, {Opt, {invalid_protocol, Bin}}}}) + end, List). + validate_versions([], Versions) -> Versions; validate_versions([Version | Rest], Versions) when Version == 'tlsv1.2'; @@ -839,6 +906,34 @@ cipher_suites(Version, Ciphers0) -> no_format(Error) -> lists:flatten(io_lib:format("No format string for error: \"~p\" available.", [Error])). + +detect(_Pred, []) -> + undefined; +detect(Pred, [H|T]) -> + case Pred(H) of + true -> + H; + _ -> + detect(Pred, T) + end. + +make_next_protocol_selector(undefined) -> + undefined; +make_next_protocol_selector({client, AllProtocols, DefaultProtocol}) -> + fun(AdvertisedProtocols) -> + case detect(fun(PreferredProtocol) -> lists:member(PreferredProtocol, AdvertisedProtocols) end, AllProtocols) of + undefined -> DefaultProtocol; + PreferredProtocol -> PreferredProtocol + end + end; + +make_next_protocol_selector({server, AllProtocols, DefaultProtocol}) -> + fun(AdvertisedProtocols) -> + case detect(fun(PreferredProtocol) -> lists:member(PreferredProtocol, AllProtocols) end, AdvertisedProtocols) of + undefined -> DefaultProtocol; + PreferredProtocol -> PreferredProtocol + end + end. %% Only used to remove exit messages from old ssl %% First is a nonsense clause to provide some diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index ff2556c488..23f22987df 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -41,7 +41,7 @@ socket_control/3, close/1, shutdown/2, new_user/2, get_opts/2, set_opts/2, info/1, session_info/1, peer_certificate/1, sockname/1, peername/1, renegotiation/1, - prf/5]). + negotiated_next_protocol/1, prf/5]). %% Called by ssl_connection_sup -export([start_link/7]). @@ -92,7 +92,9 @@ start_or_recv_from, % "gen_fsm From" send_queue, % queue() terminated = false, % - allow_renegotiate = true + allow_renegotiate = true, + expecting_next_protocol_negotiation = false :: boolean(), + next_protocol = undefined :: undefined | binary() }). -define(DEFAULT_DIFFIE_HELLMAN_PARAMS, @@ -221,6 +223,13 @@ new_user(ConnectionPid, User) -> sockname(ConnectionPid) -> sync_send_all_state_event(ConnectionPid, sockname). %%-------------------------------------------------------------------- +-spec negotiated_next_protocol(pid()) -> {ok, binary()} | {error, reason()}. +%% +%% Description: Returns the negotiated protocol +%%-------------------------------------------------------------------- +negotiated_next_protocol(ConnectionPid) -> + sync_send_all_state_event(ConnectionPid, negotiated_next_protocol). +%%-------------------------------------------------------------------- -spec peername(pid()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, reason()}. %% %% Description: Same as inet:peername/1 @@ -374,31 +383,41 @@ hello(#server_hello{cipher_suite = CipherSuite, renegotiation = {Renegotiation, _}, ssl_options = SslOptions} = State0) -> case ssl_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of - {Version, NewId, ConnectionStates} -> + #alert{} = Alert -> + handle_own_alert(Alert, ReqVersion, hello, State0), + {stop, normal, State0}; + + {Version, NewId, ConnectionStates, NextProtocol} -> {KeyAlgorithm, _, _, _} = ssl_cipher:suite_definition(CipherSuite), - + PremasterSecret = make_premaster_secret(ReqVersion, KeyAlgorithm), - + + NewNextProtocol = case NextProtocol of + undefined -> + State0#state.next_protocol; + _ -> + NextProtocol + end, + State = State0#state{key_algorithm = KeyAlgorithm, hashsign_algorithm = default_hashsign(Version, KeyAlgorithm), negotiated_version = Version, connection_states = ConnectionStates, - premaster_secret = PremasterSecret}, - + premaster_secret = PremasterSecret, + expecting_next_protocol_negotiation = NextProtocol =/= undefined, + next_protocol = NewNextProtocol}, + case ssl_session:is_new(OldId, NewId) of true -> handle_new_session(NewId, CipherSuite, Compression, State#state{connection_states = ConnectionStates}); false -> - handle_resumed_session(NewId, State#state{connection_states = ConnectionStates}) - end; - #alert{} = Alert -> - handle_own_alert(Alert, ReqVersion, hello, State0), - {stop, normal, State0} + handle_resumed_session(NewId, State#state{connection_states = ConnectionStates}) + end end; -hello(Hello = #client_hello{client_version = ClientVersion}, +hello(Hello = #client_hello{client_version = ClientVersion}, State = #state{connection_states = ConnectionStates0, port = Port, session = #session{own_certificate = Cert} = Session0, renegotiation = {Renegotiation, _}, @@ -407,8 +426,8 @@ hello(Hello = #client_hello{client_version = ClientVersion}, ssl_options = SslOpts}) -> case ssl_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert}, Renegotiation) of - {Version, {Type, Session}, ConnectionStates} -> - do_server_hello(Type, State#state{connection_states = + {Version, {Type, Session}, ConnectionStates, ProtocolsToAdvertise} -> + do_server_hello(Type, ProtocolsToAdvertise, State#state{connection_states = ConnectionStates, negotiated_version = Version, session = Session}); @@ -593,6 +612,7 @@ certify(#client_key_exchange{exchange_keys = Keys}, {stop, normal, State} end; + certify(timeout, State) -> { next_state, certify, State, hibernate }; @@ -662,6 +682,12 @@ cipher(#certificate_verify{signature = Signature, hashsign_algorithm = CertHashS {stop, normal, State0} end; +% client must send a next protocol message if we are expecting it +cipher(#finished{}, #state{role = server, expecting_next_protocol_negotiation = true, + next_protocol = undefined, negotiated_version = Version} = State0) -> + handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, cipher, State0), + {stop, normal, State0}; + cipher(#finished{verify_data = Data} = Finished, #state{negotiated_version = Version, host = Host, @@ -683,6 +709,13 @@ cipher(#finished{verify_data = Data} = Finished, {stop, normal, State} end; +% only allowed to send next_protocol message after change cipher spec +% & before finished message and it is not allowed during renegotiation +cipher(#next_protocol{selected_protocol = SelectedProtocol}, + #state{role = server, expecting_next_protocol_negotiation = true} = State0) -> + {Record, State} = next_record(State0#state{next_protocol = SelectedProtocol}), + next_state(cipher, cipher, Record, State); + cipher(timeout, State) -> { next_state, cipher, State, hibernate }; @@ -842,6 +875,10 @@ handle_sync_event(sockname, _From, StateName, SockNameReply = inet:sockname(Socket), {reply, SockNameReply, StateName, State, get_timeout(State)}; +handle_sync_event(negotiated_next_protocol, _From, StateName, #state{next_protocol = undefined} = State) -> + {reply, {error, next_protocol_not_negotiated}, StateName, State, get_timeout(State)}; +handle_sync_event(negotiated_next_protocol, _From, StateName, #state{next_protocol = NextProtocol} = State) -> + {reply, {ok, NextProtocol}, StateName, State, get_timeout(State)}; handle_sync_event(peername, _From, StateName, #state{socket = Socket} = State) -> PeerNameReply = inet:peername(Socket), @@ -1274,17 +1311,18 @@ verify_client_cert(#state{client_certificate_requested = true, role = client, verify_client_cert(#state{client_certificate_requested = false} = State) -> State. -do_server_hello(Type, #state{negotiated_version = Version, - session = #session{session_id = SessId}, - connection_states = ConnectionStates0, - renegotiation = {Renegotiation, _}} - = State0) when is_atom(Type) -> +do_server_hello(Type, NextProtocolsToSend, #state{negotiated_version = Version, + session = #session{session_id = SessId}, + connection_states = ConnectionStates0, + renegotiation = {Renegotiation, _}} + = State0) when is_atom(Type) -> ServerHello = ssl_handshake:server_hello(SessId, Version, - ConnectionStates0, Renegotiation), - State = server_hello(ServerHello, State0), - + ConnectionStates0, Renegotiation, NextProtocolsToSend), + State = server_hello(ServerHello, + State0#state{expecting_next_protocol_negotiation = + NextProtocolsToSend =/= undefined}), case Type of new -> new_server_hello(ServerHello, State); @@ -1538,12 +1576,33 @@ request_client_cert(#state{ssl_options = #ssl_options{verify = verify_none}} = State. finalize_handshake(State, StateName) -> - ConnectionStates0 = cipher_protocol(State), + ConnectionStates0 = cipher_protocol(State), + ConnectionStates = ssl_record:activate_pending_connection_state(ConnectionStates0, write), - finished(State#state{connection_states = ConnectionStates}, StateName). - + + State1 = State#state{connection_states = ConnectionStates}, + State2 = next_protocol(State1), + finished(State2, StateName). + +next_protocol(#state{role = server} = State) -> + State; +next_protocol(#state{next_protocol = undefined} = State) -> + State; +next_protocol(#state{expecting_next_protocol_negotiation = false} = State) -> + State; +next_protocol(#state{transport_cb = Transport, socket = Socket, + negotiated_version = Version, + next_protocol = NextProtocol, + connection_states = ConnectionStates0, + tls_handshake_history = Handshake0} = State) -> + NextProtocolMessage = ssl_handshake:next_protocol(NextProtocol), + {BinMsg, ConnectionStates, Handshake} = encode_handshake(NextProtocolMessage, Version, ConnectionStates0, Handshake0), + Transport:send(Socket, BinMsg), + State#state{connection_states = ConnectionStates, + tls_handshake_history = Handshake}. + cipher_protocol(#state{connection_states = ConnectionStates0, socket = Socket, negotiated_version = Version, @@ -2053,8 +2112,8 @@ next_state_is_connection(_, State = next_state_is_connection(StateName, State0) -> {Record, State} = next_record_if_active(State0), next_state(StateName, connection, Record, State#state{premaster_secret = undefined, - public_key_info = undefined, - tls_handshake_history = ssl_handshake:init_handshake_history()}). + public_key_info = undefined, + tls_handshake_history = ssl_handshake:init_handshake_history()}). register_session(client, Host, Port, #session{is_resumable = new} = Session0) -> Session = Session0#session{is_resumable = true}, diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index bb26302fff..fa1784714f 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -30,21 +30,21 @@ -include("ssl_internal.hrl"). -include_lib("public_key/include/public_key.hrl"). --export([master_secret/4, client_hello/8, server_hello/4, hello/4, +-export([master_secret/4, client_hello/8, server_hello/5, hello/4, hello_request/0, certify/7, certificate/4, client_certificate_verify/6, certificate_verify/6, certificate_request/3, key_exchange/3, server_key_exchange_hash/2, finished/5, verify_connection/6, get_tls_handshake/3, decode_client_key/3, server_hello_done/0, encode_handshake/2, init_handshake_history/0, update_handshake_history/2, - decrypt_premaster_secret/2, prf/5]). + decrypt_premaster_secret/2, prf/5, next_protocol/1]). -export([dec_hello_extensions/2]). -type tls_handshake() :: #client_hello{} | #server_hello{} | #server_hello_done{} | #certificate{} | #certificate_request{} | #client_key_exchange{} | #finished{} | #certificate_verify{} | - #hello_request{}. + #hello_request{} | #next_protocol{}. %%==================================================================== %% Internal application API @@ -77,18 +77,31 @@ client_hello(Host, Port, ConnectionStates, cipher_suites = cipher_suites(Ciphers, Renegotiation), compression_methods = ssl_record:compressions(), random = SecParams#security_parameters.client_random, + renegotiation_info = renegotiation_info(client, ConnectionStates, Renegotiation), - hash_signs = default_hash_signs() + hash_signs = default_hash_signs(), + next_protocol_negotiation = + encode_client_protocol_negotiation(SslOpts#ssl_options.next_protocol_selector, Renegotiation) }. +encode_protocol(Protocol, Acc) -> + Len = byte_size(Protocol), + <<Acc/binary, ?BYTE(Len), Protocol/binary>>. + +encode_protocols_advertised_on_server(undefined) -> + undefined; + +encode_protocols_advertised_on_server(Protocols) -> + #next_protocol_negotiation{extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}. + %%-------------------------------------------------------------------- -spec server_hello(session_id(), tls_version(), #connection_states{}, - boolean()) -> #server_hello{}. + boolean(), [binary()] | undefined) -> #server_hello{}. %% %% Description: Creates a server hello message. %%-------------------------------------------------------------------- -server_hello(SessionId, Version, ConnectionStates, Renegotiation) -> +server_hello(SessionId, Version, ConnectionStates, Renegotiation, ProtocolsAdvertisedOnServer) -> Pending = ssl_record:pending_connection_state(ConnectionStates, read), SecParams = Pending#connection_state.security_parameters, #server_hello{server_version = Version, @@ -98,7 +111,8 @@ server_hello(SessionId, Version, ConnectionStates, Renegotiation) -> random = SecParams#security_parameters.server_random, session_id = SessionId, renegotiation_info = - renegotiation_info(server, ConnectionStates, Renegotiation) + renegotiation_info(server, ConnectionStates, Renegotiation), + next_protocol_negotiation = encode_protocols_advertised_on_server(ProtocolsAdvertisedOnServer) }. %%-------------------------------------------------------------------- @@ -113,20 +127,21 @@ hello_request() -> %%-------------------------------------------------------------------- -spec hello(#server_hello{} | #client_hello{}, #ssl_options{}, #connection_states{} | {inet:port_number(), #session{}, db_handle(), - atom(), #connection_states{}, binary()}, - boolean()) -> {tls_version(), session_id(), #connection_states{}}| - {tls_version(), {resumed | new, #session{}}, - #connection_states{}} | #alert{}. + atom(), #connection_states{}, binary()}, + boolean()) -> + {tls_version(), session_id(), #connection_states{}, binary() | undefined}| + {tls_version(), {resumed | new, #session{}}, #connection_states{}, list(binary()) | undefined} | + #alert{}. %% %% Description: Handles a recieved hello message %%-------------------------------------------------------------------- hello(#server_hello{cipher_suite = CipherSuite, server_version = Version, compression_method = Compression, random = Random, session_id = SessionId, renegotiation_info = Info, - hash_signs = _HashSigns}, - #ssl_options{secure_renegotiate = SecureRenegotation}, + hash_signs = _HashSigns} = Hello, + #ssl_options{secure_renegotiate = SecureRenegotation, next_protocol_selector = NextProtocolSelector}, ConnectionStates0, Renegotiation) -> -%%TODO: select hash and signature algorigthm + %%TODO: select hash and signature algorigthm case ssl_record:is_acceptable_version(Version) of true -> case handle_renegotiation_info(client, Info, ConnectionStates0, @@ -135,7 +150,12 @@ hello(#server_hello{cipher_suite = CipherSuite, server_version = Version, ConnectionStates = hello_pending_connection_states(client, Version, CipherSuite, Random, Compression, ConnectionStates1), - {Version, SessionId, ConnectionStates}; + case handle_next_protocol(Hello, NextProtocolSelector, Renegotiation) of + #alert{} = Alert -> + Alert; + Protocol -> + {Version, SessionId, ConnectionStates, Protocol} + end; #alert{} = Alert -> Alert end; @@ -145,9 +165,8 @@ hello(#server_hello{cipher_suite = CipherSuite, server_version = Version, hello(#client_hello{client_version = ClientVersion, random = Random, cipher_suites = CipherSuites, - renegotiation_info = Info, - hash_signs = _HashSigns} = Hello, - #ssl_options{versions = Versions, + renegotiation_info = Info} = Hello, + #ssl_options{versions = Versions, secure_renegotiate = SecureRenegotation} = SslOpts, {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert}, Renegotiation) -> %% TODO: select hash and signature algorithm @@ -173,7 +192,12 @@ hello(#client_hello{client_version = ClientVersion, random = Random, Random, Compression, ConnectionStates1), - {Version, {Type, Session}, ConnectionStates}; + case handle_next_protocol_on_server(Hello, Renegotiation, SslOpts) of + #alert{} = Alert -> + Alert; + ProtocolsToAdvertise -> + {Version, {Type, Session}, ConnectionStates, ProtocolsToAdvertise} + end; #alert{} = Alert -> Alert end @@ -427,6 +451,11 @@ master_secret(Version, PremasterSecret, ConnectionStates, Role) -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) end. +-spec next_protocol(binary()) -> #next_protocol{}. + +next_protocol(SelectedProtocol) -> + #next_protocol{selected_protocol = SelectedProtocol}. + %%-------------------------------------------------------------------- -spec finished(tls_version(), client | server, integer(), binary(), tls_handshake_history()) -> #finished{}. @@ -660,6 +689,57 @@ renegotiation_info(server, ConnectionStates, true) -> #renegotiation_info{renegotiated_connection = undefined} end. +decode_next_protocols({next_protocol_negotiation, Protocols}) -> + decode_next_protocols(Protocols, []). +decode_next_protocols(<<>>, Acc) -> + lists:reverse(Acc); +decode_next_protocols(<<?BYTE(Len), Protocol:Len/binary, Rest/binary>>, Acc) -> + case Len of + 0 -> + {error, invalid_next_protocols}; + _ -> + decode_next_protocols(Rest, [Protocol|Acc]) + end; +decode_next_protocols(_Bytes, _Acc) -> + {error, invalid_next_protocols}. + +next_protocol_extension_allowed(NextProtocolSelector, Renegotiating) -> + NextProtocolSelector =/= undefined andalso not Renegotiating. + +handle_next_protocol_on_server(#client_hello{next_protocol_negotiation = undefined}, _Renegotiation, _SslOpts) -> + undefined; + +handle_next_protocol_on_server(#client_hello{next_protocol_negotiation = {next_protocol_negotiation, <<>>}}, + false, #ssl_options{next_protocols_advertised = Protocols}) -> + Protocols; + +handle_next_protocol_on_server(_Hello, _Renegotiation, _SSLOpts) -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE). % unexpected next protocol extension + +handle_next_protocol(#server_hello{next_protocol_negotiation = undefined}, + _NextProtocolSelector, _Renegotiating) -> + undefined; + +handle_next_protocol(#server_hello{next_protocol_negotiation = Protocols}, + NextProtocolSelector, Renegotiating) -> + + case next_protocol_extension_allowed(NextProtocolSelector, Renegotiating) of + true -> + select_next_protocol(decode_next_protocols(Protocols), NextProtocolSelector); + false -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) % unexpected next protocol extension + end. + +select_next_protocol({error, _Reason}, _NextProtocolSelector) -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); +select_next_protocol(Protocols, NextProtocolSelector) -> + case NextProtocolSelector(Protocols) of + ?NO_PROTOCOL -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); + Protocol when is_binary(Protocol) -> + Protocol + end. + handle_renegotiation_info(_, #renegotiation_info{renegotiated_connection = ?byte(0)}, ConnectionStates, false, _, _) -> {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)}; @@ -816,17 +896,21 @@ master_secret(Version, MasterSecret, #security_parameters{ ServerCipherState, Role)}. -dec_hs(_Version, ?HELLO_REQUEST, <<>>) -> +dec_hs(_, ?NEXT_PROTOCOL, <<?BYTE(SelectedProtocolLength), SelectedProtocol:SelectedProtocolLength/binary, + ?BYTE(PaddingLength), _Padding:PaddingLength/binary>>) -> + #next_protocol{selected_protocol = SelectedProtocol}; + +dec_hs(_, ?HELLO_REQUEST, <<>>) -> #hello_request{}; %% Client hello v2. %% The server must be able to receive such messages, from clients that %% are willing to use ssl v3 or higher, but have ssl v2 compatibility. dec_hs(_Version, ?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), - ?UINT16(CSLength), ?UINT16(0), - ?UINT16(CDLength), - CipherSuites:CSLength/binary, - ChallengeData:CDLength/binary>>) -> + ?UINT16(CSLength), ?UINT16(0), + ?UINT16(CDLength), + CipherSuites:CSLength/binary, + ChallengeData:CDLength/binary>>) -> #client_hello{client_version = {Major, Minor}, random = ssl_ssl2:client_random(ChallengeData, CDLength), session_id = 0, @@ -839,20 +923,22 @@ dec_hs(_Version, ?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, ?UINT16(Cs_length), CipherSuites:Cs_length/binary, ?BYTE(Cm_length), Comp_methods:Cm_length/binary, Extensions/binary>>) -> - HelloExtensions = dec_hello_extensions(Extensions), - RenegotiationInfo = proplists:get_value(renegotiation_info, HelloExtensions, - undefined), - HashSigns = proplists:get_value(hash_signs, HelloExtensions, - undefined), + + DecodedExtensions = dec_hello_extensions(Extensions), + RenegotiationInfo = proplists:get_value(renegotiation_info, DecodedExtensions, undefined), + HashSigns = proplists:get_value(hash_signs, DecodedExtensions, undefined), + NextProtocolNegotiation = proplists:get_value(next_protocol_negotiation, DecodedExtensions, undefined), + #client_hello{ - client_version = {Major,Minor}, - random = Random, - session_id = Session_ID, - cipher_suites = from_2bytes(CipherSuites), - compression_methods = Comp_methods, - renegotiation_info = RenegotiationInfo, - hash_signs = HashSigns - }; + client_version = {Major,Minor}, + random = Random, + session_id = Session_ID, + cipher_suites = from_2bytes(CipherSuites), + compression_methods = Comp_methods, + renegotiation_info = RenegotiationInfo, + hash_signs = HashSigns, + next_protocol_negotiation = NextProtocolNegotiation + }; dec_hs(_Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, ?BYTE(SID_length), Session_ID:SID_length/binary, @@ -868,7 +954,7 @@ dec_hs(_Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, dec_hs(_Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, ?BYTE(SID_length), Session_ID:SID_length/binary, - Cipher_suite:2/binary, ?BYTE(Comp_method), + Cipher_suite:2/binary, ?BYTE(Comp_method), ?UINT16(ExtLen), Extensions:ExtLen/binary>>) -> HelloExtensions = dec_hello_extensions(Extensions, []), @@ -876,6 +962,8 @@ dec_hs(_Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, undefined), HashSigns = proplists:get_value(hash_signs, HelloExtensions, undefined), + NextProtocolNegotiation = proplists:get_value(next_protocol_negotiation, HelloExtensions, undefined), + #server_hello{ server_version = {Major,Minor}, random = Random, @@ -883,7 +971,8 @@ dec_hs(_Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, cipher_suite = Cipher_suite, compression_method = Comp_method, renegotiation_info = RenegotiationInfo, - hash_signs = HashSigns}; + hash_signs = HashSigns, + next_protocol_negotiation = NextProtocolNegotiation}; dec_hs(_Version, ?CERTIFICATE, <<?UINT24(ACLen), ASN1Certs:ACLen/binary>>) -> #certificate{asn1_certificates = certs_to_list(ASN1Certs)}; @@ -959,6 +1048,9 @@ dec_hello_extensions(_) -> dec_hello_extensions(<<>>, Acc) -> Acc; +dec_hello_extensions(<<?UINT16(?NEXTPROTONEG_EXT), ?UINT16(Len), ExtensionData:Len/binary, Rest/binary>>, Acc) -> + Prop = {next_protocol_negotiation, #next_protocol_negotiation{extension_data = ExtensionData}}, + dec_hello_extensions(Rest, [Prop | Acc]); dec_hello_extensions(<<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), Info:Len/binary, Rest/binary>>, Acc) -> RenegotiateInfo = case Len of 1 -> % Initial handshake @@ -982,6 +1074,7 @@ dec_hello_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len), %% Ignore data following the ClientHello (i.e., %% extensions) if not understood. + dec_hello_extensions(<<?UINT16(_), ?UINT16(Len), _Unknown:Len/binary, Rest/binary>>, Acc) -> dec_hello_extensions(Rest, Acc); %% This theoretically should not happen if the protocol is followed, but if it does it is ignored. @@ -1014,6 +1107,11 @@ certs_from_list(ACList) -> <<?UINT24(CertLen), Cert/binary>> end || Cert <- ACList]). +enc_hs(#next_protocol{selected_protocol = SelectedProtocol}, _Version) -> + PaddingLength = 32 - ((byte_size(SelectedProtocol) + 2) rem 32), + + {?NEXT_PROTOCOL, <<?BYTE((byte_size(SelectedProtocol))), SelectedProtocol/binary, + ?BYTE(PaddingLength), 0:(PaddingLength * 8)>>}; enc_hs(#hello_request{}, _Version) -> {?HELLO_REQUEST, <<>>}; enc_hs(#client_hello{client_version = {Major, Minor}, @@ -1022,19 +1120,21 @@ enc_hs(#client_hello{client_version = {Major, Minor}, cipher_suites = CipherSuites, compression_methods = CompMethods, renegotiation_info = RenegotiationInfo, - hash_signs = HashSigns}, _Version) -> + hash_signs = HashSigns, + next_protocol_negotiation = NextProtocolNegotiation}, _Version) -> SIDLength = byte_size(SessionID), BinCompMethods = list_to_binary(CompMethods), CmLength = byte_size(BinCompMethods), BinCipherSuites = list_to_binary(CipherSuites), CsLength = byte_size(BinCipherSuites), - Extensions0 = hello_extensions(RenegotiationInfo), + Extensions0 = hello_extensions(RenegotiationInfo, NextProtocolNegotiation), Extensions1 = if Major == 3, Minor >=3 -> Extensions0 ++ hello_extensions(HashSigns); true -> Extensions0 end, ExtensionsBin = enc_hello_extensions(Extensions1), - {?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, + + {?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, ?BYTE(SIDLength), SessionID/binary, ?UINT16(CsLength), BinCipherSuites/binary, ?BYTE(CmLength), BinCompMethods/binary, ExtensionsBin/binary>>}; @@ -1044,9 +1144,10 @@ enc_hs(#server_hello{server_version = {Major, Minor}, session_id = Session_ID, cipher_suite = Cipher_suite, compression_method = Comp_method, - renegotiation_info = RenegotiationInfo}, _Version) -> + renegotiation_info = RenegotiationInfo, + next_protocol_negotiation = NextProtocolNegotiation}, _Version) -> SID_length = byte_size(Session_ID), - Extensions = hello_extensions(RenegotiationInfo), + Extensions = hello_extensions(RenegotiationInfo, NextProtocolNegotiation), ExtensionsBin = enc_hello_extensions(Extensions), {?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, ?BYTE(SID_length), Session_ID/binary, @@ -1119,8 +1220,9 @@ enc_sign(_HashSign, Sign, _Version) -> SignLen = byte_size(Sign), <<?UINT16(SignLen), Sign/binary>>. -hello_extensions(undefined) -> - []; +hello_extensions(RenegotiationInfo, NextProtocolNegotiation) -> + hello_extensions(RenegotiationInfo) ++ next_protocol_extension(NextProtocolNegotiation). + %% Renegotiation info hello_extensions(#renegotiation_info{renegotiated_connection = undefined}) -> []; @@ -1129,6 +1231,11 @@ hello_extensions(#renegotiation_info{} = Info) -> hello_extensions(#hash_sign_algos{} = Info) -> [Info]. +next_protocol_extension(undefined) -> + []; +next_protocol_extension(#next_protocol_negotiation{} = Info) -> + [Info]. + enc_hello_extensions(Extensions) -> enc_hello_extensions(Extensions, <<>>). enc_hello_extensions([], <<>>) -> @@ -1137,6 +1244,9 @@ enc_hello_extensions([], Acc) -> Size = byte_size(Acc), <<?UINT16(Size), Acc/binary>>; +enc_hello_extensions([#next_protocol_negotiation{extension_data = ExtensionData} | Rest], Acc) -> + Len = byte_size(ExtensionData), + enc_hello_extensions(Rest, <<?UINT16(?NEXTPROTONEG_EXT), ?UINT16(Len), ExtensionData/binary, Acc/binary>>); enc_hello_extensions([#renegotiation_info{renegotiated_connection = ?byte(0) = Info} | Rest], Acc) -> Len = byte_size(Info), enc_hello_extensions(Rest, <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), Info/binary, Acc/binary>>); @@ -1151,8 +1261,15 @@ enc_hello_extensions([#hash_sign_algos{hash_sign_algos = HashSignAlgos} | Rest], {Hash, Sign} <- HashSignAlgos >>, ListLen = byte_size(SignAlgoList), Len = ListLen + 2, - enc_hello_extensions(Rest, <<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len), ?UINT16(ListLen), SignAlgoList/binary, Acc/binary>>). - + enc_hello_extensions(Rest, <<?UINT16(?SIGNATURE_ALGORITHMS_EXT), + ?UINT16(Len), ?UINT16(ListLen), SignAlgoList/binary, Acc/binary>>). + +encode_client_protocol_negotiation(undefined, _) -> + undefined; +encode_client_protocol_negotiation(_, false) -> + #next_protocol_negotiation{extension_data = <<>>}; +encode_client_protocol_negotiation(_, _) -> + undefined. from_3bytes(Bin3) -> from_3bytes(Bin3, []). diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl index cc17dc2975..9af6511d68 100644 --- a/lib/ssl/src/ssl_handshake.hrl +++ b/lib/ssl/src/ssl_handshake.hrl @@ -33,6 +33,8 @@ -type public_key_info() :: {algo_oid(), #'RSAPublicKey'{} | integer() , public_key_params()}. -type tls_handshake_history() :: {[binary()], [binary()]}. +-define(NO_PROTOCOL, <<>>). + %% Signature algorithms -define(ANON, 0). -define(RSA, 1). @@ -97,7 +99,8 @@ cipher_suites, % cipher_suites<2..2^16-1> compression_methods, % compression_methods<1..2^8-1>, renegotiation_info, - hash_signs % supported combinations of hashes/signature algos + hash_signs, % supported combinations of hashes/signature algos + next_protocol_negotiation = undefined % [binary()] }). -record(server_hello, { @@ -107,7 +110,8 @@ cipher_suite, % cipher_suites compression_method, % compression_method renegotiation_info, - hash_signs % supported combinations of hashes/signature algos + hash_signs, % supported combinations of hashes/signature algos + next_protocol_negotiation = undefined % [binary()] }). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -234,6 +238,18 @@ hash_sign_algos }). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Next Protocol Negotiation +%% (http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-02) +%% (http://technotes.googlecode.com/git/nextprotoneg.html) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(NEXTPROTONEG_EXT, 13172). +-define(NEXT_PROTOCOL, 67). +-record(next_protocol_negotiation, {extension_data}). + +-record(next_protocol, {selected_protocol}). + -endif. % -ifdef(ssl_handshake). diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index b8f2ae3b51..a5db2dcee7 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -106,7 +106,9 @@ % after which ssl_connection will % go into hibernation %% This option should only be set to true by inet_tls_dist - erl_dist = false + erl_dist = false, + next_protocols_advertised = undefined, %% [binary()], + next_protocol_selector = undefined %% fun([binary()]) -> binary()) }). -record(socket_options, diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl index af2bfa394d..0cf4f2ce33 100644 --- a/lib/ssl/src/ssl_manager.erl +++ b/lib/ssl/src/ssl_manager.erl @@ -191,7 +191,7 @@ init([Name, Opts]) -> proplists:get_value(session_lifetime, Opts, ?'24H_in_sec'), CertDb = ssl_certificate_db:create(), SessionCache = CacheCb:init(proplists:get_value(session_cb_init_args, Opts, [])), - Timer = erlang:send_after(SessionLifeTime * 1000, + Timer = erlang:send_after(SessionLifeTime * 1000 + 5000, self(), validate_sessions), erlang:send_after(?CLEAR_PEM_CACHE, self(), clear_pem_cache), {ok, #state{certificate_db = CertDb, diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile index 343157b22e..d36dcb588b 100644 --- a/lib/ssl/test/Makefile +++ b/lib/ssl/test/Makefile @@ -44,6 +44,8 @@ MODULES = \ ssl_to_openssl_SUITE \ ssl_session_cache_SUITE \ ssl_dist_SUITE \ + ssl_npn_hello_SUITE \ + ssl_npn_handshake_SUITE \ make_certs\ erl_make_certs diff --git a/lib/ssl/test/make_certs.erl b/lib/ssl/test/make_certs.erl index 693289990c..4603a9f846 100644 --- a/lib/ssl/test/make_certs.erl +++ b/lib/ssl/test/make_certs.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2010. All Rights Reserved. +%% Copyright Ericsson AB 2007-2012. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -121,7 +121,19 @@ create_self_signed_cert(Root, OpenSSLCmd, CAName, Cnf) -> " -keyout ", KeyFile, " -out ", CertFile], Env = [{"ROOTDIR", Root}], - cmd(Cmd, Env). + cmd(Cmd, Env), + fix_key_file(OpenSSLCmd, KeyFile). + +% openssl 1.0 generates key files in pkcs8 format by default and we don't handle this format +fix_key_file(OpenSSLCmd, KeyFile) -> + KeyFileTmp = KeyFile ++ ".tmp", + Cmd = [OpenSSLCmd, " rsa", + " -in ", + KeyFile, + " -out ", + KeyFileTmp], + cmd(Cmd, []), + ok = file:rename(KeyFileTmp, KeyFile). create_ca_dir(Root, CAName, Cnf) -> CARoot = filename:join([Root, CAName]), @@ -139,7 +151,8 @@ create_req(Root, OpenSSLCmd, CnfFile, KeyFile, ReqFile) -> " -keyout ", KeyFile, " -out ", ReqFile], Env = [{"ROOTDIR", Root}], - cmd(Cmd, Env). + cmd(Cmd, Env), + fix_key_file(OpenSSLCmd, KeyFile). sign_req(Root, OpenSSLCmd, CA, CertType, ReqFile, CertFile) -> CACnfFile = filename:join([Root, CA, "ca.cnf"]), diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 93f7209aea..6cf712fa6f 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -39,6 +39,7 @@ -define(EXPIRE, 10). -define(SLEEP, 500). -define(RENEGOTIATION_DISABLE_TIME, 12000). +-define(CLEAN_SESSION_DB, 60000). %% Test server callback functions %%-------------------------------------------------------------------- @@ -108,12 +109,12 @@ init_per_testcase(protocol_versions, Config) -> init_per_testcase(reuse_session_expired, Config0) -> Config = lists:keydelete(watchdog, 1, Config0), - Dog = ssl_test_lib:timetrap(?EXPIRE * 1000 * 5), ssl:stop(), application:load(ssl), application:set_env(ssl, session_lifetime, ?EXPIRE), + application:set_env(ssl, session_delay_cleanup_time, 500), ssl:start(), - [{watchdog, Dog} | Config]; + Config; init_per_testcase(empty_protocol_versions, Config) -> ssl:stop(), @@ -141,6 +142,7 @@ init_per_testcase(_TestCase, Config0) -> %%-------------------------------------------------------------------- end_per_testcase(reuse_session_expired, Config) -> application:unset_env(ssl, session_lifetime), + application:unset_env(ssl, session_delay_cleanup_time), end_per_testcase(default_action, Config); end_per_testcase(_TestCase, Config) -> @@ -2089,13 +2091,14 @@ reuse_session_expired(Config) when is_list(Config) -> %% Make sure session is unregistered due to expiration test_server:sleep((?EXPIRE+1)), [{session_id, Id} |_] = SessionInfo, + make_sure_expired(Hostname, Port, Id), Client2 = ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, + {port, Port}, {host, Hostname}, {mfa, {ssl_test_lib, session_info_result, []}}, - {from, self()}, {options, ClientOpts}]), + {from, self()}, {options, ClientOpts}]), receive {Client2, SessionInfo} -> test_server:fail(session_reused_when_session_expired); @@ -2113,16 +2116,16 @@ make_sure_expired(Host, Port, Id) -> [_, _,_, _, Prop] = StatusInfo, State = ssl_test_lib:state(Prop), Cache = element(2, State), - case ssl_session_cache:lookup(Cache, {{Host, Port}, Id}) of + + case ssl_session_cache:lookup(Cache, {{Host, Port}, Id}) of undefined -> - ok; + ok; #session{is_resumable = false} -> - ok; + ok; _ -> test_server:sleep(?SLEEP), make_sure_expired(Host, Port, Id) - end. - + end. %%-------------------------------------------------------------------- server_does_not_want_to_reuse_session(doc) -> diff --git a/lib/ssl/test/ssl_npn_handshake_SUITE.erl b/lib/ssl/test/ssl_npn_handshake_SUITE.erl new file mode 100644 index 0000000000..8597aa6740 --- /dev/null +++ b/lib/ssl/test/ssl_npn_handshake_SUITE.erl @@ -0,0 +1,310 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2012. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(ssl_npn_handshake_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). +-include_lib("common_test/include/ct.hrl"). + +suite() -> [{ct_hooks,[ts_install_cth]}]. + +all() -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}]. + +groups() -> + [ + {'tlsv1.2', [], next_protocol_tests()}, + {'tlsv1.1', [], next_protocol_tests()}, + {'tlsv1', [], next_protocol_tests()}, + {'sslv3', [], next_protocol_not_supported()} + ]. + +next_protocol_tests() -> + [validate_empty_protocols_are_not_allowed, + validate_empty_advertisement_list_is_allowed, + validate_advertisement_must_be_a_binary_list, + validate_client_protocols_must_be_a_tuple, + normal_npn_handshake_server_preference, + normal_npn_handshake_client_preference, + fallback_npn_handshake, + fallback_npn_handshake_server_preference, + client_negotiate_server_does_not_support, + no_client_negotiate_but_server_supports_npn, + renegotiate_from_client_after_npn_handshake + ]. + +next_protocol_not_supported() -> + [npn_not_supported_client, + npn_not_supported_server + ]. + +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + application:start(public_key), + ssl:start(), + Result = + (catch make_certs:all(?config(data_dir, Config), + ?config(priv_dir, Config))), + test_server:format("Make certs ~p~n", [Result]), + ssl_test_lib:cert_options(Config) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto). + + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName), + Config; + false -> + {skip, "Missing crypto support"} + end; + _ -> + ssl:start(), + Config + end. + + +end_per_group(_GroupName, Config) -> + Config. + + +%% Test cases starts here. +%%-------------------------------------------------------------------- + +validate_empty_protocols_are_not_allowed(Config) when is_list(Config) -> + {error, {eoptions, {next_protocols_advertised, {invalid_protocol, <<>>}}}} + = (catch ssl:listen(9443, + [{next_protocols_advertised, [<<"foo/1">>, <<"">>]}])), + {error, {eoptions, {client_preferred_next_protocols, {invalid_protocol, <<>>}}}} + = (catch ssl:connect({127,0,0,1}, 9443, + [{client_preferred_next_protocols, + {client, [<<"foo/1">>, <<"">>], <<"foox/1">>}}], infinity)), + Option = {client_preferred_next_protocols, {invalid_protocol, <<"">>}}, + {error, {eoptions, Option}} = (catch ssl:connect({127,0,0,1}, 9443, [Option], infinity)). + +%-------------------------------------------------------------------------------- + +validate_empty_advertisement_list_is_allowed(Config) when is_list(Config) -> + Option = {next_protocols_advertised, []}, + {ok, Socket} = ssl:listen(0, [Option]), + ssl:close(Socket). +%-------------------------------------------------------------------------------- + +validate_advertisement_must_be_a_binary_list(Config) when is_list(Config) -> + Option = {next_protocols_advertised, blah}, + {error, {eoptions, Option}} = (catch ssl:listen(9443, [Option])). +%-------------------------------------------------------------------------------- + +validate_client_protocols_must_be_a_tuple(Config) when is_list(Config) -> + Option = {client_preferred_next_protocols, [<<"foo/1">>]}, + {error, {eoptions, Option}} = (catch ssl:connect({127,0,0,1}, 9443, [Option])). + +%-------------------------------------------------------------------------------- + +normal_npn_handshake_server_preference(Config) when is_list(Config) -> + run_npn_handshake(Config, + [{client_preferred_next_protocols, + {server, [<<"http/1.0">>, <<"http/1.1">>], <<"http/1.1">>}}], + [{next_protocols_advertised, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}], + {ok, <<"http/1.1">>}). +%-------------------------------------------------------------------------------- + +normal_npn_handshake_client_preference(Config) when is_list(Config) -> + run_npn_handshake(Config, + [{client_preferred_next_protocols, + {client, [<<"http/1.0">>, <<"http/1.1">>], <<"http/1.1">>}}], + [{next_protocols_advertised, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}], + {ok, <<"http/1.0">>}). + +%-------------------------------------------------------------------------------- + +fallback_npn_handshake(Config) when is_list(Config) -> + run_npn_handshake(Config, + [{client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"http/1.1">>}}], + [{next_protocols_advertised, [<<"spdy/1">>, <<"http/1.1">>, <<"http/1.0">>]}], + {ok, <<"http/1.1">>}). +%-------------------------------------------------------------------------------- + +fallback_npn_handshake_server_preference(Config) when is_list(Config) -> + run_npn_handshake(Config, + [{client_preferred_next_protocols, {server, [<<"spdy/2">>], <<"http/1.1">>}}], + [{next_protocols_advertised, [<<"spdy/1">>, <<"http/1.1">>, <<"http/1.0">>]}], + {ok, <<"http/1.1">>}). + +%-------------------------------------------------------------------------------- + +no_client_negotiate_but_server_supports_npn(Config) when is_list(Config) -> + run_npn_handshake(Config, + [], + [{next_protocols_advertised, [<<"spdy/1">>, <<"http/1.1">>, <<"http/1.0">>]}], + {error, next_protocol_not_negotiated}). +%-------------------------------------------------------------------------------- + + +client_negotiate_server_does_not_support(Config) when is_list(Config) -> + run_npn_handshake(Config, + [{client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"http/1.1">>}}], + [], + {error, next_protocol_not_negotiated}). + +%-------------------------------------------------------------------------------- +renegotiate_from_client_after_npn_handshake(Config) when is_list(Config) -> + Data = "hello world", + + ClientOpts0 = ?config(client_opts, Config), + ClientOpts = [{client_preferred_next_protocols, + {client, [<<"http/1.0">>], <<"http/1.1">>}}] ++ ClientOpts0, + ServerOpts0 = ?config(server_opts, Config), + ServerOpts = [{next_protocols_advertised, + [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}] ++ ServerOpts0, + ExpectedProtocol = {ok, <<"http/1.0">>}, + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, ssl_receive_and_assert_npn, [ExpectedProtocol, Data]}}, + {options, ServerOpts}]), + + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, assert_npn_and_renegotiate_and_send_data, [ExpectedProtocol, Data]}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, ok, Client, ok). + +%-------------------------------------------------------------------------------- +npn_not_supported_client(Config) when is_list(Config) -> + ClientOpts0 = ?config(client_opts, Config), + PrefProtocols = {client_preferred_next_protocols, + {client, [<<"http/1.0">>], <<"http/1.1">>}}, + ClientOpts = [PrefProtocols] ++ ClientOpts0, + {ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Client = ssl_test_lib:start_client_error([{node, ClientNode}, + {port, 8888}, {host, Hostname}, + {from, self()}, {options, ClientOpts}]), + + ssl_test_lib:check_result(Client, {error, + {eoptions, + {not_supported_in_sslv3, PrefProtocols}}}). + +%-------------------------------------------------------------------------------- +npn_not_supported_server(Config) when is_list(Config)-> + ServerOpts0 = ?config(server_opts, Config), + AdvProtocols = {next_protocols_advertised, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}, + ServerOpts = [AdvProtocols] ++ ServerOpts0, + + {error, {eoptions, {not_supported_in_sslv3, AdvProtocols}}} = ssl:listen(0, ServerOpts). + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- + +run_npn_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedProtocol) -> + Data = "hello world", + + ClientOpts0 = ?config(client_opts, Config), + ClientOpts = ClientExtraOpts ++ ClientOpts0, + ServerOpts0 = ?config(server_opts, Config), + ServerOpts = ServerExtraOpts ++ ServerOpts0, + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, ssl_receive_and_assert_npn, [ExpectedProtocol, Data]}}, + {options, ServerOpts}]), + + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, ssl_send_and_assert_npn, [ExpectedProtocol, Data]}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, ok, Client, ok). + + +assert_npn(Socket, Protocol) -> + test_server:format("Negotiated Protocol ~p, Expecting: ~p ~n", + [ssl:negotiated_next_protocol(Socket), Protocol]), + Protocol = ssl:negotiated_next_protocol(Socket). + +assert_npn_and_renegotiate_and_send_data(Socket, Protocol, Data) -> + assert_npn(Socket, Protocol), + test_server:format("Renegotiating ~n", []), + ok = ssl:renegotiate(Socket), + ssl:send(Socket, Data), + assert_npn(Socket, Protocol), + ok. + +ssl_send_and_assert_npn(Socket, Protocol, Data) -> + assert_npn(Socket, Protocol), + ssl_send(Socket, Data). + +ssl_receive_and_assert_npn(Socket, Protocol, Data) -> + assert_npn(Socket, Protocol), + ssl_receive(Socket, Data). + +ssl_send(Socket, Data) -> + test_server:format("Connection info: ~p~n", + [ssl:connection_info(Socket)]), + ssl:send(Socket, Data). + +ssl_receive(Socket, Data) -> + ssl_receive(Socket, Data, []). + +ssl_receive(Socket, Data, Buffer) -> + test_server:format("Connection info: ~p~n", + [ssl:connection_info(Socket)]), + receive + {ssl, Socket, MoreData} -> + test_server:format("Received ~p~n",[MoreData]), + NewBuffer = Buffer ++ MoreData, + case NewBuffer of + Data -> + ssl:send(Socket, "Got it"), + ok; + _ -> + ssl_receive(Socket, Data, NewBuffer) + end; + Other -> + test_server:fail({unexpected_message, Other}) + after 4000 -> + test_server:fail({did_not_get, Data}) + end. + + +connection_info_result(Socket) -> + ssl:connection_info(Socket). diff --git a/lib/ssl/test/ssl_npn_hello_SUITE.erl b/lib/ssl/test/ssl_npn_hello_SUITE.erl new file mode 100644 index 0000000000..5102c74e87 --- /dev/null +++ b/lib/ssl/test/ssl_npn_hello_SUITE.erl @@ -0,0 +1,117 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2012. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% + +-module(ssl_npn_hello_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). +-include("ssl_handshake.hrl"). +-include("ssl_record.hrl"). +-include_lib("common_test/include/ct.hrl"). + +suite() -> [{ct_hooks,[ts_install_cth]}]. + +all() -> + [encode_and_decode_npn_client_hello_test, + encode_and_decode_npn_server_hello_test, + encode_and_decode_client_hello_test, + encode_and_decode_server_hello_test, + create_server_hello_with_advertised_protocols_test, + create_server_hello_with_no_advertised_protocols_test]. + + +create_client_handshake(Npn) -> + ssl_handshake:encode_handshake(#client_hello{ + client_version = {1, 2}, + random = <<1:256>>, + session_id = <<>>, + cipher_suites = "", + compression_methods = "", + next_protocol_negotiation = Npn, + renegotiation_info = #renegotiation_info{} + }, vsn). + + +encode_and_decode_client_hello_test(_Config) -> + HandShakeData = create_client_handshake(undefined), + Version = ssl_record:protocol_version(ssl_record:highest_protocol_version([])), + {[{DecodedHandshakeMessage, _Raw}], _} = ssl_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>), + NextProtocolNegotiation = DecodedHandshakeMessage#client_hello.next_protocol_negotiation, + NextProtocolNegotiation = undefined. + +encode_and_decode_npn_client_hello_test(_Config) -> + HandShakeData = create_client_handshake(#next_protocol_negotiation{extension_data = <<>>}), + Version = ssl_record:protocol_version(ssl_record:highest_protocol_version([])), + {[{DecodedHandshakeMessage, _Raw}], _} = ssl_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>), + NextProtocolNegotiation = DecodedHandshakeMessage#client_hello.next_protocol_negotiation, + NextProtocolNegotiation = #next_protocol_negotiation{extension_data = <<>>}. + +create_server_handshake(Npn) -> + ssl_handshake:encode_handshake(#server_hello{ + server_version = {1, 2}, + random = <<1:256>>, + session_id = <<>>, + cipher_suite = <<1,2>>, + compression_method = 1, + next_protocol_negotiation = Npn, + renegotiation_info = #renegotiation_info{} + }, vsn). + +encode_and_decode_server_hello_test(_Config) -> + HandShakeData = create_server_handshake(undefined), + Version = ssl_record:protocol_version(ssl_record:highest_protocol_version([])), + {[{DecodedHandshakeMessage, _Raw}], _} = + ssl_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>), + NextProtocolNegotiation = DecodedHandshakeMessage#server_hello.next_protocol_negotiation, + NextProtocolNegotiation = undefined. + +encode_and_decode_npn_server_hello_test(_Config) -> + HandShakeData = create_server_handshake(#next_protocol_negotiation{extension_data = <<6, "spdy/2">>}), + Version = ssl_record:protocol_version(ssl_record:highest_protocol_version([])), + {[{DecodedHandshakeMessage, _Raw}], _} = ssl_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>), + NextProtocolNegotiation = DecodedHandshakeMessage#server_hello.next_protocol_negotiation, + ct:print("~p ~n", [NextProtocolNegotiation]), + NextProtocolNegotiation = #next_protocol_negotiation{extension_data = <<6, "spdy/2">>}. + +create_connection_states() -> + #connection_states{ + pending_read = #connection_state{ + security_parameters = #security_parameters{ + server_random = <<1:256>>, + compression_algorithm = 1, + cipher_suite = <<1, 2>> + } + }, + + current_read = #connection_state { + secure_renegotiation = false + } + }. + +create_server_hello_with_no_advertised_protocols_test(_Config) -> + Hello = ssl_handshake:server_hello(<<>>, {3, 0}, create_connection_states(), false, undefined), + undefined = Hello#server_hello.next_protocol_negotiation. + +create_server_hello_with_advertised_protocols_test(_Config) -> + Hello = ssl_handshake:server_hello(<<>>, {3, 0}, create_connection_states(), + false, [<<"spdy/1">>, <<"http/1.0">>, <<"http/1.1">>]), + #next_protocol_negotiation{extension_data = <<6, "spdy/1", 8, "http/1.0", 8, "http/1.1">>} = + Hello#server_hello.next_protocol_negotiation. diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl index d446014f7b..21797bee08 100644 --- a/lib/ssl/test/ssl_to_openssl_SUITE.erl +++ b/lib/ssl/test/ssl_to_openssl_SUITE.erl @@ -29,7 +29,7 @@ -define(TIMEOUT, 120000). -define(LONG_TIMEOUT, 600000). -define(SLEEP, 1000). --define(OPENSSL_RENEGOTIATE, "r\n"). +-define(OPENSSL_RENEGOTIATE, "R\n"). -define(OPENSSL_QUIT, "Q\n"). -define(OPENSSL_GARBAGE, "P\n"). -define(EXPIRE, 10). @@ -114,6 +114,17 @@ special_init(TestCase, Config) special_init(ssl2_erlang_server_openssl_client, Config) -> check_sane_openssl_sslv2(Config); +special_init(TestCase, Config) + when TestCase == erlang_client_openssl_server_npn; + TestCase == erlang_server_openssl_client_npn; + TestCase == erlang_server_openssl_client_npn_renegotiate; + TestCase == erlang_client_openssl_server_npn_renegotiate; + TestCase == erlang_server_openssl_client_npn_only_server; + TestCase == erlang_server_openssl_client_npn_only_client; + TestCase == erlang_client_openssl_server_npn_only_client; + TestCase == erlang_client_openssl_server_npn_only_server -> + check_openssl_npn_support(Config); + special_init(_, Config) -> Config. @@ -161,9 +172,9 @@ all() -> groups() -> [{basic, [], basic_tests()}, - {'tlsv1.2', [], all_versions_tests()}, - {'tlsv1.1', [], all_versions_tests()}, - {'tlsv1', [], all_versions_tests()}, + {'tlsv1.2', [], all_versions_tests() ++ npn_tests()}, + {'tlsv1.1', [], all_versions_tests() ++ npn_tests()}, + {'tlsv1', [], all_versions_tests()++ npn_tests()}, {'sslv3', [], all_versions_tests()}]. basic_tests() -> @@ -179,16 +190,26 @@ all_versions_tests() -> erlang_server_openssl_client_dsa_cert, erlang_server_openssl_client_reuse_session, erlang_client_openssl_server_renegotiate, - erlang_client_openssl_server_no_wrap_sequence_number, - erlang_server_openssl_client_no_wrap_sequence_number, + erlang_client_openssl_server_nowrap_seqnum, + erlang_server_openssl_client_nowrap_seqnum, erlang_client_openssl_server_no_server_ca_cert, erlang_client_openssl_server_client_cert, erlang_server_openssl_client_client_cert, ciphers_rsa_signed_certs, ciphers_dsa_signed_certs, erlang_client_bad_openssl_server, - ssl2_erlang_server_openssl_client - ]. + expired_session, + ssl2_erlang_server_openssl_client]. + +npn_tests() -> + [erlang_client_openssl_server_npn, + erlang_server_openssl_client_npn, + erlang_server_openssl_client_npn_renegotiate, + erlang_client_openssl_server_npn_renegotiate, + erlang_server_openssl_client_npn_only_client, + erlang_server_openssl_client_npn_only_server, + erlang_client_openssl_server_npn_only_client, + erlang_client_openssl_server_npn_only_server]. init_per_group(GroupName, Config) -> case ssl_test_lib:is_tls_version(GroupName) of @@ -544,14 +565,14 @@ erlang_client_openssl_server_renegotiate(Config) when is_list(Config) -> %%-------------------------------------------------------------------- -erlang_client_openssl_server_no_wrap_sequence_number(doc) -> +erlang_client_openssl_server_nowrap_seqnum(doc) -> ["Test that erlang client will renegotiate session when", "max sequence number celing is about to be reached. Although" "in the testcase we use the test option renegotiate_at" " to lower treashold substantially."]; -erlang_client_openssl_server_no_wrap_sequence_number(suite) -> +erlang_client_openssl_server_nowrap_seqnum(suite) -> []; -erlang_client_openssl_server_no_wrap_sequence_number(Config) when is_list(Config) -> +erlang_client_openssl_server_nowrap_seqnum(Config) when is_list(Config) -> process_flag(trap_exit, true), ServerOpts = ?config(server_opts, Config), ClientOpts = ?config(client_opts, Config), @@ -590,15 +611,15 @@ erlang_client_openssl_server_no_wrap_sequence_number(Config) when is_list(Config process_flag(trap_exit, false), ok. %%-------------------------------------------------------------------- -erlang_server_openssl_client_no_wrap_sequence_number(doc) -> +erlang_server_openssl_client_nowrap_seqnum(doc) -> ["Test that erlang client will renegotiate session when", "max sequence number celing is about to be reached. Although" "in the testcase we use the test option renegotiate_at" " to lower treashold substantially."]; -erlang_server_openssl_client_no_wrap_sequence_number(suite) -> +erlang_server_openssl_client_nowrap_seqnum(suite) -> []; -erlang_server_openssl_client_no_wrap_sequence_number(Config) when is_list(Config) -> +erlang_server_openssl_client_nowrap_seqnum(Config) when is_list(Config) -> process_flag(trap_exit, true), ServerOpts = ?config(server_opts, Config), @@ -1069,6 +1090,248 @@ ssl2_erlang_server_openssl_client(Config) when is_list(Config) -> ok. %%-------------------------------------------------------------------- +erlang_client_openssl_server_npn(doc) -> + ["Test erlang client with openssl server doing npn negotiation"]; +erlang_client_openssl_server_npn(suite) -> + []; +erlang_client_openssl_server_npn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, fun(Client, OpensslPort) -> + port_command(OpensslPort, Data), + + ssl_test_lib:check_result(Client, ok) + end), + + ok. + + +%%-------------------------------------------------------------------- +erlang_client_openssl_server_npn_renegotiate(doc) -> + ["Test erlang client with openssl server doing npn negotiation and renegotiate"]; +erlang_client_openssl_server_npn_renegotiate(suite) -> + []; +erlang_client_openssl_server_npn_renegotiate(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, fun(Client, OpensslPort) -> + port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), + test_server:sleep(?SLEEP), + port_command(OpensslPort, Data), + ssl_test_lib:check_result(Client, ok) + end), + ok. + + +%%-------------------------------------------------------------------------- + + +erlang_server_openssl_client_npn(doc) -> + ["Test erlang server with openssl client and npn negotiation"]; +erlang_server_openssl_client_npn(suite) -> + []; +erlang_server_openssl_client_npn(Config) when is_list(Config) -> + + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, fun(Server, OpensslPort) -> + port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, ok) + end), + ok. + +%%-------------------------------------------------------------------------- + +erlang_server_openssl_client_npn_renegotiate(doc) -> + ["Test erlang server with openssl client and npn negotiation with renegotiation"]; +erlang_server_openssl_client_npn_renegotiate(suite) -> + []; +erlang_server_openssl_client_npn_renegotiate(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, fun(Server, OpensslPort) -> + port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), + test_server:sleep(?SLEEP), + port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, ok) + end), + ok. +%%-------------------------------------------------------------------------- + +erlang_client_openssl_server_npn_only_server(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_with_opts(Config, [], "-nextprotoneg spdy/2", Data, fun(Server, OpensslPort) -> + port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, ok) + end), + ok. + +%%-------------------------------------------------------------------------- + +erlang_client_openssl_server_npn_only_client(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_with_opts(Config, [{client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"http/1.1">>}}], "", Data, fun(Server, OpensslPort) -> + port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, ok) + end), + ok. + +%%-------------------------------------------------------------------------- +erlang_server_openssl_client_npn_only_server(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_with_opts(Config, [{next_protocols_advertised, [<<"spdy/2">>]}], "", Data, fun(Server, OpensslPort) -> + port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, ok) + end), + ok. + +erlang_server_openssl_client_npn_only_client(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_with_opts(Config, [], "-nextprotoneg spdy/2", Data, fun(Server, OpensslPort) -> + port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, ok) + end), + ok. + +%%-------------------------------------------------------------------------- + +start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, OpensslServerOpts, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts = ?config(server_opts, Config), + ClientOpts0 = ?config(client_opts, Config), + ClientOpts = ErlangClientOpts ++ ClientOpts0, + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = ssl_record:protocol_version(ssl_record:highest_protocol_version([])), + + Cmd = "openssl s_server " ++ OpensslServerOpts ++ " -accept " ++ + integer_to_list(Port) ++ version_flag(Version) ++ + " -cert " ++ CertFile ++ " -key " ++ KeyFile, + + test_server:format("openssl cmd: ~p~n", [Cmd]), + + OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + + wait_for_openssl_server(), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + erlang_ssl_receive, [Data]}}, + {options, ClientOpts}]), + + Callback(Client, OpensslPort), + + %% Clean close down! Server needs to be closed first !! + close_port(OpensslPort), + + ssl_test_lib:close(Client), + process_flag(trap_exit, false). + +start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts = ?config(server_opts, Config), + ClientOpts0 = ?config(client_opts, Config), + ClientOpts = [{client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"http/1.1">>}} | ClientOpts0], + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = ssl_record:protocol_version(ssl_record:highest_protocol_version([])), + + Cmd = "openssl s_server -msg -nextprotoneg http/1.1,spdy/2 -accept " ++ integer_to_list(Port) ++ version_flag(Version) ++ + " -cert " ++ CertFile ++ " -key " ++ KeyFile, + + test_server:format("openssl cmd: ~p~n", [Cmd]), + + OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + + wait_for_openssl_server(), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + erlang_ssl_receive_and_assert_npn, [<<"spdy/2">>, Data]}}, + {options, ClientOpts}]), + + Callback(Client, OpensslPort), + + %% Clean close down! Server needs to be closed first !! + close_port(OpensslPort), + + ssl_test_lib:close(Client), + process_flag(trap_exit, false). + +start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts0 = ?config(server_opts, Config), + ServerOpts = [{next_protocols_advertised, [<<"spdy/2">>]}, ServerOpts0], + + {_, ServerNode, _} = ssl_test_lib:run_where(Config), + + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive_and_assert_npn, [<<"spdy/2">>, Data]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Version = ssl_record:protocol_version(ssl_record:highest_protocol_version([])), + Cmd = "openssl s_client -nextprotoneg http/1.0,spdy/2 -msg -port " ++ integer_to_list(Port) ++ version_flag(Version) ++ + " -host localhost", + + test_server:format("openssl cmd: ~p~n", [Cmd]), + + OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + + Callback(Server, OpenSslPort), + + ssl_test_lib:close(Server), + + close_port(OpenSslPort), + process_flag(trap_exit, false). + +start_erlang_server_and_openssl_client_with_opts(Config, ErlangServerOpts, OpenSSLClientOpts, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts0 = ?config(server_opts, Config), + ServerOpts = ErlangServerOpts ++ ServerOpts0, + + {_, ServerNode, _} = ssl_test_lib:run_where(Config), + + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Cmd = "openssl s_client " ++ OpenSSLClientOpts ++ " -msg -port " ++ integer_to_list(Port) ++ + " -host localhost", + + test_server:format("openssl cmd: ~p~n", [Cmd]), + + OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + + Callback(Server, OpenSslPort), + + ssl_test_lib:close(Server), + + close_port(OpenSslPort), + process_flag(trap_exit, false). + + +erlang_ssl_receive_and_assert_npn(Socket, Protocol, Data) -> + {ok, Protocol} = ssl:negotiated_next_protocol(Socket), + erlang_ssl_receive(Socket, Data), + {ok, Protocol} = ssl:negotiated_next_protocol(Socket), + ok. erlang_ssl_receive(Socket, Data) -> test_server:format("Connection info: ~p~n", @@ -1168,6 +1431,15 @@ version_flag('tlsv1.2') -> version_flag(sslv3) -> " -ssl3 ". +check_openssl_npn_support(Config) -> + HelpText = os:cmd("openssl s_client --help"), + case string:str(HelpText, "nextprotoneg") of + 0 -> + {skip, "Openssl not compiled with nextprotoneg support"}; + _ -> + Config + end. + check_sane_openssl_renegotaite(Config) -> case os:cmd("openssl version") of "OpenSSL 0.9.8" ++ _ -> diff --git a/lib/test_server/src/test_server_ctrl.erl b/lib/test_server/src/test_server_ctrl.erl index f7266e5632..9731f1ddba 100644 --- a/lib/test_server/src/test_server_ctrl.erl +++ b/lib/test_server/src/test_server_ctrl.erl @@ -4686,21 +4686,16 @@ output_to_fd(stdout, Msg, Sender) -> io:format("Testing ~s: ~s\n", [Name, lists:flatten(Msg)]); output_to_fd(undefined, _Msg, _Sender) -> ok; -output_to_fd(Fd, [$=|Msg], internal) -> - io:put_chars(Fd, [$=]), - io:put_chars(Fd, Msg), - io:put_chars(Fd, "\n"); +output_to_fd(Fd, Msg=[$=|_], internal) -> + io:put_chars(Fd, [Msg,"\n"]); output_to_fd(Fd, Msg, internal) -> - io:put_chars(Fd, [$=,$=,$=,$ ]), - io:put_chars(Fd, Msg), - io:put_chars(Fd, "\n"); + io:put_chars(Fd, [$=,$=,$=,$ , Msg, "\n"]); output_to_fd(Fd, Msg, _Sender) -> - io:put_chars(Fd, Msg), case get(test_server_log_nl) of - false -> ok; - _ -> io:put_chars(Fd, "\n") + false -> io:put_chars(Fd, Msg); + _ -> io:put_chars(Fd, [Msg,"\n"]) end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/tools/src/tools.app.src b/lib/tools/src/tools.app.src index cd9b622f15..94998fb763 100644 --- a/lib/tools/src/tools.app.src +++ b/lib/tools/src/tools.app.src @@ -24,6 +24,7 @@ eprof, fprof, instrument, + lcnt, make, xref, xref_base, |