Skip to content

Commit

Permalink
Merge branch 'egil/ia/ssl/accept-timeout/OTP-10600' into maint-r15
Browse files Browse the repository at this point in the history
* egil/ia/ssl/accept-timeout/OTP-10600:
  ssl: Prepare for release
  ssl: Export sslsocket() dialyzer type
  ssl: Cancel non expired timers
  ssl: Fix recv after timeout expired
  ssl: Timeout handling changed so that the fsm-process will terminate if the ssl:ssl_accept/[2,3] or ssl:connect/[3,4] timeout expires.
  • Loading branch information
Erlang/OTP committed Dec 6, 2012
2 parents d5de2e1 + 51e291f commit 2067c7c
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 31 deletions.
4 changes: 4 additions & 0 deletions lib/ssl/src/ssl.appup.src
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
%% -*- erlang -*-
{"%VSN%",
[
{"5.1.1", [{restart_application, ssl}]
},
{"5.1", [
{load_module, ssl_connection, soft_purge, soft_purge, []}
]
Expand All @@ -10,6 +12,8 @@
{<<"3\\.*">>, [{restart_application, ssl}]}
],
[
{"5.1.1", [{restart_application, ssl}]
},
{"5.1", [
{load_module, ssl_connection, soft_purge, soft_purge, []}
]
Expand Down
4 changes: 3 additions & 1 deletion lib/ssl/src/ssl.erl
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,16 @@
-export_type([connect_option/0, listen_option/0, ssl_option/0, transport_option/0,
erl_cipher_suite/0, %% From ssl_cipher.hrl
tls_atom_version/0, %% From ssl_internal.hrl
prf_random/0]).
prf_random/0, sslsocket/0]).

-record(config, {ssl, %% SSL parameters
inet_user, %% User set inet options
emulated, %% #socket_option{} emulated
inet_ssl, %% inet options for internal ssl socket
cb %% Callback info
}).

-type sslsocket() :: #sslsocket{}.
-type connect_option() :: socket_connect_option() | ssl_option() | transport_option().
-type socket_connect_option() :: gen_tcp:connect_option().
-type listen_option() :: socket_listen_option() | ssl_option() | transport_option().
Expand Down
68 changes: 47 additions & 21 deletions lib/ssl/src/ssl_connection.erl
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
log_alert, % boolean()
renegotiation, % {boolean(), From | internal | peer}
start_or_recv_from, % "gen_fsm From"
timer, % start_or_recv_timer
send_queue, % queue()
terminated = false, %
allow_renegotiate = true
Expand Down Expand Up @@ -755,8 +756,9 @@ handle_sync_event({application_data, Data}, From, StateName,
get_timeout(State)};

handle_sync_event({start, Timeout}, StartFrom, hello, State) ->
start_or_recv_cancel_timer(Timeout, StartFrom),
hello(start, State#state{start_or_recv_from = StartFrom});
Timer = start_or_recv_cancel_timer(Timeout, StartFrom),
hello(start, State#state{start_or_recv_from = StartFrom,
timer = Timer});

%% The two clauses below could happen if a server upgrades a socket in
%% active mode. Note that in this case we are lucky that
Expand All @@ -772,8 +774,9 @@ handle_sync_event({start,_}, _From, error, {Error, State = #state{}}) ->
{stop, {shutdown, Error}, {error, Error}, State};

handle_sync_event({start, Timeout}, StartFrom, StateName, State) ->
start_or_recv_cancel_timer(Timeout, StartFrom),
{next_state, StateName, State#state{start_or_recv_from = StartFrom}, get_timeout(State)};
Timer = start_or_recv_cancel_timer(Timeout, StartFrom),
{next_state, StateName, State#state{start_or_recv_from = StartFrom,
timer = Timer}, get_timeout(State)};

handle_sync_event(close, _, StateName, State) ->
%% Run terminate before returning
Expand Down Expand Up @@ -805,14 +808,16 @@ handle_sync_event({shutdown, How0}, _, StateName,
end;

handle_sync_event({recv, N, Timeout}, RecvFrom, connection = StateName, State0) ->
start_or_recv_cancel_timer(Timeout, RecvFrom),
passive_receive(State0#state{bytes_to_read = N, start_or_recv_from = RecvFrom}, StateName);
Timer = start_or_recv_cancel_timer(Timeout, RecvFrom),
passive_receive(State0#state{bytes_to_read = N,
start_or_recv_from = RecvFrom, timer = Timer}, StateName);

%% Doing renegotiate wait with handling request until renegotiate is
%% finished. Will be handled by next_state_is_connection/2.
handle_sync_event({recv, N, Timeout}, RecvFrom, StateName, State) ->
start_or_recv_cancel_timer(Timeout, RecvFrom),
{next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom},
Timer = start_or_recv_cancel_timer(Timeout, RecvFrom),
{next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom,
timer = Timer},
get_timeout(State)};

handle_sync_event({new_user, User}, _From, StateName,
Expand Down Expand Up @@ -981,13 +986,20 @@ handle_info({'DOWN', MonitorRef, _, _, _}, _,

handle_info(allow_renegotiate, StateName, State) ->
{next_state, StateName, State#state{allow_renegotiate = true}, get_timeout(State)};

handle_info({cancel_start_or_recv, RecvFrom}, connection = StateName, #state{start_or_recv_from = RecvFrom} = State) ->

handle_info({cancel_start_or_recv, StartFrom}, StateName,
#state{renegotiation = {false, first}} = State) when StateName =/= connection ->
gen_fsm:reply(StartFrom, {error, timeout}),
{stop, {shutdown, user_timeout}, State#state{timer = undefined}};

handle_info({cancel_start_or_recv, RecvFrom}, StateName, #state{start_or_recv_from = RecvFrom} = State) ->
gen_fsm:reply(RecvFrom, {error, timeout}),
{next_state, StateName, State#state{start_or_recv_from = undefined}, get_timeout(State)};
{next_state, StateName, State#state{start_or_recv_from = undefined,
bytes_to_read = undefined,
timer = undefined}, get_timeout(State)};

handle_info({cancel_start_or_recv, _RecvFrom}, StateName, State) ->
{next_state, StateName, State, get_timeout(State)};
{next_state, StateName, State#state{timer = undefined}, get_timeout(State)};

handle_info(Msg, StateName, State) ->
Report = io_lib:format("SSL: Got unexpected info: ~p ~n", [Msg]),
Expand Down Expand Up @@ -1729,10 +1741,11 @@ passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) ->
end.

read_application_data(Data, #state{user_application = {_Mon, Pid},
socket_options = SOpts,
bytes_to_read = BytesToRead,
start_or_recv_from = RecvFrom,
user_data_buffer = Buffer0} = State0) ->
socket_options = SOpts,
bytes_to_read = BytesToRead,
start_or_recv_from = RecvFrom,
timer = Timer,
user_data_buffer = Buffer0} = State0) ->
Buffer1 = if
Buffer0 =:= <<>> -> Data;
Data =:= <<>> -> Buffer0;
Expand All @@ -1741,9 +1754,11 @@ read_application_data(Data, #state{user_application = {_Mon, Pid},
case get_data(SOpts, BytesToRead, Buffer1) of
{ok, ClientData, Buffer} -> % Send data
SocketOpt = deliver_app_data(SOpts, ClientData, Pid, RecvFrom),
cancel_timer(Timer),
State = State0#state{user_data_buffer = Buffer,
start_or_recv_from = undefined,
bytes_to_read = 0,
timer = undefined,
bytes_to_read = undefined,
socket_options = SocketOpt
},
if
Expand All @@ -1756,6 +1771,8 @@ read_application_data(Data, #state{user_application = {_Mon, Pid},
end;
{more, Buffer} -> % no reply, we need more data
next_record(State0#state{user_data_buffer = Buffer});
{passive, Buffer} ->
next_record_if_active(State0#state{user_data_buffer = Buffer});
{error,_Reason} -> %% Invalid packet in packet mode
deliver_packet_error(SOpts, Buffer1, Pid, RecvFrom),
{stop, normal, State0}
Expand Down Expand Up @@ -1797,6 +1814,9 @@ is_time_to_renegotiate(_,_) ->
%% Picks ClientData
get_data(_, _, <<>>) ->
{more, <<>>};
%% Recv timed out save buffer data until next recv
get_data(#socket_options{active=false}, undefined, Buffer) ->
{passive, Buffer};
get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Buffer)
when Raw =:= raw; Raw =:= 0 -> %% Raw Mode
if
Expand Down Expand Up @@ -2102,7 +2122,6 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User,
tls_record_buffer = <<>>,
tls_cipher_texts = [],
user_application = {Monitor, User},
bytes_to_read = 0,
user_data_buffer = <<>>,
log_alert = true,
session_cache_cb = SessionCacheCb,
Expand Down Expand Up @@ -2325,9 +2344,11 @@ ack_connection(#state{renegotiation = {true, From}} = State) ->
gen_fsm:reply(From, ok),
State#state{renegotiation = undefined};
ack_connection(#state{renegotiation = {false, first},
start_or_recv_from = StartFrom} = State) when StartFrom =/= undefined ->
start_or_recv_from = StartFrom,
timer = Timer} = State) when StartFrom =/= undefined ->
gen_fsm:reply(StartFrom, connected),
State#state{renegotiation = undefined, start_or_recv_from = undefined};
cancel_timer(Timer),
State#state{renegotiation = undefined, start_or_recv_from = undefined, timer = undefined};
ack_connection(State) ->
State.

Expand Down Expand Up @@ -2465,10 +2486,15 @@ default_hashsign(_Version, KeyExchange)
{null, anon}.

start_or_recv_cancel_timer(infinity, _RecvFrom) ->
ok;
undefined;
start_or_recv_cancel_timer(Timeout, RecvFrom) ->
erlang:send_after(Timeout, self(), {cancel_start_or_recv, RecvFrom}).

cancel_timer(undefined) ->
ok;
cancel_timer(Timer) ->
erlang:cancel_timer(Timer).

handle_unrecv_data(StateName, #state{socket = Socket, transport_cb = Transport} = State) ->
inet:setopts(Socket, [{active, false}]),
case Transport:recv(Socket, 0, 0) of
Expand Down
77 changes: 76 additions & 1 deletion lib/ssl/test/ssl_basic_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,9 @@ api_tests() ->
shutdown_write,
shutdown_both,
shutdown_error,
hibernate
hibernate,
ssl_accept_timeout,
ssl_recv_timeout
].

certificate_verify_tests() ->
Expand Down Expand Up @@ -3776,6 +3778,62 @@ hibernate(Config) ->
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).

%%--------------------------------------------------------------------
ssl_accept_timeout(doc) ->
["Test ssl:ssl_accept timeout"];
ssl_accept_timeout(suite) ->
[];
ssl_accept_timeout(Config) ->
process_flag(trap_exit, true),
ServerOpts = ?config(server_opts, Config),
{_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
{from, self()},
{timeout, 5000},
{mfa, {ssl_test_lib,
no_result_msg, []}},
{options, ServerOpts}]),
Port = ssl_test_lib:inet_port(Server),
{ok, CSocket} = gen_tcp:connect(Hostname, Port, [binary, {active, true}]),

receive
{tcp_closed, CSocket} ->
ssl_test_lib:check_result(Server, {error, timeout}),
receive
{'EXIT', Server, _} ->
[] = supervisor:which_children(ssl_connection_sup)
end
end.

%%--------------------------------------------------------------------
ssl_recv_timeout(doc) ->
["Test ssl:ssl_accept timeout"];
ssl_recv_timeout(suite) ->
[];
ssl_recv_timeout(Config) ->
ServerOpts = ?config(server_opts, Config),
ClientOpts = ?config(client_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, send_recv_result_timeout_server, []}},
{options, [{active, false} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),

Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
{from, self()},
{mfa, {?MODULE,
send_recv_result_timeout_client, []}},
{options, [{active, false} | ClientOpts]}]),

ssl_test_lib:check_result(Client, ok, Server, ok),
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).

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

connect_twice(doc) ->
Expand Down Expand Up @@ -4019,6 +4077,23 @@ send_recv_result(Socket) ->
{ok,"Hello world"} = ssl:recv(Socket, 11),
ok.

send_recv_result_timeout_client(Socket) ->
{error, timeout} = ssl:recv(Socket, 11, 500),
ssl:send(Socket, "Hello world"),
receive
Msg ->
io:format("Msg ~p~n",[Msg])
after 500 ->
ok
end,
{ok, "Hello world"} = ssl:recv(Socket, 11, 500),
ok.
send_recv_result_timeout_server(Socket) ->
ssl:send(Socket, "Hello"),
{ok, "Hello world"} = ssl:recv(Socket, 11),
ssl:send(Socket, " world"),
ok.

recv_close(Socket) ->
{error, closed} = ssl:recv(Socket, 11),
receive
Expand Down
27 changes: 20 additions & 7 deletions lib/ssl/test/ssl_test_lib.erl
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,13 @@ run_server(Opts) ->
run_server(ListenSocket, Opts).

run_server(ListenSocket, Opts) ->
AcceptSocket = connect(ListenSocket, Opts),
do_run_server(ListenSocket, connect(ListenSocket, Opts), Opts).

do_run_server(_, {error, timeout} = Result, Opts) ->
Pid = proplists:get_value(from, Opts),
Pid ! {self(), Result};

do_run_server(ListenSocket, AcceptSocket, Opts) ->
Node = proplists:get_value(node, Opts),
Pid = proplists:get_value(from, Opts),
{Module, Function, Args} = proplists:get_value(mfa, Opts),
Expand Down Expand Up @@ -102,7 +108,8 @@ run_server(ListenSocket, Opts) ->
connect(ListenSocket, Opts) ->
Node = proplists:get_value(node, Opts),
ReconnectTimes = proplists:get_value(reconnect_times, Opts, 0),
AcceptSocket = connect(ListenSocket, Node, 1 + ReconnectTimes, dummy),
Timeout = proplists:get_value(timeout, Opts, infinity),
AcceptSocket = connect(ListenSocket, Node, 1 + ReconnectTimes, dummy, Timeout),
case ReconnectTimes of
0 ->
AcceptSocket;
Expand All @@ -111,15 +118,21 @@ connect(ListenSocket, Opts) ->
AcceptSocket
end.

connect(_, _, 0, AcceptSocket) ->
connect(_, _, 0, AcceptSocket, _) ->
AcceptSocket;
connect(ListenSocket, Node, N, _) ->
connect(ListenSocket, Node, N, _, Timeout) ->
test_server:format("ssl:transport_accept(~p)~n", [ListenSocket]),
{ok, AcceptSocket} = rpc:call(Node, ssl, transport_accept,
[ListenSocket]),
test_server:format("ssl:ssl_accept(~p)~n", [AcceptSocket]),
ok = rpc:call(Node, ssl, ssl_accept, [AcceptSocket]),
connect(ListenSocket, Node, N-1, AcceptSocket).
test_server:format("ssl:ssl_accept(~p, ~p)~n", [AcceptSocket, Timeout]),

case rpc:call(Node, ssl, ssl_accept, [AcceptSocket, Timeout]) of
ok ->
connect(ListenSocket, Node, N-1, AcceptSocket, Timeout);
Result ->
Result
end.


remove_close_msg(0) ->
ok;
Expand Down
2 changes: 1 addition & 1 deletion lib/ssl/vsn.mk
Original file line number Diff line number Diff line change
@@ -1 +1 @@
SSL_VSN = 5.1.1
SSL_VSN = 5.1.2

0 comments on commit 2067c7c

Please sign in to comment.