diff options
| author | Ingela Anderton Andin <[email protected]> | 2018-11-19 13:50:35 +0100 | 
|---|---|---|
| committer | Ingela Anderton Andin <[email protected]> | 2018-12-03 19:42:07 +0100 | 
| commit | 6168cf2f5f8b5839b1a56ce870658d76faf3c22f (patch) | |
| tree | 9333480a078138395129acb89cdce3a7acb8ef6d | |
| parent | 19c6875fb10935864f6f28607c61700eaf56c59a (diff) | |
| download | otp-6168cf2f5f8b5839b1a56ce870658d76faf3c22f.tar.gz otp-6168cf2f5f8b5839b1a56ce870658d76faf3c22f.tar.bz2 otp-6168cf2f5f8b5839b1a56ce870658d76faf3c22f.zip  | |
ssl: Internaly use {active, N}
Make next_record an internal help function to next_event and avoid
duplicate calls to tls_socket:setopts for setting the active option.
| -rw-r--r-- | lib/ssl/doc/src/ssl_app.xml | 14 | ||||
| -rw-r--r-- | lib/ssl/src/dtls_connection.erl | 137 | ||||
| -rw-r--r-- | lib/ssl/src/ssl_connection.erl | 176 | ||||
| -rw-r--r-- | lib/ssl/src/ssl_internal.hrl | 1 | ||||
| -rw-r--r-- | lib/ssl/src/tls_connection.erl | 129 | ||||
| -rw-r--r-- | lib/ssl/test/ssl_basic_SUITE.erl | 21 | 
6 files changed, 224 insertions, 254 deletions
diff --git a/lib/ssl/doc/src/ssl_app.xml b/lib/ssl/doc/src/ssl_app.xml index f6d9021d4a..53b899058f 100644 --- a/lib/ssl/doc/src/ssl_app.xml +++ b/lib/ssl/doc/src/ssl_app.xml @@ -171,6 +171,20 @@  	  shutdown gracefully. Defaults to 5000 milliseconds.     	</p>        </item> + +      <tag><c><![CDATA[internal_active_n = integer() <optional>]]></c></tag> +        <item> +	<p> +	  For TLS connections this value is used to handle the +	  internal socket. As the implementation was changed from an +	  active once to an active N behavior (N = 100), for +	  performance reasons, this option exist for possible tweaking +	  or restoring of the old behavior (internal_active_n = 1) in +	  unforeseen scenarios.  This option will not affect erlang +	  distribution over TLS that will always run in active N mode. +	  Added in ssl-9.1 (OTP-21.2). +	</p> +      </item>      </taglist>    </section> diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index 2a0b2b317d..fa96c585a0 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -39,7 +39,7 @@  -export([start_fsm/8, start_link/7, init/1, pids/1]).  %% State transition handling	  --export([next_record/1, next_event/3, next_event/4,  handle_common_event/4]). +-export([next_event/3, next_event/4, handle_common_event/4]).  %% Handshake handling  -export([renegotiate/2, send_handshake/2,  @@ -50,7 +50,7 @@  -export([encode_alert/3, send_alert/2, send_alert_in_connection/2, close/5, protocol_name/0]).  %% Data handling --export([encode_data/3, passive_receive/2, next_record_if_active/1, +-export([encode_data/3, passive_receive/2,   	 send/3, socket/5, setopts/3, getopts/3]).  %% gen_statem state functions @@ -162,9 +162,9 @@ next_record(State) ->  next_event(StateName, Record, State) ->      next_event(StateName, Record, State, []). -next_event(connection = StateName, no_record, +next_event(StateName, no_record,  	   #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) -> -    case next_record_if_active(State0) of +    case next_record(State0) of  	{no_record, State} ->              ssl_connection:hibernate_after(StateName, State, Actions);          {#ssl_tls{epoch = CurrentEpoch, @@ -178,21 +178,18 @@ next_event(connection = StateName, no_record,  	{#ssl_tls{epoch = Epoch,  		  type = ?HANDSHAKE,  		  version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 -> -	    {State2, MoreActions} = send_handshake_flight(State1, CurrentEpoch), -            {NextRecord, State} = next_record(State2), -            next_event(StateName, NextRecord, State, Actions ++ MoreActions); +	    {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch), +            next_event(StateName, no_record, State, Actions ++ MoreActions);          %% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake          {#ssl_tls{epoch = Epoch,  		  type = ?CHANGE_CIPHER_SPEC,  		  version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 -> -	    {State2, MoreActions} = send_handshake_flight(State1, CurrentEpoch), -	    {NextRecord, State} = next_record(State2), -            next_event(StateName, NextRecord, State, Actions ++ MoreActions); +	    {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch), +            next_event(StateName, no_record, State, Actions ++ MoreActions);  	{#ssl_tls{epoch = _Epoch, -		  version = _Version}, State1} -> +		  version = _Version}, State} ->  	    %% TODO maybe buffer later epoch -            {Record, State} = next_record(State1), -            next_event(StateName, Record, State, Actions);  +            next_event(StateName, no_record, State, Actions);   	{#alert{} = Alert, State} ->  	    {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}      end; @@ -210,24 +207,20 @@ next_event(connection = StateName, Record,  	#ssl_tls{epoch = Epoch,                   type = ?HANDSHAKE,                   version = _Version} when Epoch == CurrentEpoch-1 -> -	    {State1, MoreActions} = send_handshake_flight(State0, CurrentEpoch), -            {NextRecord, State} = next_record(State1), -            next_event(StateName, NextRecord, State, Actions ++ MoreActions); +	    {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch), +            next_event(StateName, no_record, State, Actions ++ MoreActions);          %% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake          #ssl_tls{epoch = Epoch,                   type = ?CHANGE_CIPHER_SPEC,                   version = _Version} when Epoch == CurrentEpoch-1 -> -	    {State1, MoreActions} = send_handshake_flight(State0, CurrentEpoch), -            {NextRecord, State} = next_record(State1), -            next_event(StateName, NextRecord, State, Actions ++ MoreActions);  +	    {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch), +            next_event(StateName, no_record, State, Actions ++ MoreActions);           _ ->               next_event(StateName, no_record, State0, Actions)       end;  next_event(StateName, Record,   	   #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) ->      case Record of -	no_record -> -            {next_state, StateName, State0, Actions};          #ssl_tls{epoch = CurrentEpoch,                   version = Version} = Record ->              State = dtls_version(StateName, Version, State0), @@ -236,8 +229,7 @@ next_event(StateName, Record,  	#ssl_tls{epoch = _Epoch,  		 version = _Version} = _Record ->  	    %% TODO maybe buffer later epoch -            {Record, State} = next_record(State0), -            next_event(StateName, Record, State, Actions);  +            next_event(StateName, no_record, State0, Actions);   	#alert{} = Alert ->  	    {next_state, StateName, State0, [{next_event, internal, Alert} | Actions]}      end. @@ -254,8 +246,7 @@ handle_common_event(internal, #ssl_tls{type = ?HANDSHAKE,      try  	case dtls_handshake:get_dtls_handshake(Version, Data, Buffers0) of  	    {[], Buffers} -> -		{Record, State} = next_record(State0#state{protocol_buffers = Buffers}), -		next_event(StateName, Record, State); +		next_event(StateName, no_record, State0#state{protocol_buffers = Buffers});  	    {Packets, Buffers} ->  		State = State0#state{protocol_buffers = Buffers},  		Events = dtls_handshake_events(Packets), @@ -291,15 +282,12 @@ handle_common_event(internal, #ssl_tls{type = _Unknown}, StateName, State) ->  renegotiate(#state{role = client} = State, Actions) ->      %% Handle same way as if server requested      %% the renegotiation -    {next_state, connection, State, -     [{next_event, internal, #hello_request{}} | Actions]}; - +    next_event(connection, no_record, State, [{next_event, internal, #hello_request{}} | Actions]);  renegotiate(#state{role = server} = State0, Actions) ->      HelloRequest = ssl_handshake:hello_request(),      State1 = prepare_flight(State0), -    {State2, MoreActions} = send_handshake(HelloRequest, State1), -    {Record, State} = next_record(State2), -    next_event(hello, Record, State, Actions ++ MoreActions). +    {State, MoreActions} = send_handshake(HelloRequest, State1), +    next_event(hello, no_record, State, Actions ++ MoreActions).  send_handshake(Handshake, #state{connection_states = ConnectionStates} = State) ->      #{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write), @@ -396,19 +384,11 @@ encode_data(Data, Version, ConnectionStates0)->  passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) ->      case Buffer of  	<<>> -> -	    {Record, State} = next_record(State0), -	    next_event(StateName, Record, State); +	    next_event(StateName, no_record, State0);  	_ ->  	    {Record, State} = ssl_connection:read_application_data(<<>>, State0),  	    next_event(StateName, Record, State)      end. -next_record_if_active(State = -		      #state{socket_options = -			     #socket_options{active = false}}) -> -    {no_record ,State}; - -next_record_if_active(State) -> -    next_record(State).  send(Transport, {_, {{_,_}, _} = Socket}, Data) ->      send(Transport, Socket, Data); @@ -451,15 +431,14 @@ init({call, From}, {start, Timeout},      HelloVersion = dtls_record:hello_version(Version, SslOpts#ssl_options.versions),      State1 = prepare_flight(State0#state{negotiated_version = Version}),      {State2, Actions} = send_handshake(Hello, State1#state{negotiated_version = HelloVersion}),   -    State3 = State2#state{negotiated_version = Version, %% Requested version -			  session = -			      Session0#session{session_id = Hello#client_hello.session_id}, -			  start_or_recv_from = From, -			  timer = Timer, -                          flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT} -                         }, -    {Record, State} = next_record(State3), -    next_event(hello, Record, State, Actions); +    State = State2#state{negotiated_version = Version, %% Requested version +                         session = +                             Session0#session{session_id = Hello#client_hello.session_id}, +                         start_or_recv_from = From, +                         timer = Timer, +                         flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT} +                        }, +    next_event(hello, no_record, State, Actions);  init({call, _} = Type, Event, #state{role = server, data_tag = udp} = State) ->      Result = gen_handshake(?FUNCTION_NAME, Type, Event,                              State#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}, @@ -469,7 +448,6 @@ init({call, _} = Type, Event, #state{role = server, data_tag = udp} = State) ->                                                               max_ignored_alerts => 10}}),      erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret),      Result; -  init({call, _} = Type, Event, #state{role = server} = State) ->      %% I.E. DTLS over sctp      gen_handshake(?FUNCTION_NAME, Type, Event, State#state{flight_state = reliable}); @@ -519,9 +497,9 @@ hello(internal, #client_hello{cookie = <<>>,      %% negotiated.      VerifyRequest = dtls_handshake:hello_verify_request(Cookie, ?HELLO_VERIFY_REQUEST_VERSION),      State1 = prepare_flight(State0#state{negotiated_version = Version}), -    {State2, Actions} = send_handshake(VerifyRequest, State1), -    {Record, State} = next_record(State2), -    next_event(?FUNCTION_NAME, Record, State#state{tls_handshake_history = ssl_handshake:init_handshake_history()}, Actions); +    {State, Actions} = send_handshake(VerifyRequest, State1), +    next_event(?FUNCTION_NAME, no_record,  +               State#state{tls_handshake_history = ssl_handshake:init_handshake_history()}, Actions);  hello(internal, #hello_verify_request{cookie = Cookie}, #state{role = client,  							       host = Host, port = Port,   							       ssl_options = SslOpts, @@ -540,27 +518,29 @@ hello(internal, #hello_verify_request{cookie = Cookie}, #state{role = client,      State1 = prepare_flight(State0#state{tls_handshake_history = ssl_handshake:init_handshake_history()}),      {State2, Actions} = send_handshake(Hello, State1),  -    State3 = State2#state{negotiated_version = Version, %% Requested version -			  session = -			      Session0#session{session_id =  -						   Hello#client_hello.session_id}}, -    {Record, State} = next_record(State3), -    next_event(?FUNCTION_NAME, Record, State, Actions); -hello(internal, #client_hello{extensions = Extensions} = Hello, #state{ssl_options = #ssl_options{handshake = hello}, -                                                                       start_or_recv_from = From} = State) -> +    State = State2#state{negotiated_version = Version, %% Requested version +                         session = +                             Session0#session{session_id =  +                                                  Hello#client_hello.session_id}}, +    next_event(?FUNCTION_NAME, no_record, State, Actions); +hello(internal, #client_hello{extensions = Extensions} = Hello,  +      #state{ssl_options = #ssl_options{handshake = hello}, +             start_or_recv_from = From} = State) ->      {next_state, user_hello, State#state{start_or_recv_from = undefined,                                               hello = Hello},       [{reply, From, {ok, ssl_connection:map_extensions(Extensions)}}]}; -hello(internal, #server_hello{extensions = Extensions} = Hello, #state{ssl_options = #ssl_options{handshake = hello}, -                                                                       start_or_recv_from = From} = State) -> +hello(internal, #server_hello{extensions = Extensions} = Hello,  +      #state{ssl_options = #ssl_options{handshake = hello}, +             start_or_recv_from = From} = State) ->      {next_state, user_hello, State#state{start_or_recv_from = undefined,                                               hello = Hello},       [{reply, From, {ok, ssl_connection:map_extensions(Extensions)}}]};      -hello(internal, #client_hello{cookie = Cookie} = Hello, #state{role = server, -							       transport_cb = Transport, -							       socket = Socket, -                                                               protocol_specific = #{current_cookie_secret := Secret, -                                                                                     previous_cookie_secret := PSecret}} = State0) -> +hello(internal, #client_hello{cookie = Cookie} = Hello,  +      #state{role = server, +             transport_cb = Transport, +             socket = Socket, +             protocol_specific = #{current_cookie_secret := Secret, +                                   previous_cookie_secret := PSecret}} = State0) ->      {ok, {IP, Port}} = dtls_socket:peername(Transport, Socket),      case dtls_handshake:cookie(Secret, IP, Port, Hello) of  	Cookie -> @@ -595,8 +575,7 @@ hello(internal, {handshake, {#hello_verify_request{} = Handshake, _}}, State) ->      {next_state, ?FUNCTION_NAME, State, [{next_event, internal, Handshake}]};  hello(internal,  #change_cipher_spec{type = <<1>>}, State0) ->      {State1, Actions0} = send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)), -    {Record, State2} = next_record(State1), -    {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, Record, State2, Actions0), +    {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, no_record, State1, Actions0),      %% This will reset the retransmission timer by repeating the enter state event      {repeat_state, State, Actions};  hello(info, Event, State) -> @@ -647,8 +626,7 @@ certify(internal = Type, #server_hello_done{} = Event, State) ->      ssl_connection:certify(Type, Event, prepare_flight(State), ?MODULE);  certify(internal,  #change_cipher_spec{type = <<1>>}, State0) ->      {State1, Actions0} = send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)), -    {Record, State2} = next_record(State1), -    {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, Record, State2, Actions0), +    {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, no_record, State1, Actions0),      %% This will reset the retransmission timer by repeating the enter state event      {repeat_state, State, Actions};  certify(state_timeout, Event, State) -> @@ -701,13 +679,11 @@ connection(internal, #hello_request{}, #state{host = Host, port = Port,      Version = Hello#client_hello.client_version,      HelloVersion = dtls_record:hello_version(Version, SslOpts#ssl_options.versions),      State1 = prepare_flight(State0), -    {State2, Actions} = send_handshake(Hello, State1#state{negotiated_version = HelloVersion}), -    {Record, State} = -	next_record( -	  State2#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}, -                       session = Session0#session{session_id -						  = Hello#client_hello.session_id}}), -    next_event(hello, Record, State, Actions); +    {State, Actions} = send_handshake(Hello, State1#state{negotiated_version = HelloVersion}), +    next_event(hello, no_record, State#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}, +                                             session = Session0#session{session_id +                                                                        = Hello#client_hello.session_id}}, +               Actions);  connection(internal, #client_hello{} = Hello, #state{role = server, allow_renegotiate = true} = State) ->      %% Mitigate Computational DoS attack      %% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html @@ -927,8 +903,7 @@ handle_state_timeout(flight_retransmission_timeout, StateName,                       #state{flight_state = {retransmit, NextTimeout}} = State0) ->      {State1, Actions0} = send_handshake_flight(State0#state{flight_state = {retransmit, NextTimeout}},                                                 retransmit_epoch(StateName, State0)), -    {Record, State2} = next_record(State1), -    {next_state, StateName, State, Actions} = next_event(StateName, Record, State2, Actions0), +    {next_state, StateName, State, Actions} = next_event(StateName, no_record, State1, Actions0),      %% This will reset the retransmission timer by repeating the enter state event      {repeat_state, State, Actions}. diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index acd9f14f7b..08197f6038 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -403,9 +403,8 @@ handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert,      log_alert(SslOpts#ssl_options.log_alert,  Role,                Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),      gen_statem:reply(From, {error, renegotiation_rejected}), -    State1 = Connection:reinit_handshake_data(State0), -    {Record, State} = Connection:next_record(State1#state{renegotiation = undefined}), -    Connection:next_event(connection, Record, State); +    State = Connection:reinit_handshake_data(State0), +    Connection:next_event(connection, no_record, State#state{renegotiation = undefined});  handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName,   	     #state{role = Role, @@ -414,18 +413,16 @@ handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert,      log_alert(SslOpts#ssl_options.log_alert,  Role,                Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),      gen_statem:reply(From, {error, renegotiation_rejected}), -    {Record, State1} = Connection:next_record(State0),      %% Go back to connection! -    State = Connection:reinit(State1#state{renegotiation = undefined}), -    Connection:next_event(connection, Record, State); +    State = Connection:reinit(State0#state{renegotiation = undefined}), +    Connection:next_event(connection, no_record, State);  %% Gracefully log and ignore all other warning alerts  handle_alert(#alert{level = ?WARNING} = Alert, StateName, -	     #state{ssl_options = SslOpts, protocol_cb = Connection, role = Role} = State0) -> +	     #state{ssl_options = SslOpts, protocol_cb = Connection, role = Role} = State) ->      log_alert(SslOpts#ssl_options.log_alert,  Role,                Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), -    {Record, State} = Connection:next_record(State0), -    Connection:next_event(StateName, Record, State). +    Connection:next_event(StateName, no_record, State).  %%====================================================================  %% Data handling @@ -472,28 +469,26 @@ read_application_data(Data, #state{user_application = {_Mon, Pid},                                 Buffer =:= <<>> ->                                     %% Passive mode, wait for active once or recv                              %% Active and empty, get more data -                                   Connection:next_record_if_active(State); +                                   {no_record, State};                                 true -> %% We have more data                                     read_application_data(<<>>, State)                             end                     end;  	{more, Buffer} -> % no reply, we need more data -	    Connection:next_record(State0#state{user_data_buffer = Buffer}); +            {no_record, State0#state{user_data_buffer = Buffer}};  	{passive, Buffer} -> -	    Connection:next_record_if_active(State0#state{user_data_buffer = Buffer}); +	    {no_record, State0#state{user_data_buffer = Buffer}};  	{error,_Reason} -> %% Invalid packet in packet mode  	    deliver_packet_error(Connection:pids(State0),                                   Transport, Socket, SOpts, Buffer1, Pid, RecvFrom, Tracker, Connection),              stop(normal, State0)      end. -dist_app_data(ClientData, #state{protocol_cb = Connection, -                                 erl_dist_data = #{dist_handle := undefined, +dist_app_data(ClientData, #state{erl_dist_data = #{dist_handle := undefined,                                                     dist_buffer := DistBuff} = DistData} = State) -> -    Connection:next_record_if_active(State#state{erl_dist_data = DistData#{dist_buffer => [ClientData, DistBuff]}}); +    {no_record, State#state{erl_dist_data = DistData#{dist_buffer => [ClientData, DistBuff]}}};  dist_app_data(ClientData, #state{erl_dist_data = #{dist_handle := DHandle,                                                     dist_buffer := DistBuff} = ErlDistData, -                                 protocol_cb = Connection,                                   user_data_buffer = Buffer,                                   socket_options = SOpts} = State) ->      Data = merge_dist_data(DistBuff, ClientData), @@ -502,7 +497,7 @@ dist_app_data(ClientData, #state{erl_dist_data = #{dist_handle := DHandle,                 Buffer =:= <<>> ->              %% Passive mode, wait for active once or recv              %% Active and empty, get more data -            Connection:next_record_if_active(State#state{erl_dist_data = ErlDistData#{dist_buffer => <<>>}}); +            {no_record, State#state{erl_dist_data = ErlDistData#{dist_buffer => <<>>}}};          _ -> %% We have more data              read_application_data(<<>>, State)      catch error:_ -> @@ -606,9 +601,7 @@ ssl_config(Opts, Role, State0, Type) ->  init({call, From}, {start, Timeout}, State0, Connection) ->      Timer = start_or_recv_cancel_timer(Timeout, From), -    {Record, State} = Connection:next_record(State0#state{start_or_recv_from = From, -							  timer = Timer}), -    Connection:next_event(hello, Record, State); +    Connection:next_event(hello, no_record, State0#state{start_or_recv_from = From, timer = Timer});  init({call, From}, {start, {Opts, EmOpts}, Timeout},        #state{role = Role, ssl_options = OrigSSLOptions,              socket_options = SockOpts} = State0, Connection) -> @@ -721,20 +714,19 @@ abbreviated(internal, #finished{verify_data = Data} = Finished,  %% only allowed to send next_protocol message after change cipher spec  %% & before finished message and it is not allowed during renegotiation  abbreviated(internal, #next_protocol{selected_protocol = SelectedProtocol}, -	    #state{role = server, expecting_next_protocol_negotiation = true} = State0, +	    #state{role = server, expecting_next_protocol_negotiation = true} = State,  	    Connection) -> -    {Record, State} = -	Connection:next_record(State0#state{negotiated_protocol = SelectedProtocol}), -    Connection:next_event(?FUNCTION_NAME, Record,  -			  State#state{expecting_next_protocol_negotiation = false}); +    Connection:next_event(?FUNCTION_NAME, no_record,  +			  State#state{negotiated_protocol = SelectedProtocol, +                                      expecting_next_protocol_negotiation = false});  abbreviated(internal,   	    #change_cipher_spec{type = <<1>>},   -            #state{connection_states = ConnectionStates0} = State0, Connection) -> +            #state{connection_states = ConnectionStates0} = State, Connection) ->      ConnectionStates1 =  	ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection), -    {Record, State} = Connection:next_record(State0#state{connection_states =  -							      ConnectionStates1}), -    Connection:next_event(?FUNCTION_NAME, Record, State#state{expecting_finished = true}); +    Connection:next_event(?FUNCTION_NAME, no_record, State#state{connection_states =  +                                                                     ConnectionStates1, +                                                                 expecting_finished = true});  abbreviated(info, Msg, State, _) ->      handle_info(Msg, ?FUNCTION_NAME, State);  abbreviated(Type, Msg, State, Connection) -> @@ -763,9 +755,7 @@ certify(internal, #certificate{asn1_certificates = []},  	       ssl_options = #ssl_options{verify = verify_peer,  					  fail_if_no_peer_cert = false}} =  	State0, Connection) -> -    {Record, State} =  -	Connection:next_record(State0#state{client_certificate_requested = false}), -    Connection:next_event(?FUNCTION_NAME, Record, State); +    Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_requested = false});  certify(internal, #certificate{},  	#state{role = server,  	       negotiated_version = Version, @@ -833,24 +823,23 @@ certify(internal, #certificate_request{},                       Version, ?FUNCTION_NAME, State);  certify(internal, #certificate_request{},  	#state{session = #session{own_certificate = undefined}, -	       role = client} = State0, Connection) -> +	       role = client} = State, Connection) ->      %% The client does not have a certificate and will send an empty reply, the server may fail       %% or accept the connection by its own preference. No signature algorihms needed as there is      %% no certificate to verify. -    {Record, State} = Connection:next_record(State0), -    Connection:next_event(?FUNCTION_NAME, Record, State#state{client_certificate_requested = true}); +    Connection:next_event(?FUNCTION_NAME, no_record, State#state{client_certificate_requested = true});  certify(internal, #certificate_request{} = CertRequest,  	#state{session = #session{own_certificate = Cert},  	       role = client,  	       ssl_options = #ssl_options{signature_algs = SupportedHashSigns}, -	       negotiated_version = Version} = State0, Connection) -> +	       negotiated_version = Version} = State, Connection) ->      case ssl_handshake:select_hashsign(CertRequest, Cert, SupportedHashSigns, ssl:tls_version(Version)) of  	#alert {} = Alert -> -	    handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0); -	NegotiatedHashSign ->  -	    {Record, State} = Connection:next_record(State0#state{client_certificate_requested = true}), -	    Connection:next_event(?FUNCTION_NAME, Record, -				  State#state{cert_hashsign_algorithm = NegotiatedHashSign}) +	    handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); +	NegotiatedHashSign -> 	 +	    Connection:next_event(?FUNCTION_NAME, no_record, +				  State#state{client_certificate_requested = true, +                                              cert_hashsign_algorithm = NegotiatedHashSign})      end;  %% PSK and RSA_PSK might bypass the Server-Key-Exchange  certify(internal, #server_hello_done{}, @@ -959,7 +948,7 @@ cipher(internal, #certificate_verify{signature = Signature,  	      negotiated_version = Version,  	      session = #session{master_secret = MasterSecret},  	      tls_handshake_history = Handshake -	     } = State0, Connection) -> +	     } = State, Connection) ->      TLSVersion = ssl:tls_version(Version),      %% Use negotiated value if TLS-1.2 otherwhise return default @@ -967,11 +956,10 @@ cipher(internal, #certificate_verify{signature = Signature,      case ssl_handshake:certificate_verify(Signature, PublicKeyInfo,  					  TLSVersion, HashSign, MasterSecret, Handshake) of  	valid -> -	    {Record, State} = Connection:next_record(State0), -	    Connection:next_event(?FUNCTION_NAME, Record, +	    Connection:next_event(?FUNCTION_NAME, no_record,  				  State#state{cert_hashsign_algorithm = HashSign});  	#alert{} = Alert -> -	    handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) +	    handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)      end;  %% client must send a next protocol message if we are expecting it  cipher(internal, #finished{}, @@ -1005,18 +993,18 @@ cipher(internal, #finished{verify_data = Data} = Finished,  %% & before finished message and it is not allowed during renegotiation  cipher(internal, #next_protocol{selected_protocol = SelectedProtocol},         #state{role = server, expecting_next_protocol_negotiation = true, -	      expecting_finished = true} = State0, Connection) -> -    {Record, State} =  -	Connection:next_record(State0#state{negotiated_protocol = SelectedProtocol}), -    Connection:next_event(?FUNCTION_NAME, Record,  -			  State#state{expecting_next_protocol_negotiation = false}); +	      expecting_finished = true} = State, Connection) -> +    Connection:next_event(?FUNCTION_NAME, no_record,  +			  State#state{expecting_next_protocol_negotiation = false, +                                      negotiated_protocol = SelectedProtocol +                                     });  cipher(internal, #change_cipher_spec{type = <<1>>},  #state{connection_states = ConnectionStates0} = -	   State0, Connection) -> -    ConnectionStates1 = +	   State, Connection) -> +    ConnectionStates =  	ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection), -    {Record, State} = Connection:next_record(State0#state{connection_states =  -							      ConnectionStates1}), -    Connection:next_event(?FUNCTION_NAME, Record, State#state{expecting_finished = true}); +    Connection:next_event(?FUNCTION_NAME, no_record, State#state{connection_states =  +                                                                     ConnectionStates, +                                                                 expecting_finished = true});  cipher(Type, Msg, State, Connection) ->      handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). @@ -1126,15 +1114,15 @@ handle_common_event(internal, {application_data, Data}, StateName, State0, Conne      case read_application_data(Data, State0) of  	{stop, _, _} = Stop->              Stop; -	{Record, State} -> -   	     case Connection:next_event(StateName, Record, State) of -                 {next_state, StateName, State} -> -                     hibernate_after(StateName, State, []); -                 {next_state, StateName, State, Actions} ->  -                     hibernate_after(StateName, State, Actions); -                 {stop, _, _} = Stop -> -                     Stop -             end  +	{Record, State1} -> +            case Connection:next_event(StateName, Record, State1) of +                {next_state, StateName, State} -> +                    hibernate_after(StateName, State, []); +                {next_state, StateName, State, Actions} ->  +                    hibernate_after(StateName, State, Actions); +                {stop, _, _} = Stop -> +                    Stop +            end       end;  handle_common_event(internal, #change_cipher_spec{type = <<1>>}, StateName,   		    #state{negotiated_version = Version} = State,  _) -> @@ -1447,13 +1435,12 @@ new_server_hello(#server_hello{cipher_suite = CipherSuite,  		       negotiated_version = Version} = State0, Connection) ->      try server_certify_and_key_exchange(State0, Connection) of          #state{} = State1 -> -            {State2, Actions} = server_hello_done(State1, Connection), +            {State, Actions} = server_hello_done(State1, Connection),  	    Session =  		Session0#session{session_id = SessionId,  				 cipher_suite = CipherSuite,  				 compression_method = Compression}, -	    {Record, State} = Connection:next_record(State2#state{session = Session}), -	    Connection:next_event(certify, Record, State, Actions) +	    Connection:next_event(certify, no_record, State#state{session = Session}, Actions)      catch          #alert{} = Alert ->  	    handle_own_alert(Alert, Version, hello, State0) @@ -1468,10 +1455,9 @@ resumed_server_hello(#state{session = Session,  	{_, ConnectionStates1} ->  	    State1 = State0#state{connection_states = ConnectionStates1,  				  session = Session}, -	    {State2, Actions} = +	    {State, Actions} =  		finalize_handshake(State1, abbreviated, Connection), -	    {Record, State} = Connection:next_record(State2), -	    Connection:next_event(abbreviated, Record, State, Actions); +	    Connection:next_event(abbreviated, no_record, State, Actions);  	#alert{} = Alert ->  	    handle_own_alert(Alert, Version, hello, State0)      end. @@ -1493,10 +1479,8 @@ handle_peer_cert(Role, PeerCert, PublicKeyInfo,  			 Session#session{peer_certificate = PeerCert},  			 public_key_info = PublicKeyInfo},      #{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_definition(CipherSuite), -    State2 = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlgorithm, State1), - -    {Record, State} = Connection:next_record(State2), -    Connection:next_event(certify, Record, State). +    State = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlgorithm, State1), +    Connection:next_event(certify, no_record, State).  handle_peer_cert_key(client, _,  		     {?'id-ecPublicKey',  #'ECPoint'{point = _ECPoint} = PublicKey, @@ -1554,11 +1538,10 @@ client_certify_and_key_exchange(#state{negotiated_version = Version} =      try do_client_certify_and_key_exchange(State0, Connection) of          State1 = #state{} ->  	    {State2, Actions} = finalize_handshake(State1, certify, Connection), -            State3 = State2#state{ -		       %% Reinitialize -		       client_certificate_requested = false}, -	    {Record, State} = Connection:next_record(State3), -	    Connection:next_event(cipher, Record, State, Actions) +            State = State2#state{ +                      %% Reinitialize +                      client_certificate_requested = false}, +	    Connection:next_event(cipher, no_record, State, Actions)      catch          throw:#alert{} = Alert ->  	    handle_own_alert(Alert, Version, certify, State0) @@ -1967,10 +1950,9 @@ calculate_master_secret(PremasterSecret,  				     ConnectionStates0, server) of  	{MasterSecret, ConnectionStates} ->  	    Session = Session0#session{master_secret = MasterSecret}, -	    State1 = State0#state{connection_states = ConnectionStates, +	    State = State0#state{connection_states = ConnectionStates,  				  session = Session}, -	    {Record, State} = Connection:next_record(State1), -	    Connection:next_event(Next, Record, State); +	    Connection:next_event(Next, no_record, State);  	#alert{} = Alert ->  	    handle_own_alert(Alert, Version, certify, State0)      end. @@ -2043,10 +2025,9 @@ calculate_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey},  calculate_secret(#server_psk_params{  		    hint = IdentityHint}, -		 State0, Connection) -> +		 State, Connection) ->      %% store for later use -    {Record, State} = Connection:next_record(State0#state{psk_identity = IdentityHint}), -    Connection:next_event(certify, Record, State); +    Connection:next_event(certify, no_record, State#state{psk_identity = IdentityHint});  calculate_secret(#server_dhe_psk_params{  		    dh_params = #server_dh_params{dh_p = Prime, dh_g = Base}} = ServerKey, @@ -2339,9 +2320,8 @@ prepare_connection(#state{renegotiation = Renegotiate,  			  start_or_recv_from = RecvFrom} = State0, Connection)     when Renegotiate =/= {false, first},          RecvFrom =/= undefined -> -    State1 = Connection:reinit(State0),    -    {Record, State} = Connection:next_record(State1), -    {Record, ack_connection(State)}; +    State = Connection:reinit(State0),    +    {no_record, ack_connection(State)};  prepare_connection(State0, Connection) ->      State = Connection:reinit(State0),      {no_record, ack_connection(State)}. @@ -2395,26 +2375,23 @@ handle_new_session(NewId, CipherSuite, Compression,      Session = Session0#session{session_id = NewId,  			       cipher_suite = CipherSuite,  			       compression_method = Compression}, -    {Record, State} = Connection:next_record(State0#state{session = Session}), -    Connection:next_event(certify, Record, State). +    Connection:next_event(certify, no_record, State0#state{session = Session}).  handle_resumed_session(SessId, #state{connection_states = ConnectionStates0,  				      negotiated_version = Version,  				      host = Host, port = Port,  				      protocol_cb = Connection,  				      session_cache = Cache, -				      session_cache_cb = CacheCb} = State0) -> +				      session_cache_cb = CacheCb} = State) ->      Session = CacheCb:lookup(Cache, {{Host, Port}, SessId}),      case ssl_handshake:master_secret(ssl:tls_version(Version), Session,  				     ConnectionStates0, client) of  	{_, ConnectionStates} -> -	    {Record, State} = -		Connection:next_record(State0#state{ -			      connection_states = ConnectionStates, -			      session = Session}), -	    Connection:next_event(abbreviated, Record, State); +	    Connection:next_event(abbreviated, no_record, State#state{ +                                                            connection_states = ConnectionStates, +                                                            session = Session});  	#alert{} = Alert -> -	    handle_own_alert(Alert, Version, hello, State0) +	    handle_own_alert(Alert, Version, hello, State)      end.  make_premaster_secret({MajVer, MinVer}, rsa) -> @@ -2464,10 +2441,7 @@ handle_active_option(false, connection = StateName, To, Reply, State) ->  handle_active_option(_, connection = StateName0, To, Reply, #state{protocol_cb = Connection,  							      user_data_buffer = <<>>} = State0) -> -    %% Need data, set active once -    {Record, State1} = Connection:next_record_if_active(State0), -    %% Note: Renogotiation may cause StateName0 =/= StateName -    case Connection:next_event(StateName0, Record, State1) of +    case Connection:next_event(StateName0, no_record, State0) of  	{next_state, StateName, State} ->  	    hibernate_after(StateName, State, [{reply, To, Reply}]);  	{next_state, StateName, State, Actions} ->  diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index fd246e2550..63e751440a 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -60,6 +60,7 @@  -define(CDR_MAGIC, "GIOP").  -define(CDR_HDR_SIZE, 12). +-define(INTERNAL_ACTIVE_N, 100).  -define(DEFAULT_TIMEOUT, 5000).  -define(NO_DIST_POINT, "http://dummy/no_distribution_point"). diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 4dfb50967d..48f1206769 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -46,7 +46,7 @@  -export([start_fsm/8, start_link/8, init/1, pids/1]).  %% State transition handling	  --export([next_record/1, next_event/3, next_event/4,  +-export([next_event/3, next_event/4,            handle_common_event/4]).  %% Handshake handling @@ -61,7 +61,7 @@           encode_alert/3, close/5, protocol_name/0]).  %% Data handling --export([encode_data/3, passive_receive/2, next_record_if_active/1,  +-export([encode_data/3, passive_receive/2,            send/3, socket/5, setopts/3, getopts/3]).  %% gen_statem state functions @@ -161,30 +161,30 @@ next_record(#state{protocol_buffers =  	    {Alert, State}      end;  next_record(#state{protocol_buffers = #protocol_buffers{tls_packets = [], tls_cipher_texts = []}, -		   socket = Socket, +                   protocol_specific = #{active_n_toggle := true, active_n := N} = ProtocolSpec, + 		   socket = Socket,                     close_tag = CloseTag,  		   transport_cb = Transport} = State) -> -    case tls_socket:setopts(Transport, Socket, [{active,once}]) of -	ok -> -	    {no_record, State}; -	_ -> -            self() ! {CloseTag, Socket}, -	    {no_record, State} -    end; +     case tls_socket:setopts(Transport, Socket, [{active, N}]) of + 	ok -> +             {no_record, State#state{protocol_specific = ProtocolSpec#{active_n_toggle => false}}};  + 	_ -> +             self() ! {CloseTag, Socket}, +             {no_record, State} +     end;  next_record(State) ->      {no_record, State}.  next_event(StateName, Record, State) ->      next_event(StateName, Record, State, []). - -next_event(connection = StateName, no_record, State0, Actions) -> -    case next_record_if_active(State0) of -	{no_record, State} -> -	    ssl_connection:hibernate_after(StateName, State, Actions); -	{#ssl_tls{} = Record, State} -> -	    {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]}; -	{#alert{} = Alert, State} -> -	    {next_state, StateName, State, [{next_event, internal, Alert} | Actions]} +next_event(StateName, no_record, State0, Actions) -> +    case next_record(State0) of + 	{no_record, State} -> +            {next_state, StateName, State, Actions}; + 	{#ssl_tls{} = Record, State} -> + 	    {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]}; + 	{#alert{} = Alert, State} -> + 	    {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}      end;  next_event(StateName, Record, State, Actions) ->      case Record of @@ -207,22 +207,21 @@ handle_common_event(internal,  #ssl_tls{type = ?HANDSHAKE, fragment = Data},  				      ssl_options = Options} = State0) ->      try  	{Packets, Buf} = tls_handshake:get_tls_handshake(Version,Data,Buf0, Options), -	State1 = +	State =  	    State0#state{protocol_buffers =  			     Buffers#protocol_buffers{tls_handshake_buffer = Buf}},  	case Packets of              [] ->                   assert_buffer_sanity(Buf, Options), -                {Record, State} = next_record(State1), -                next_event(StateName, Record, State); +                next_event(StateName, no_record, State);              _ ->                                  Events = tls_handshake_events(Packets),                  case StateName of                      connection -> -                        ssl_connection:hibernate_after(StateName, State1, Events); +                        ssl_connection:hibernate_after(StateName, State, Events);                      _ ->                          {next_state, StateName,  -                         State1#state{unprocessed_handshake_events = unprocessed_events(Events)}, Events} +                         State#state{unprocessed_handshake_events = unprocessed_events(Events)}, Events}                  end          end      catch throw:#alert{} = Alert -> @@ -277,11 +276,10 @@ renegotiate(#state{role = server,      {BinMsg, ConnectionStates} =   	tls_record:encode_handshake(Frag, Version, ConnectionStates0),      send(Transport, Socket, BinMsg), -    State1 = State0#state{connection_states =  +    State = State0#state{connection_states =   			     ConnectionStates,  			 tls_handshake_history = Hs0}, -    {Record, State} = next_record(State1), -    next_event(hello, Record, State, Actions). +    next_event(hello, no_record, State, Actions).  send_handshake(Handshake, State) ->      send_handshake_flight(queue_handshake(Handshake, State)). @@ -411,23 +409,15 @@ protocol_name() ->  encode_data(Data, Version, ConnectionStates0)->      tls_record:encode_data(Data, Version, ConnectionStates0). -passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) ->  +passive_receive(#state{user_data_buffer = Buffer} = State0, StateName) ->       case Buffer of  	<<>> -> -	    {Record, State} = next_record(State0), -	    next_event(StateName, Record, State); +	    next_event(StateName, no_record, State0);  	_ ->  	    {Record, State} = ssl_connection:read_application_data(<<>>, State0), -	    next_event(StateName, Record, State) +            next_event(StateName, Record, State)      end. -next_record_if_active(State =  -		      #state{socket_options =  -			     #socket_options{active = false}}) ->  -    {no_record ,State}; -next_record_if_active(State) -> -    next_record(State). -  send(Transport, Socket, Data) ->     tls_socket:send(Transport, Socket, Data). @@ -469,15 +459,14 @@ init({call, From}, {start, Timeout},      {BinMsg, ConnectionStates, Handshake} =          encode_handshake(Hello,  HelloVersion, ConnectionStates0, Handshake0),      send(Transport, Socket, BinMsg), -    State1 = State0#state{connection_states = ConnectionStates, -			  negotiated_version = Version, %% Requested version -			  session = -			      Session0#session{session_id = Hello#client_hello.session_id}, -			  tls_handshake_history = Handshake, -			  start_or_recv_from = From, +    State = State0#state{connection_states = ConnectionStates, +                         negotiated_version = Version, %% Requested version +                         session = +                             Session0#session{session_id = Hello#client_hello.session_id}, +                         tls_handshake_history = Handshake, +                         start_or_recv_from = From,  			  timer = Timer}, -    {Record, State} = next_record(State1), -    next_event(hello, Record, State); +    next_event(hello, no_record, State);  init(Type, Event, State) ->      gen_handshake(?FUNCTION_NAME, Type, Event, State). @@ -612,36 +601,33 @@ connection(internal, #hello_request{},  		  connection_states = ConnectionStates} = State0) ->      Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts,  				       Cache, CacheCb, Renegotiation, Cert), -    {State1, Actions} = send_handshake(Hello, State0), -    {Record, State} = -	next_record( -	  State1#state{session = Session0#session{session_id -						  = Hello#client_hello.session_id}}), -    next_event(hello, Record, State, Actions); +    {State, Actions} = send_handshake(Hello, State0), +    next_event(hello, no_record, State#state{session = Session0#session{session_id +                                                                        = Hello#client_hello.session_id}}, Actions);  connection(internal, #client_hello{} = Hello,   	   #state{role = server, allow_renegotiate = true, connection_states = CS,                    %%protocol_cb = Connection,                    protocol_specific = #{sender := Sender} -                 } = State0) -> +                 } = State) ->      %% Mitigate Computational DoS attack      %% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html      %% http://www.thc.org/thc-ssl-dos/ Rather than disabling client      %% initiated renegotiation we will disallow many client initiated      %% renegotiations immediately after each other.      erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate), -    {Record, State} = next_record(State0#state{allow_renegotiate = false, -					       renegotiation = {true, peer}}),      {ok, Write} = tls_sender:renegotiate(Sender), -    next_event(hello, Record, State#state{connection_states = CS#{current_write => Write}},  +    next_event(hello, no_record, State#state{connection_states = CS#{current_write => Write}, +                                             allow_renegotiate = false, +                                             renegotiation = {true, peer} +                                            },                  [{next_event, internal, Hello}]);  connection(internal, #client_hello{},   	   #state{role = server, allow_renegotiate = false,                    protocol_cb = Connection} = State0) ->      Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION),      send_alert_in_connection(Alert, State0), -    State1 = Connection:reinit_handshake_data(State0), -    {Record, State} = next_record(State1), -    next_event(?FUNCTION_NAME, Record, State); +    State = Connection:reinit_handshake_data(State0), +    next_event(?FUNCTION_NAME, no_record, State);  connection(Type, Event, State) ->      ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). @@ -684,6 +670,13 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac  			 _  ->  			     ssl_session_cache  		     end, + +    InternalActiveN =  case application:get_env(ssl, internal_active_n) of +                           {ok, N} when is_integer(N) andalso (not IsErlDist) -> +                               N; +                           _  -> +                               ?INTERNAL_ACTIVE_N +                       end,      UserMonitor = erlang:monitor(process, User), @@ -710,7 +703,10 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac  	   protocol_cb = ?MODULE,  	   tracker = Tracker,  	   flight_buffer = [], -           protocol_specific = #{sender => Sender} +           protocol_specific = #{sender => Sender, +                                 active_n => InternalActiveN, +                                 active_n_toggle => true +                                }  	  }.  erl_dist_data(true) -> @@ -771,7 +767,8 @@ tls_handshake_events(Packets) ->  %% raw data from socket, upack records  handle_info({Protocol, _, Data}, StateName, -            #state{data_tag = Protocol} = State0) -> +            #state{data_tag = Protocol +                  } = State0) ->      case next_tls_record(Data, StateName, State0) of  	{Record, State} ->  	    next_event(StateName, Record, State); @@ -779,11 +776,16 @@ handle_info({Protocol, _, Data}, StateName,  	    ssl_connection:handle_normal_shutdown(Alert, StateName, State0),   	    ssl_connection:stop({shutdown, own_alert}, State0)      end; +handle_info({tcp_passive, Socket},  StateName, #state{socket = Socket, +                                                      protocol_specific = PS +                                                     } = State) -> +    next_event(StateName, no_record, State#state{protocol_specific = PS#{active_n_toggle => true}});  handle_info({CloseTag, Socket}, StateName,              #state{socket = Socket, close_tag = CloseTag,                     socket_options = #socket_options{active = Active},                     protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs},                     user_data_buffer = Buffer, +                   protocol_specific = PS,  		   negotiated_version = Version} = State) ->      %% Note that as of TLS 1.1, @@ -809,8 +811,9 @@ handle_info({CloseTag, Socket}, StateName,          true ->              %% Fixes non-delivery of final TLS record in {active, once}.              %% Basically allows the application the opportunity to set {active, once} again -            %% and then receive the final message. -            next_event(StateName, no_record, State) +            %% and then receive the final message. Set internal active_n to zero  +            %% to ensure socket close message is sent if there is not enough data to deliver. +            next_event(StateName, no_record, State#state{protocol_specific = PS#{active_n_toggle => true}})      end;  handle_info({'EXIT', Sender, Reason}, _,              #state{protocol_specific = #{sender := Sender}} = State) -> diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 6f668f0c00..d93bd85c76 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -1097,16 +1097,19 @@ tls_closed_in_active_once(Config) when is_list(Config) ->      end.  tls_closed_in_active_once_loop(Socket) -> -    ssl:setopts(Socket, [{active, once}]), -    receive -	{ssl, Socket, _} -> -	    tls_closed_in_active_once_loop(Socket); -	{ssl_closed, Socket} -> -	    ok -    after 5000 -> -	      no_ssl_closed_received +    case ssl:setopts(Socket, [{active, once}]) of +        ok -> +            receive +                {ssl, Socket, _} -> +                    tls_closed_in_active_once_loop(Socket); +                {ssl_closed, Socket} -> +                    ok +            after 5000 -> +                    no_ssl_closed_received +            end; +        {error, closed} -> +            ok      end. -  %%--------------------------------------------------------------------  connect_dist() ->      [{doc,"Test a simple connect as is used by distribution"}].  | 
