%% %% Copyright Ericsson AB 2019-2019. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% %% -module(ssl_socket_SUITE). %% Note: This directive should only be used in test suites. -compile(export_all). -include_lib("common_test/include/ct.hrl"). -include_lib("public_key/include/public_key.hrl"). -define(SLEEP, 500). %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- all() -> [ {group, tls}, {group, dtls} ]. groups() -> [ {tls,[], socket_tests() ++ raw_inet_opt()}, {dtls,[], socket_tests()} ]. socket_tests() -> [ getstat, socket_options, invalid_inet_get_option, invalid_inet_get_option_not_list, invalid_inet_get_option_improper_list, invalid_inet_set_option, invalid_inet_set_option_not_list, invalid_inet_set_option_improper_list ]. raw_inet_opt() -> [ raw_inet_option ]. init_per_suite(Config0) -> catch crypto:stop(), try crypto:start() of ok -> ssl_test_lib:clean_start(), ssl_test_lib:make_rsa_cert(Config0) catch _:_ -> {skip, "Crypto did not start"} end. end_per_suite(_Config) -> ssl:stop(), application:unload(ssl), application:stop(crypto). init_per_group(dtls, Config) -> [{protocol_opts, [{protocol, dtls}]} | proplists:delete(protocol_opts, Config)]; init_per_group(tls, Config) -> [{protocol_opts, [{protocol, tls}]} | proplists:delete(protocol_opts, Config)]; init_per_group(_GroupName, Config) -> [{client_type, erlang}, {server_type, erlang} | Config]. end_per_group(_GroupName, Config) -> Config. init_per_testcase(raw_inet_option, Config) -> ct:timetrap({seconds, 5}), case os:type() of {unix,linux} -> Config; _ -> {skip, "Raw options are platform-specific"} end; init_per_testcase(_TestCase, Config) -> ct:timetrap({seconds, 5}), Config. end_per_testcase(_TestCase, Config) -> Config. %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- getstat() -> [{doc,"Test API function getstat/2"}]. getstat(Config) when is_list(Config) -> ClientOpts = ?config(client_rsa_opts, Config), ServerOpts = ?config(server_rsa_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server1 = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {ssl_test_lib, send_recv_result, []}}, {options, [{active, false} | ServerOpts]}]), Port1 = ssl_test_lib:inet_port(Server1), Server2 = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {ssl_test_lib, send_recv_result, []}}, {options, [{active, false} | ServerOpts]}]), Port2 = ssl_test_lib:inet_port(Server2), {ok, ActiveC} = rpc:call(ClientNode, ssl, connect, [Hostname,Port1,[{active, once}|ClientOpts]]), {ok, PassiveC} = rpc:call(ClientNode, ssl, connect, [Hostname,Port2,[{active, false}|ClientOpts]]), ct:log("Testcase ~p, Client ~p Servers ~p, ~p ~n", [self(), self(), Server1, Server2]), %% We only check that the values are non-zero initially %% (due to the handshake), and that sending more changes the values. %% Passive socket. {ok, InitialStats} = ssl:getstat(PassiveC), ct:pal("InitialStats ~p~n", [InitialStats]), [true] = lists:usort([0 =/= proplists:get_value(Name, InitialStats) || Name <- [recv_cnt, recv_oct, recv_avg, recv_max, send_cnt, send_oct, send_avg, send_max]]), ok = ssl:send(PassiveC, "Hello world"), wait_for_send(PassiveC), {ok, SStats} = ssl:getstat(PassiveC, [send_cnt, send_oct]), ct:pal("SStats ~p~n", [SStats]), [true] = lists:usort([proplists:get_value(Name, SStats) =/= proplists:get_value(Name, InitialStats) || Name <- [send_cnt, send_oct]]), %% Active socket. {ok, InitialAStats} = ssl:getstat(ActiveC), ct:pal("InitialAStats ~p~n", [InitialAStats]), [true] = lists:usort([0 =/= proplists:get_value(Name, InitialAStats) || Name <- [recv_cnt, recv_oct, recv_avg, recv_max, send_cnt, send_oct, send_avg, send_max]]), _ = receive {ssl, ActiveC, _} -> ok after ?SLEEP -> exit(timeout) end, ok = ssl:send(ActiveC, "Hello world"), wait_for_send(ActiveC), {ok, ASStats} = ssl:getstat(ActiveC, [send_cnt, send_oct]), ct:pal("ASStats ~p~n", [ASStats]), [true] = lists:usort([proplists:get_value(Name, ASStats) =/= proplists:get_value(Name, InitialAStats) || Name <- [send_cnt, send_oct]]), ok. %%-------------------------------------------------------------------- socket_options() -> [{doc,"Test API function getopts/2 and setopts/2"}]. socket_options(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Values = [{mode, list}, {active, true}], %% Shall be the reverse order of Values! Options = [active, mode], NewValues = [{mode, binary}, {active, once}], %% Shall be the reverse order of NewValues! NewOptions = [active, mode], Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {?MODULE, socket_options_result, [Options, Values, NewOptions, NewValues]}}, {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, socket_options_result, [Options, Values, NewOptions, NewValues]}}, {options, ClientOpts}]), ssl_test_lib:check_result(Server, ok, Client, ok), ssl_test_lib:close(Server), {ok, Listen} = ssl:listen(0, ServerOpts), {ok,[{mode,list}]} = ssl:getopts(Listen, [mode]), ok = ssl:setopts(Listen, [{mode, binary}]), {ok,[{mode, binary}]} = ssl:getopts(Listen, [mode]), {ok,[{recbuf, _}]} = ssl:getopts(Listen, [recbuf]), ssl:close(Listen). %%-------------------------------------------------------------------- raw_inet_option() -> [{doc,"Ensure that a single 'raw' option is passed to ssl:listen correctly."}]. raw_inet_option(Config) when is_list(Config) -> % 'raw' option values are platform-specific; these are the Linux values: IpProtoTcp = 6, % Use TCP_KEEPIDLE, because (e.g.) TCP_MAXSEG can't be read back reliably. TcpKeepIdle = 4, KeepAliveTimeSecs = 55, LOptions = [{raw, IpProtoTcp, TcpKeepIdle, <>}], {ok, LSocket} = ssl:listen(0, LOptions), % Per http://www.erlang.org/doc/man/inet.html#getopts-2, we have to specify % exactly which raw option we want, and the size of the buffer. {ok, [{raw, IpProtoTcp, TcpKeepIdle, <>}]} = ssl:getopts(LSocket, [{raw, IpProtoTcp, TcpKeepIdle, 4}]). %%-------------------------------------------------------------------- invalid_inet_get_option() -> [{doc,"Test handling of invalid inet options in getopts"}]. invalid_inet_get_option(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {?MODULE, get_invalid_inet_option, []}}, {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, {mfa, {ssl_test_lib, no_result, []}}, {options, ClientOpts}]), ct:log("Testcase ~p, Client ~p Server ~p ~n", [self(), Client, Server]), ssl_test_lib:check_result(Server, ok), ssl_test_lib:close(Server), ssl_test_lib:close(Client). %%-------------------------------------------------------------------- invalid_inet_get_option_not_list() -> [{doc,"Test handling of invalid type in getopts"}]. invalid_inet_get_option_not_list(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {?MODULE, get_invalid_inet_option_not_list, []}}, {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, {mfa, {ssl_test_lib, no_result, []}}, {options, ClientOpts}]), ct:log("Testcase ~p, Client ~p Server ~p ~n", [self(), Client, Server]), ssl_test_lib:check_result(Server, ok), ssl_test_lib:close(Server), ssl_test_lib:close(Client). %%-------------------------------------------------------------------- invalid_inet_get_option_improper_list() -> [{doc,"Test handling of invalid type in getopts"}]. invalid_inet_get_option_improper_list(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {?MODULE, get_invalid_inet_option_improper_list, []}}, {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, {mfa, {ssl_test_lib, no_result, []}}, {options, ClientOpts}]), ct:log("Testcase ~p, Client ~p Server ~p ~n", [self(), Client, Server]), ssl_test_lib:check_result(Server, ok), ssl_test_lib:close(Server), ssl_test_lib:close(Client). %%-------------------------------------------------------------------- invalid_inet_set_option() -> [{doc,"Test handling of invalid inet options in setopts"}]. invalid_inet_set_option(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {?MODULE, set_invalid_inet_option, []}}, {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, {mfa, {ssl_test_lib, no_result, []}}, {options, ClientOpts}]), ct:log("Testcase ~p, Client ~p Server ~p ~n", [self(), Client, Server]), ssl_test_lib:check_result(Server, ok), ssl_test_lib:close(Server), ssl_test_lib:close(Client). %%-------------------------------------------------------------------- invalid_inet_set_option_not_list() -> [{doc,"Test handling of invalid type in setopts"}]. invalid_inet_set_option_not_list(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {?MODULE, set_invalid_inet_option_not_list, []}}, {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, {mfa, {ssl_test_lib, no_result, []}}, {options, ClientOpts}]), ct:log("Testcase ~p, Client ~p Server ~p ~n", [self(), Client, Server]), ssl_test_lib:check_result(Server, ok), ssl_test_lib:close(Server), ssl_test_lib:close(Client). %%-------------------------------------------------------------------- invalid_inet_set_option_improper_list() -> [{doc,"Test handling of invalid tye in setopts"}]. invalid_inet_set_option_improper_list(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {?MODULE, set_invalid_inet_option_improper_list, []}}, {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, {mfa, {ssl_test_lib, no_result, []}}, {options, ClientOpts}]), ct:log("Testcase ~p, Client ~p Server ~p ~n", [self(), Client, Server]), ssl_test_lib:check_result(Server, ok), ssl_test_lib:close(Server), ssl_test_lib:close(Client). %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues) -> %% Test get/set emulated opts {ok, DefaultValues} = ssl:getopts(Socket, Options), ssl:setopts(Socket, NewValues), {ok, NewValues} = ssl:getopts(Socket, NewOptions), %% Test get/set inet opts {ok,[{reuseaddr, _}]} = ssl:getopts(Socket, [reuseaddr]), {ok, All} = ssl:getopts(Socket, []), ct:log("All opts ~p~n", [All]), ok. get_invalid_inet_option(Socket) -> {error, {options, {socket_options, foo, _}}} = ssl:getopts(Socket, [foo]), ok. get_invalid_inet_option_not_list(Socket) -> {error, {options, {socket_options, some_invalid_atom_here}}} = ssl:getopts(Socket, some_invalid_atom_here), ok. get_invalid_inet_option_improper_list(Socket) -> {error, {options, {socket_options, foo,_}}} = ssl:getopts(Socket, [packet | foo]), ok. set_invalid_inet_option(Socket) -> {error, {options, {socket_options, {packet, foo}}}} = ssl:setopts(Socket, [{packet, foo}]), {error, {options, {socket_options, {header, foo}}}} = ssl:setopts(Socket, [{header, foo}]), {error, {options, {socket_options, {active, foo}}}} = ssl:setopts(Socket, [{active, foo}]), {error, {options, {socket_options, {mode, foo}}}} = ssl:setopts(Socket, [{mode, foo}]), ok. set_invalid_inet_option_not_list(Socket) -> {error, {options, {not_a_proplist, some_invalid_atom_here}}} = ssl:setopts(Socket, some_invalid_atom_here), ok. set_invalid_inet_option_improper_list(Socket) -> {error, {options, {not_a_proplist, [{packet, 0} | {foo, 2}]}}} = ssl:setopts(Socket, [{packet, 0} | {foo, 2}]), ok. wait_for_send(Socket) -> %% Make sure TLS process processed send message event _ = ssl:connection_information(Socket).