From d87ac1c55188f5ba5cdf72384125d94d42118c18 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Fri, 15 Jun 2018 22:32:59 +0200 Subject: ssl: Add new sender process for TLS state machine Separate sending and receiving when using TCP as transport as prim_inet:send may block which in turn may result in a deadlock between two Erlang processes communicating over TLS, this is especially likely to happen when running Erlang distribution over TLS. --- lib/ssl/src/Makefile | 1 + lib/ssl/src/dtls_connection.erl | 67 +++++++++- lib/ssl/src/dtls_socket.erl | 10 +- lib/ssl/src/ssl.app.src | 1 + lib/ssl/src/ssl.erl | 52 +++++--- lib/ssl/src/ssl_connection.erl | 277 +++++++++++++++----------------------- lib/ssl/src/ssl_connection.hrl | 1 + lib/ssl/src/tls_connection.erl | 159 +++++++++++++++------- lib/ssl/src/tls_sender.erl | 281 +++++++++++++++++++++++++++++++++++++++ lib/ssl/src/tls_socket.erl | 9 +- lib/ssl/test/ssl_basic_SUITE.erl | 25 ++-- 11 files changed, 616 insertions(+), 267 deletions(-) create mode 100644 lib/ssl/src/tls_sender.erl (limited to 'lib') diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile index c0c55c6eb7..8d1341f594 100644 --- a/lib/ssl/src/Makefile +++ b/lib/ssl/src/Makefile @@ -66,6 +66,7 @@ MODULES= \ ssl_srp_primes \ tls_connection \ dtls_connection \ + tls_sender\ ssl_config \ ssl_connection \ tls_handshake \ diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index bf3ff3a9a7..2a0b2b317d 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -36,7 +36,7 @@ %% Internal application API %% Setup --export([start_fsm/8, start_link/7, init/1]). +-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]). @@ -44,10 +44,10 @@ %% Handshake handling -export([renegotiate/2, send_handshake/2, queue_handshake/2, queue_change_cipher/2, - reinit_handshake_data/1, select_sni_extension/1, empty_connection_state/2]). + reinit/1, reinit_handshake_data/1, select_sni_extension/1, empty_connection_state/2]). %% Alert and close handling --export([encode_alert/3,send_alert/2, close/5, protocol_name/0]). +-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, @@ -72,7 +72,7 @@ start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_, Tracker} try {ok, Pid} = dtls_connection_sup:start_child([Role, Host, Port, Socket, Opts, User, CbInfo]), - {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, Pid, CbModule, Tracker), + {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid], CbModule, Tracker), ssl_connection:handshake(SslSocket, Timeout) catch error:{badmatch, {error, _} = Error} -> @@ -100,6 +100,10 @@ init([Role, Host, Port, Socket, Options, User, CbInfo]) -> EState = State0#state{protocol_specific = Map#{error => Error}}, gen_statem:enter_loop(?MODULE, [], error, EState) end. + +pids(_) -> + [self()]. + %%==================================================================== %% State transition handling %%==================================================================== @@ -328,10 +332,14 @@ queue_change_cipher(ChangeCipher, #state{flight_buffer = Flight, dtls_record:next_epoch(ConnectionStates0, write), State#state{flight_buffer = Flight#{change_cipher_spec => ChangeCipher}, connection_states = ConnectionStates}. + +reinit(State) -> + %% To be API compatible with TLS NOOP here + reinit_handshake_data(State). reinit_handshake_data(#state{protocol_buffers = Buffers} = State) -> State#state{premaster_secret = undefined, public_key_info = undefined, - tls_handshake_history = ssl_handshake:init_handshake_history(), + tls_handshake_history = ssl_handshake:init_handshake_history(), flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}, flight_buffer = new_flight(), protocol_buffers = @@ -365,6 +373,10 @@ send_alert(Alert, #state{negotiated_version = Version, send(Transport, Socket, BinMsg), State0#state{connection_states = ConnectionStates}. +send_alert_in_connection(Alert, State) -> + _ = send_alert(Alert, State), + ok. + close(downgrade, _,_,_,_) -> ok; %% Other @@ -710,6 +722,12 @@ connection(internal, #client_hello{}, #state{role = server, allow_renegotiate = State1 = send_alert(Alert, State0), {Record, State} = ssl_connection:prepare_connection(State1, ?MODULE), next_event(?FUNCTION_NAME, Record, State); +connection({call, From}, {application_data, Data}, State) -> + try + send_application_data(Data, From, ?FUNCTION_NAME, State) + catch throw:Error -> + ssl_connection:hibernate_after(?FUNCTION_NAME, State, [{reply, From, Error}]) + end; connection(Type, Event, State) -> ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). @@ -1131,3 +1149,42 @@ log_ignore_alert(true, StateName, Alert, Role) -> [Role, StateName, Txt]); log_ignore_alert(false, _, _,_) -> ok. + +send_application_data(Data, From, _StateName, + #state{socket = Socket, + negotiated_version = Version, + protocol_cb = Connection, + transport_cb = Transport, + connection_states = ConnectionStates0, + ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}} = State0) -> + + case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of + true -> + renegotiate(State0#state{renegotiation = {true, internal}}, + [{next_event, {call, From}, {application_data, Data}}]); + false -> + {Msgs, ConnectionStates} = + Connection:encode_data(Data, Version, ConnectionStates0), + State = State0#state{connection_states = ConnectionStates}, + case Connection:send(Transport, Socket, Msgs) of + ok -> + ssl_connection:hibernate_after(connection, State, [{reply, From, ok}]); + Result -> + ssl_connection:hibernate_after(connection, State, [{reply, From, Result}]) + end + end. + +time_to_renegotiate(_Data, + #{current_write := #{sequence_number := Num}}, + RenegotiateAt) -> + + %% We could do test: + %% is_time_to_renegotiate((erlang:byte_size(_Data) div + %% ?MAX_PLAIN_TEXT_LENGTH) + 1, RenegotiateAt), but we chose to + %% have a some what lower renegotiateAt and a much cheaper test + is_time_to_renegotiate(Num, RenegotiateAt). + +is_time_to_renegotiate(N, M) when N < M-> + false; +is_time_to_renegotiate(_,_) -> + true. diff --git a/lib/ssl/src/dtls_socket.erl b/lib/ssl/src/dtls_socket.erl index b26d3ae41a..2001afd02f 100644 --- a/lib/ssl/src/dtls_socket.erl +++ b/lib/ssl/src/dtls_socket.erl @@ -48,7 +48,7 @@ accept(dtls, #config{transport_info = {Transport,_,_,_}, dtls_handler = {Listner, _}}, _Timeout) -> case dtls_packet_demux:accept(Listner, self()) of {ok, Pid, Socket} -> - {ok, socket(Pid, Transport, {Listner, Socket}, ConnectionCb)}; + {ok, socket([Pid], Transport, {Listner, Socket}, ConnectionCb)}; {error, Reason} -> {error, Reason} end. @@ -73,12 +73,12 @@ close(gen_udp, {_Client, _Socket}) -> close(Transport, {_Client, Socket}) -> Transport:close(Socket). -socket(Pid, gen_udp = Transport, {{_, _}, Socket}, ConnectionCb) -> - #sslsocket{pid = Pid, +socket(Pids, gen_udp = Transport, {{_, _}, Socket}, ConnectionCb) -> + #sslsocket{pid = Pids, %% "The name "fd" is keept for backwards compatibility fd = {Transport, Socket, ConnectionCb}}; -socket(Pid, Transport, Socket, ConnectionCb) -> - #sslsocket{pid = Pid, +socket(Pids, Transport, Socket, ConnectionCb) -> + #sslsocket{pid = Pids, %% "The name "fd" is keept for backwards compatibility fd = {Transport, Socket, ConnectionCb}}. setopts(_, #sslsocket{pid = {dtls, #config{dtls_handler = {ListenPid, _}}}}, Options) -> diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src index 41871260fa..936df12e70 100644 --- a/lib/ssl/src/ssl.app.src +++ b/lib/ssl/src/ssl.app.src @@ -10,6 +10,7 @@ tls_v1, ssl_v3, tls_connection_sup, + tls_sender, %% DTLS dtls_connection, dtls_handshake, diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 71d1a28f98..4cf56035ba 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -231,7 +231,7 @@ handshake(#sslsocket{fd = {_, _, _, Tracker}} = Socket, SslOpts, Timeout) when catch Error = {error, _Reason} -> Error end; -handshake(#sslsocket{pid = Pid, fd = {_, _, _}} = Socket, SslOpts, Timeout) when +handshake(#sslsocket{pid = [Pid|_], fd = {_, _, _}} = Socket, SslOpts, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)-> try {ok, EmOpts, _} = dtls_packet_demux:get_all_opts(Pid), @@ -291,7 +291,7 @@ handshake_cancel(Socket) -> %% %% Description: Close an ssl connection %%-------------------------------------------------------------------- -close(#sslsocket{pid = Pid}) when is_pid(Pid) -> +close(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) -> ssl_connection:close(Pid, {close, ?DEFAULT_TIMEOUT}); close(#sslsocket{pid = {dtls, #config{dtls_handler = {Pid, _}}}}) -> dtls_packet_demux:close(Pid); @@ -303,12 +303,12 @@ close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_, _, _} %% %% Description: Close an ssl connection %%-------------------------------------------------------------------- -close(#sslsocket{pid = TLSPid}, +close(#sslsocket{pid = [TLSPid|_]}, {Pid, Timeout} = DownGrade) when is_pid(TLSPid), is_pid(Pid), (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> ssl_connection:close(TLSPid, {close, DownGrade}); -close(#sslsocket{pid = TLSPid}, Timeout) when is_pid(TLSPid), +close(#sslsocket{pid = [TLSPid|_]}, Timeout) when is_pid(TLSPid), (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> ssl_connection:close(TLSPid, {close, Timeout}); close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_, _, _}}}}, _) -> @@ -319,8 +319,10 @@ close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_, _, _} %% %% Description: Sends data over the ssl connection %%-------------------------------------------------------------------- -send(#sslsocket{pid = Pid}, Data) when is_pid(Pid) -> +send(#sslsocket{pid = [Pid]}, Data) when is_pid(Pid) -> ssl_connection:send(Pid, Data); +send(#sslsocket{pid = [_, Pid]}, Data) when is_pid(Pid) -> + tls_sender:send_data(Pid, erlang:iolist_to_binary(Data)); send(#sslsocket{pid = {_, #config{transport_info={_, udp, _, _}}}}, _) -> {error,enotconn}; %% Emulate connection behaviour send(#sslsocket{pid = {dtls,_}}, _) -> @@ -336,7 +338,7 @@ send(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport, _, _, _} %%-------------------------------------------------------------------- recv(Socket, Length) -> recv(Socket, Length, infinity). -recv(#sslsocket{pid = Pid}, Length, Timeout) when is_pid(Pid), +recv(#sslsocket{pid = [Pid|_]}, Length, Timeout) when is_pid(Pid), (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)-> ssl_connection:recv(Pid, Length, Timeout); recv(#sslsocket{pid = {dtls,_}}, _, _) -> @@ -351,7 +353,7 @@ recv(#sslsocket{pid = {Listen, %% Description: Changes process that receives the messages when active = true %% or once. %%-------------------------------------------------------------------- -controlling_process(#sslsocket{pid = Pid}, NewOwner) when is_pid(Pid), is_pid(NewOwner) -> +controlling_process(#sslsocket{pid = [Pid|_]}, NewOwner) when is_pid(Pid), is_pid(NewOwner) -> ssl_connection:new_user(Pid, NewOwner); controlling_process(#sslsocket{pid = {dtls, _}}, NewOwner) when is_pid(NewOwner) -> @@ -369,7 +371,7 @@ controlling_process(#sslsocket{pid = {Listen, %% %% Description: Return SSL information for the connection %%-------------------------------------------------------------------- -connection_information(#sslsocket{pid = Pid}) when is_pid(Pid) -> +connection_information(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) -> case ssl_connection:connection_information(Pid, false) of {ok, Info} -> {ok, [Item || Item = {_Key, Value} <- Info, Value =/= undefined]}; @@ -386,7 +388,7 @@ connection_information(#sslsocket{pid = {dtls,_}}) -> %% %% Description: Return SSL information for the connection %%-------------------------------------------------------------------- -connection_information(#sslsocket{pid = Pid}, Items) when is_pid(Pid) -> +connection_information(#sslsocket{pid = [Pid|_]}, Items) when is_pid(Pid) -> case ssl_connection:connection_information(Pid, include_security_info(Items)) of {ok, Info} -> {ok, [Item || Item = {Key, Value} <- Info, lists:member(Key, Items), @@ -400,9 +402,9 @@ connection_information(#sslsocket{pid = Pid}, Items) when is_pid(Pid) -> %% %% Description: same as inet:peername/1. %%-------------------------------------------------------------------- -peername(#sslsocket{pid = Pid, fd = {Transport, Socket, _}}) when is_pid(Pid)-> +peername(#sslsocket{pid = [Pid|_], fd = {Transport, Socket, _}}) when is_pid(Pid)-> dtls_socket:peername(Transport, Socket); -peername(#sslsocket{pid = Pid, fd = {Transport, Socket, _, _}}) when is_pid(Pid)-> +peername(#sslsocket{pid = [Pid|_], fd = {Transport, Socket, _, _}}) when is_pid(Pid)-> tls_socket:peername(Transport, Socket); peername(#sslsocket{pid = {dtls, #config{dtls_handler = {_Pid, _}}}}) -> dtls_socket:peername(dtls, undefined); @@ -416,7 +418,7 @@ peername(#sslsocket{pid = {dtls,_}}) -> %% %% Description: Returns the peercert. %%-------------------------------------------------------------------- -peercert(#sslsocket{pid = Pid}) when is_pid(Pid) -> +peercert(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) -> case ssl_connection:peer_certificate(Pid) of {ok, undefined} -> {error, no_peercert}; @@ -434,7 +436,7 @@ peercert(#sslsocket{pid = {Listen, _}}) when is_port(Listen) -> %% Description: Returns the protocol that has been negotiated. If no %% protocol has been negotiated will return {error, protocol_not_negotiated} %%-------------------------------------------------------------------- -negotiated_protocol(#sslsocket{pid = Pid}) -> +negotiated_protocol(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) -> ssl_connection:negotiated_protocol(Pid). %%-------------------------------------------------------------------- @@ -571,7 +573,7 @@ eccs_filter_supported(Curves) -> %% %% Description: Gets options %%-------------------------------------------------------------------- -getopts(#sslsocket{pid = Pid}, OptionTags) when is_pid(Pid), is_list(OptionTags) -> +getopts(#sslsocket{pid = [Pid|_]}, OptionTags) when is_pid(Pid), is_list(OptionTags) -> ssl_connection:get_opts(Pid, OptionTags); getopts(#sslsocket{pid = {dtls, #config{transport_info = {Transport,_,_,_}}}} = ListenSocket, OptionTags) when is_list(OptionTags) -> try dtls_socket:getopts(Transport, ListenSocket, OptionTags) of @@ -602,7 +604,7 @@ getopts(#sslsocket{}, OptionTags) -> %% %% Description: Sets options %%-------------------------------------------------------------------- -setopts(#sslsocket{pid = Pid}, Options0) when is_pid(Pid), is_list(Options0) -> +setopts(#sslsocket{pid = [Pid|_]}, Options0) when is_pid(Pid), is_list(Options0) -> try proplists:expand([{binary, [{mode, binary}]}, {list, [{mode, list}]}], Options0) of Options -> @@ -657,7 +659,7 @@ getstat(Socket) -> getstat(#sslsocket{pid = {Listen, #config{transport_info = {Transport, _, _, _}}}}, Options) when is_port(Listen), is_list(Options) -> tls_socket:getstat(Transport, Listen, Options); -getstat(#sslsocket{pid = Pid, fd = {Transport, Socket, _, _}}, Options) when is_pid(Pid), is_list(Options) -> +getstat(#sslsocket{pid = [Pid|_], fd = {Transport, Socket, _, _}}, Options) when is_pid(Pid), is_list(Options) -> tls_socket:getstat(Transport, Socket, Options). %%--------------------------------------------------------------- @@ -670,7 +672,7 @@ shutdown(#sslsocket{pid = {Listen, #config{transport_info = {Transport,_, _, _}} Transport:shutdown(Listen, How); shutdown(#sslsocket{pid = {dtls,_}},_) -> {error, enotconn}; -shutdown(#sslsocket{pid = Pid}, How) -> +shutdown(#sslsocket{pid = [Pid|_]}, How) when is_pid(Pid) -> ssl_connection:shutdown(Pid, How). %%-------------------------------------------------------------------- @@ -682,9 +684,9 @@ sockname(#sslsocket{pid = {Listen, #config{transport_info = {Transport, _, _, _ tls_socket:sockname(Transport, Listen); sockname(#sslsocket{pid = {dtls, #config{dtls_handler = {Pid, _}}}}) -> dtls_packet_demux:sockname(Pid); -sockname(#sslsocket{pid = Pid, fd = {Transport, Socket, _}}) when is_pid(Pid) -> +sockname(#sslsocket{pid = [Pid|_], fd = {Transport, Socket, _}}) when is_pid(Pid) -> dtls_socket:sockname(Transport, Socket); -sockname(#sslsocket{pid = Pid, fd = {Transport, Socket, _, _}}) when is_pid(Pid) -> +sockname(#sslsocket{pid = [Pid| _], fd = {Transport, Socket, _, _}}) when is_pid(Pid) -> tls_socket:sockname(Transport, Socket). %%--------------------------------------------------------------- @@ -713,7 +715,15 @@ versions() -> %% %% Description: Initiates a renegotiation. %%-------------------------------------------------------------------- -renegotiate(#sslsocket{pid = Pid}) when is_pid(Pid) -> +renegotiate(#sslsocket{pid = [Pid, Sender |_]}) when is_pid(Pid), + is_pid(Sender) -> + case tls_sender:renegotiate(Sender) of + {ok, Write} -> + tls_connection:renegotiation(Pid, Write); + Error -> + Error + end; +renegotiate(#sslsocket{pid = [Pid |_]}) when is_pid(Pid) -> ssl_connection:renegotiation(Pid); renegotiate(#sslsocket{pid = {dtls,_}}) -> {error, enotconn}; @@ -727,7 +737,7 @@ renegotiate(#sslsocket{pid = {Listen,_}}) when is_port(Listen) -> %% %% Description: use a ssl sessions TLS PRF to generate key material %%-------------------------------------------------------------------- -prf(#sslsocket{pid = Pid}, +prf(#sslsocket{pid = [Pid|_]}, Secret, Label, Seed, WantedLength) when is_pid(Pid) -> ssl_connection:prf(Pid, Secret, Label, Seed, WantedLength); prf(#sslsocket{pid = {dtls,_}}, _,_,_,_) -> diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 7649ed2899..d83cc7225f 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -55,7 +55,7 @@ ]). %% Data handling --export([write_application_data/3, read_application_data/2]). +-export([read_application_data/2, internal_renegotiation/2]). %% Help functions for tls|dtls_connection.erl -export([handle_session/7, ssl_config/3, @@ -118,7 +118,7 @@ handshake(Connection, Port, Socket, Opts, User, CbInfo, Timeout) -> %% %% Description: Starts ssl handshake. %%-------------------------------------------------------------------- -handshake(#sslsocket{pid = Pid} = Socket, Timeout) -> +handshake(#sslsocket{pid = [Pid|_]} = Socket, Timeout) -> case call(Pid, {start, Timeout}) of connected -> {ok, Socket}; @@ -134,7 +134,7 @@ handshake(#sslsocket{pid = Pid} = Socket, Timeout) -> %% %% Description: Starts ssl handshake with some new options %%-------------------------------------------------------------------- -handshake(#sslsocket{pid = Pid} = Socket, SslOptions, Timeout) -> +handshake(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) -> case call(Pid, {start, SslOptions, Timeout}) of connected -> {ok, Socket}; @@ -148,7 +148,7 @@ handshake(#sslsocket{pid = Pid} = Socket, SslOptions, Timeout) -> %% %% Description: Continues handshake with new options %%-------------------------------------------------------------------- -handshake_continue(#sslsocket{pid = Pid} = Socket, SslOptions, Timeout) -> +handshake_continue(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) -> case call(Pid, {handshake_continue, SslOptions, Timeout}) of connected -> {ok, Socket}; @@ -160,7 +160,7 @@ handshake_continue(#sslsocket{pid = Pid} = Socket, SslOptions, Timeout) -> %% %% Description: Cancels connection %%-------------------------------------------------------------------- -handshake_cancel(#sslsocket{pid = Pid}) -> +handshake_cancel(#sslsocket{pid = [Pid|_]}) -> case call(Pid, cancel) of closed -> ok; @@ -168,7 +168,7 @@ handshake_cancel(#sslsocket{pid = Pid}) -> Error end. %-------------------------------------------------------------------- --spec socket_control(tls_connection | dtls_connection, port(), pid(), atom()) -> +-spec socket_control(tls_connection | dtls_connection, port(), [pid()], atom()) -> {ok, #sslsocket{}} | {error, reason()}. %% %% Description: Set the ssl process to own the accept socket @@ -177,24 +177,24 @@ socket_control(Connection, Socket, Pid, Transport) -> socket_control(Connection, Socket, Pid, Transport, undefined). %-------------------------------------------------------------------- --spec socket_control(tls_connection | dtls_connection, port(), pid(), atom(), pid()| undefined) -> +-spec socket_control(tls_connection | dtls_connection, port(), [pid()], atom(), pid()| atom()) -> {ok, #sslsocket{}} | {error, reason()}. %%-------------------------------------------------------------------- -socket_control(Connection, Socket, Pid, Transport, udp_listener) -> +socket_control(Connection, Socket, Pids, Transport, udp_listener) -> %% dtls listener process must have the socket control - {ok, Connection:socket(Pid, Transport, Socket, Connection, undefined)}; + {ok, Connection:socket(Pids, Transport, Socket, Connection, undefined)}; -socket_control(tls_connection = Connection, Socket, Pid, Transport, ListenTracker) -> +socket_control(tls_connection = Connection, Socket, [Pid|_] = Pids, Transport, ListenTracker) -> case Transport:controlling_process(Socket, Pid) of ok -> - {ok, Connection:socket(Pid, Transport, Socket, Connection, ListenTracker)}; + {ok, Connection:socket(Pids, Transport, Socket, Connection, ListenTracker)}; {error, Reason} -> {error, Reason} end; -socket_control(dtls_connection = Connection, {_, Socket}, Pid, Transport, ListenTracker) -> +socket_control(dtls_connection = Connection, {_, Socket}, [Pid|_] = Pids, Transport, ListenTracker) -> case Transport:controlling_process(Socket, Pid) of ok -> - {ok, Connection:socket(Pid, Transport, Socket, Connection, ListenTracker)}; + {ok, Connection:socket(Pids, Transport, Socket, Connection, ListenTracker)}; {error, Reason} -> {error, Reason} end. @@ -306,6 +306,14 @@ peer_certificate(ConnectionPid) -> renegotiation(ConnectionPid) -> call(ConnectionPid, renegotiate). +%%-------------------------------------------------------------------- +-spec internal_renegotiation(pid(), ssl_record:connection_states()) -> + ok. +%% +%% Description: Starts a renegotiation of the ssl session. +%%-------------------------------------------------------------------- +internal_renegotiation(ConnectionPid, #{current_write := WriteState}) -> + gen_statem:cast(ConnectionPid, {internal_renegotiate, WriteState}). get_sslsocket(ConnectionPid) -> call(ConnectionPid, get_sslsocket). @@ -335,7 +343,7 @@ handle_own_alert(Alert, Version, StateName, ssl_options = SslOpts} = State) -> try %% Try to tell the other side {BinMsg, _} = - Connection:encode_alert(Alert, Version, ConnectionStates), + Connection:encode_alert(Alert, Version, ConnectionStates), Connection:send(Transport, Socket, BinMsg) catch _:_ -> %% Can crash if we are in a uninitialized state ignore @@ -353,8 +361,9 @@ handle_normal_shutdown(Alert, _, #state{socket = Socket, protocol_cb = Connection, start_or_recv_from = StartFrom, tracker = Tracker, - role = Role, renegotiation = {false, first}}) -> - alert_user(Transport, Tracker,Socket, StartFrom, Alert, Role, Connection); + role = Role, renegotiation = {false, first}} = State) -> + Pids = Connection:pids(State), + alert_user(Pids, Transport, Tracker,Socket, StartFrom, Alert, Role, Connection); handle_normal_shutdown(Alert, StateName, #state{socket = Socket, socket_options = Opts, @@ -362,8 +371,9 @@ handle_normal_shutdown(Alert, StateName, #state{socket = Socket, protocol_cb = Connection, user_application = {_Mon, Pid}, tracker = Tracker, - start_or_recv_from = RecvFrom, role = Role}) -> - alert_user(Transport, Tracker, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, Connection). + start_or_recv_from = RecvFrom, role = Role} = State) -> + Pids = Connection:pids(State), + alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, Connection). handle_alert(#alert{level = ?FATAL} = Alert, StateName, #state{socket = Socket, transport_cb = Transport, @@ -374,7 +384,8 @@ handle_alert(#alert{level = ?FATAL} = Alert, StateName, invalidate_session(Role, Host, Port, Session), log_alert(SslOpts#ssl_options.log_alert, Role, Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), - alert_user(Transport, Tracker, Socket, StateName, Opts, Pid, From, Alert, Role, Connection), + Pids = Connection:pids(State), + alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, From, Alert, Role, Connection), stop(normal, State); handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, @@ -383,12 +394,24 @@ handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, stop({shutdown, peer_close}, State); handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, - #state{role = Role, ssl_options = SslOpts, protocol_cb = Connection, renegotiation = {true, internal}} = State) -> + #state{role = Role, ssl_options = SslOpts, protocol_cb = Connection, + renegotiation = {true, internal}} = State) -> log_alert(SslOpts#ssl_options.log_alert, Role, Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), handle_normal_shutdown(Alert, StateName, State), stop({shutdown, peer_close}, State); +handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, connection = StateName, + #state{role = Role, + ssl_options = SslOpts, renegotiation = {true, From}, + protocol_cb = Connection} = State0) -> + 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); + handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, #state{role = Role, ssl_options = SslOpts, renegotiation = {true, From}, @@ -398,7 +421,7 @@ handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, gen_statem:reply(From, {error, renegotiation_rejected}), {Record, State1} = Connection:next_record(State0), %% Go back to connection! - State = Connection:reinit_handshake_data(State1#state{renegotiation = undefined}), + State = Connection:reinit(State1#state{renegotiation = undefined}), Connection:next_event(connection, Record, State); %% Gracefully log and ignore all other warning alerts @@ -412,36 +435,6 @@ handle_alert(#alert{level = ?WARNING} = Alert, StateName, %%==================================================================== %% Data handling %%==================================================================== -write_application_data(Data0, {FromPid, _} = From, - #state{socket = Socket, - negotiated_version = Version, - protocol_cb = Connection, - transport_cb = Transport, - connection_states = ConnectionStates0, - socket_options = SockOpts, - ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}} = State) -> - Data = encode_packet(Data0, SockOpts), - - case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of - true -> - Connection:renegotiate(State#state{renegotiation = {true, internal}}, - [{next_event, {call, From}, {application_data, Data0}}]); - false -> - {Msgs, ConnectionStates} = - Connection:encode_data(Data, Version, ConnectionStates0), - NewState = State#state{connection_states = ConnectionStates}, - case Connection:send(Transport, Socket, Msgs) of - ok when FromPid =:= self() -> - hibernate_after(connection, NewState, []); - Error when FromPid =:= self() -> - stop({shutdown, Error}, NewState); - ok -> - hibernate_after(connection, NewState, [{reply, From, ok}]); - Result -> - hibernate_after(connection, NewState, [{reply, From, Result}]) - end - end. - read_application_data(Data, #state{user_application = {_Mon, Pid}, socket = Socket, protocol_cb = Connection, @@ -481,9 +474,9 @@ read_application_data(Data, #state{user_application = {_Mon, Pid}, end; _ -> SocketOpt = - deliver_app_data( - Transport, Socket, SOpts, - ClientData, Pid, RecvFrom, Tracker, Connection), + deliver_app_data(Connection:pids(State0), + Transport, Socket, SOpts, + ClientData, Pid, RecvFrom, Tracker, Connection), cancel_timer(Timer), State = State0#state{ @@ -508,7 +501,8 @@ read_application_data(Data, #state{user_application = {_Mon, Pid}, {passive, Buffer} -> Connection:next_record_if_active(State0#state{user_data_buffer = Buffer}); {error,_Reason} -> %% Invalid packet in packet mode - deliver_packet_error(Transport, Socket, SOpts, Buffer1, Pid, RecvFrom, Tracker, Connection), + deliver_packet_error(Connection:pids(State0), + Transport, Socket, SOpts, Buffer1, Pid, RecvFrom, Tracker, Connection), stop(normal, State0) end. %%==================================================================== @@ -728,8 +722,8 @@ abbreviated(internal, #next_protocol{selected_protocol = SelectedProtocol}, Connection:next_event(?FUNCTION_NAME, Record, State#state{expecting_next_protocol_negotiation = false}); abbreviated(internal, - #change_cipher_spec{type = <<1>>}, #state{connection_states = ConnectionStates0} = - State0, Connection) -> + #change_cipher_spec{type = <<1>>}, + #state{connection_states = ConnectionStates0} = State0, Connection) -> ConnectionStates1 = ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection), {Record, State} = Connection:next_record(State0#state{connection_states = @@ -1025,22 +1019,6 @@ cipher(Type, Msg, State, Connection) -> #state{}, tls_connection | dtls_connection) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- -connection({call, {FromPid, _} = From}, {application_data, Data}, - #state{protocol_cb = Connection} = State, Connection) -> - %% We should look into having a worker process to do this to - %% parallize send and receive decoding and not block the receiver - %% if sending is overloading the socket. - try - write_application_data(Data, From, State) - catch throw:Error -> - case self() of - FromPid -> - stop({shutdown, Error}, State); - _ -> - hibernate_after( - ?FUNCTION_NAME, State, [{reply, From, Error}]) - end - end; connection({call, RecvFrom}, {recv, N, Timeout}, #state{protocol_cb = Connection, socket_options = #socket_options{active = false}} = State0, Connection) -> @@ -1067,13 +1045,10 @@ connection({call, From}, negotiated_protocol, #state{negotiated_protocol = SelectedProtocol} = State, _) -> hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, SelectedProtocol}}]); -connection( - {call, From}, {handshake_complete, _Node, DHandle}, - #state{ - ssl_options = #ssl_options{erl_dist = true}, - socket_options = SockOpts, - protocol_specific = ProtocolSpecific} = State, - Connection) -> +connection({call, From}, {handshake_complete, _Node, DHandle}, + #state{ssl_options = #ssl_options{erl_dist = true}, + socket_options = SockOpts, + protocol_specific = ProtocolSpecific} = State, Connection) -> %% From now on we execute on normal priority process_flag(priority, normal), try erlang:dist_ctrl_get_data_notification(DHandle) of @@ -1091,23 +1066,20 @@ connection( end; connection({call, From}, Msg, State, Connection) -> handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); -connection( - info, dist_data = Msg, - #state{ - ssl_options = #ssl_options{erl_dist = true}, - protocol_specific = #{d_handle := DHandle}} = State, - _) -> +connection(cast, {internal_renegotiate, WriteState}, #state{protocol_cb = Connection, + connection_states = ConnectionStates} + = State, Connection) -> + Connection:renegotiate(State#state{renegotiation = {true, internal}, + connection_states = ConnectionStates#{current_write => WriteState}}, []); +connection(info, dist_data = Msg, #state{ssl_options = #ssl_options{erl_dist = true}, + protocol_specific = #{d_handle := DHandle}} = State, _) -> eat_msgs(Msg), try send_dist_data(?FUNCTION_NAME, State, DHandle, []) catch error:_ -> death_row(State, disconnect) end; -connection( - info, {send, From, Ref, Data}, - #state{ - ssl_options = #ssl_options{erl_dist = true}, - protocol_specific = #{d_handle := _}}, - _) -> +connection(info, {send, From, Ref, Data}, #state{ssl_options = #ssl_options{erl_dist = true}, + protocol_specific = #{d_handle := _}}, _) -> %% This is for testing only! %% %% Needed by some OTP distribution @@ -1116,12 +1088,8 @@ connection( {keep_state_and_data, [{next_event, {call, {self(), undefined}}, {application_data, iolist_to_binary(Data)}}]}; -connection( - info, tick = Msg, - #state{ - ssl_options = #ssl_options{erl_dist = true}, - protocol_specific = #{d_handle := _}}, - _) -> +connection(info, tick = Msg, #state{ssl_options = #ssl_options{erl_dist = true}, + protocol_specific = #{d_handle := _}},_) -> eat_msgs(Msg), {keep_state_and_data, [{next_event, {call, {self(), undefined}}, {application_data, <<>>}}]}; @@ -1336,7 +1304,8 @@ handle_info({ErrorTag, Socket, econnaborted}, StateName, start_or_recv_from = StartFrom, role = Role, error_tag = ErrorTag, tracker = Tracker} = State) when StateName =/= connection -> - alert_user(Transport, Tracker,Socket, + Pids = Connection:pids(State), + alert_user(Pids, Transport, Tracker,Socket, StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, Connection), stop(normal, State); @@ -1422,15 +1391,14 @@ terminate({shutdown, own_alert}, _StateName, #state{%%send_queue = SendQueue, _ -> Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, undefined, undefined) end; -terminate(Reason, connection, #state{negotiated_version = Version, - protocol_cb = Connection, - connection_states = ConnectionStates0, +terminate(Reason, connection, #state{protocol_cb = Connection, + connection_states = ConnectionStates, ssl_options = #ssl_options{padding_check = Check}, transport_cb = Transport, socket = Socket } = State) -> handle_trusted_certs_db(State), - {BinAlert, ConnectionStates} = terminate_alert(Reason, Version, ConnectionStates0, Connection), - Connection:send(Transport, Socket, BinAlert), + Alert = terminate_alert(Reason), + ok = Connection:send_alert_in_connection(Alert, State), Connection:close(Reason, Socket, Transport, ConnectionStates, Check); terminate(Reason, _StateName, #state{transport_cb = Transport, protocol_cb = Connection, socket = Socket @@ -2377,18 +2345,13 @@ map_extensions(#hello_extensions{renegotiation_info = RenegotiationInfo, elliptic_curves => ssl_handshake:extension_value(ECCCurves), sni => ssl_handshake:extension_value(SNI)}. -terminate_alert(normal, Version, ConnectionStates, Connection) -> - Connection:encode_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), - Version, ConnectionStates); -terminate_alert({Reason, _}, Version, ConnectionStates, Connection) when Reason == close; - Reason == shutdown -> - Connection:encode_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), - Version, ConnectionStates); - -terminate_alert(_, Version, ConnectionStates, Connection) -> - {BinAlert, _} = Connection:encode_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR), - Version, ConnectionStates), - BinAlert. +terminate_alert(normal) -> + ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY); +terminate_alert({Reason, _}) when Reason == close; + Reason == shutdown -> + ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY); +terminate_alert(_) -> + ?ALERT_REC(?FATAL, ?INTERNAL_ERROR). handle_trusted_certs_db(#state{ssl_options = #ssl_options{cacertfile = <<>>, cacerts = []}}) -> @@ -2418,16 +2381,15 @@ prepare_connection(#state{renegotiation = Renegotiate, start_or_recv_from = RecvFrom} = State0, Connection) when Renegotiate =/= {false, first}, RecvFrom =/= undefined -> - State1 = Connection:reinit_handshake_data(State0), + State1 = Connection:reinit(State0), {Record, State} = Connection:next_record(State1), {Record, ack_connection(State)}; prepare_connection(State0, Connection) -> - State = Connection:reinit_handshake_data(State0), + State = Connection:reinit(State0), {no_record, ack_connection(State)}. -ack_connection(#state{renegotiation = {true, Initiater}} = State) - when Initiater == internal; - Initiater == peer -> +ack_connection(#state{renegotiation = {true, Initiater}} = State) when Initiater == peer; + Initiater == internal -> State#state{renegotiation = undefined}; ack_connection(#state{renegotiation = {true, From}} = State) -> gen_statem:reply(From, ok), @@ -2576,35 +2538,6 @@ handle_active_option(_, StateName0, To, Reply, #state{protocol_cb = Connection} end end. -encode_packet(Data, #socket_options{packet=Packet}) -> - case Packet of - 1 -> encode_size_packet(Data, 8, (1 bsl 8) - 1); - 2 -> encode_size_packet(Data, 16, (1 bsl 16) - 1); - 4 -> encode_size_packet(Data, 32, (1 bsl 32) - 1); - _ -> Data - end. - -encode_size_packet(Bin, Size, Max) -> - Len = erlang:byte_size(Bin), - case Len > Max of - true -> throw({error, {badarg, {packet_to_large, Len, Max}}}); - false -> <> - end. - -time_to_renegotiate(_Data, - #{current_write := #{sequence_number := Num}}, - RenegotiateAt) -> - - %% We could do test: - %% is_time_to_renegotiate((erlang:byte_size(_Data) div ?MAX_PLAIN_TEXT_LENGTH) + 1, RenegotiateAt), - %% but we chose to have a some what lower renegotiateAt and a much cheaper test - is_time_to_renegotiate(Num, RenegotiateAt). - -is_time_to_renegotiate(N, M) when N < M-> - false; -is_time_to_renegotiate(_,_) -> - true. - %% Picks ClientData get_data(_, _, <<>>) -> @@ -2651,9 +2584,10 @@ decode_packet(Type, Buffer, PacketOpts) -> %% Note that if the user has explicitly configured the socket to expect %% HTTP headers using the {packet, httph} option, we don't do any automatic %% switching of states. -deliver_app_data(Transport, Socket, SOpts = #socket_options{active=Active, packet=Type}, +deliver_app_data(CPids, Transport, Socket, SOpts = #socket_options{active=Active, packet=Type}, Data, Pid, From, Tracker, Connection) -> - send_or_reply(Active, Pid, From, format_reply(Transport, Socket, SOpts, Data, Tracker, Connection)), + send_or_reply(Active, Pid, From, + format_reply(CPids, Transport, Socket, SOpts, Data, Tracker, Connection)), SO = case Data of {P, _, _, _} when ((P =:= http_request) or (P =:= http_response)), ((Type =:= http) or (Type =:= http_bin)) -> @@ -2672,21 +2606,24 @@ deliver_app_data(Transport, Socket, SOpts = #socket_options{active=Active, packe SO end. -format_reply(_, _,#socket_options{active = false, mode = Mode, packet = Packet, +format_reply(_, _, _,#socket_options{active = false, mode = Mode, packet = Packet, header = Header}, Data, _, _) -> {ok, do_format_reply(Mode, Packet, Header, Data)}; -format_reply(Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet, +format_reply(CPids, Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet, header = Header}, Data, Tracker, Connection) -> - {ssl, Connection:socket(self(), Transport, Socket, Connection, Tracker), + {ssl, Connection:socket(CPids, Transport, Socket, Connection, Tracker), do_format_reply(Mode, Packet, Header, Data)}. -deliver_packet_error(Transport, Socket, SO= #socket_options{active = Active}, Data, Pid, From, Tracker, Connection) -> - send_or_reply(Active, Pid, From, format_packet_error(Transport, Socket, SO, Data, Tracker, Connection)). +deliver_packet_error(CPids, Transport, Socket, + SO= #socket_options{active = Active}, Data, Pid, From, Tracker, Connection) -> + send_or_reply(Active, Pid, From, format_packet_error(CPids, + Transport, Socket, SO, Data, Tracker, Connection)). -format_packet_error(_, _,#socket_options{active = false, mode = Mode}, Data, _, _) -> +format_packet_error(_, _, _,#socket_options{active = false, mode = Mode}, Data, _, _) -> {error, {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}; -format_packet_error(Transport, Socket, #socket_options{active = _, mode = Mode}, Data, Tracker, Connection) -> - {ssl_error, Connection:socket(self(), Transport, Socket, Connection, Tracker), +format_packet_error(CPids, Transport, Socket, #socket_options{active = _, mode = Mode}, + Data, Tracker, Connection) -> + {ssl_error, Connection:socket(CPids, Transport, Socket, Connection, Tracker), {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}. do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode @@ -2724,29 +2661,29 @@ send_user(Pid, Msg) -> Pid ! Msg, ok. -alert_user(Transport, Tracker, Socket, connection, Opts, Pid, From, Alert, Role, Connection) -> - alert_user(Transport, Tracker, Socket, Opts#socket_options.active, Pid, From, Alert, Role, Connection); -alert_user(Transport, Tracker, Socket,_, _, _, From, Alert, Role, Connection) -> - alert_user(Transport, Tracker, Socket, From, Alert, Role, Connection). +alert_user(Pids, Transport, Tracker, Socket, connection, Opts, Pid, From, Alert, Role, Connection) -> + alert_user(Pids, Transport, Tracker, Socket, Opts#socket_options.active, Pid, From, Alert, Role, Connection); +alert_user(Pids, Transport, Tracker, Socket,_, _, _, From, Alert, Role, Connection) -> + alert_user(Pids, Transport, Tracker, Socket, From, Alert, Role, Connection). -alert_user(Transport, Tracker, Socket, From, Alert, Role, Connection) -> - alert_user(Transport, Tracker, Socket, false, no_pid, From, Alert, Role, Connection). +alert_user(Pids, Transport, Tracker, Socket, From, Alert, Role, Connection) -> + alert_user(Pids, Transport, Tracker, Socket, false, no_pid, From, Alert, Role, Connection). -alert_user(_, _, _, false = Active, Pid, From, Alert, Role, _) when From =/= undefined -> +alert_user(_, _, _, _, false = Active, Pid, From, Alert, Role, _) when From =/= undefined -> %% If there is an outstanding ssl_accept | recv %% From will be defined and send_or_reply will %% send the appropriate error message. ReasonCode = ssl_alert:reason_code(Alert, Role), send_or_reply(Active, Pid, From, {error, ReasonCode}); -alert_user(Transport, Tracker, Socket, Active, Pid, From, Alert, Role, Connection) -> +alert_user(Pids, Transport, Tracker, Socket, Active, Pid, From, Alert, Role, Connection) -> case ssl_alert:reason_code(Alert, Role) of closed -> send_or_reply(Active, Pid, From, - {ssl_closed, Connection:socket(self(), + {ssl_closed, Connection:socket(Pids, Transport, Socket, Connection, Tracker)}); ReasonCode -> send_or_reply(Active, Pid, From, - {ssl_error, Connection:socket(self(), + {ssl_error, Connection:socket(Pids, Transport, Socket, Connection, Tracker), ReasonCode}) end. diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl index 9cef0c9605..504a141a4a 100644 --- a/lib/ssl/src/ssl_connection.hrl +++ b/lib/ssl/src/ssl_connection.hrl @@ -44,6 +44,7 @@ host :: string() | inet:ip_address(), port :: integer(), socket :: port() | tuple(), %% TODO: dtls socket + sender :: pid() | undefined, ssl_options :: #ssl_options{}, socket_options :: #socket_options{}, connection_states :: ssl_record:connection_states() | secret_printout(), diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 4d1122f804..ef52421523 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -43,22 +43,24 @@ %% Internal application API %% Setup --export([start_fsm/8, start_link/7, init/1]). +-export([start_fsm/8, start_link/8, init/1, pids/1]). %% State transition handling --export([next_record/1, next_event/3, next_event/4, handle_common_event/4]). +-export([next_record/1, next_event/3, next_event/4, + handle_common_event/4]). %% Handshake handling --export([renegotiate/2, send_handshake/2, +-export([renegotiation/2, renegotiate/2, send_handshake/2, queue_handshake/2, queue_change_cipher/2, - reinit_handshake_data/1, select_sni_extension/1, empty_connection_state/2]). + reinit/1, reinit_handshake_data/1, select_sni_extension/1, + empty_connection_state/2]). %% Alert and close handling --export([encode_alert/3, send_alert/2, close/5, protocol_name/0]). +-export([send_alert/2, send_alert_in_connection/2, encode_alert/3, close/5, protocol_name/0]). %% Data handling --export([encode_data/3, passive_receive/2, next_record_if_active/1, send/3, - socket/5, setopts/3, getopts/3]). +-export([encode_data/3, passive_receive/2, next_record_if_active/1, + send/3, socket/5, setopts/3, getopts/3]). %% gen_statem state functions -export([init/3, error/3, downgrade/3, %% Initiation and take down states @@ -77,9 +79,10 @@ start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_, Tracker} User, {CbModule, _,_, _} = CbInfo, Timeout) -> try - {ok, Pid} = tls_connection_sup:start_child([Role, Host, Port, Socket, + {ok, Sender} = tls_sender:start(), + {ok, Pid} = tls_connection_sup:start_child([Role, Sender, Host, Port, Socket, Opts, User, CbInfo]), - {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, Pid, CbModule, Tracker), + {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Tracker), ssl_connection:handshake(SslSocket, Timeout) catch error:{badmatch, {error, _} = Error} -> @@ -90,9 +93,10 @@ start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_, Tracker} = User, {CbModule, _,_, _} = CbInfo, Timeout) -> try - {ok, Pid} = tls_connection_sup:start_child_dist([Role, Host, Port, Socket, + {ok, Sender} = tls_sender:start(), + {ok, Pid} = tls_connection_sup:start_child_dist([Role, Sender, Host, Port, Socket, Opts, User, CbInfo]), - {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, Pid, CbModule, Tracker), + {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Tracker), ssl_connection:handshake(SslSocket, Timeout) catch error:{badmatch, {error, _} = Error} -> @@ -100,25 +104,31 @@ start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_, Tracker} = end. %%-------------------------------------------------------------------- --spec start_link(atom(), host(), inet:port_number(), port(), list(), pid(), tuple()) -> +-spec start_link(atom(), pid(), host(), inet:port_number(), port(), list(), pid(), tuple()) -> {ok, pid()} | ignore | {error, reason()}. %% %% Description: Creates a gen_statem process which calls Module:init/1 to %% initialize. %%-------------------------------------------------------------------- -start_link(Role, Host, Port, Socket, Options, User, CbInfo) -> - {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]])}. +start_link(Role, Sender, Host, Port, Socket, Options, User, CbInfo) -> + {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Sender, Host, Port, Socket, Options, User, CbInfo]])}. -init([Role, Host, Port, Socket, Options, User, CbInfo]) -> +init([Role, Sender, Host, Port, Socket, Options, User, CbInfo]) -> process_flag(trap_exit, true), - State0 = #state{protocol_specific = Map} = initial_state(Role, Host, Port, Socket, Options, User, CbInfo), + State0 = #state{protocol_specific = Map} = initial_state(Role, Sender, + Host, Port, Socket, Options, User, CbInfo), try State = ssl_connection:ssl_config(State0#state.ssl_options, Role, State0), - gen_statem:enter_loop(?MODULE, [], init, State) + initialize_tls_sender(State), + gen_statem:enter_loop(?MODULE, [], init, State) catch throw:Error -> EState = State0#state{protocol_specific = Map#{error => Error}}, gen_statem:enter_loop(?MODULE, [], error, EState) end. + +pids(#state{protocol_specific = #{sender := {_, Sender}}}) -> + [self(), Sender]. + %%==================================================================== %% State transition handling %%==================================================================== @@ -235,13 +245,15 @@ handle_common_event(internal, #ssl_tls{type = _Unknown}, StateName, State) -> %%==================================================================== %% Handshake handling %%==================================================================== +renegotiation(Pid, WriteState) -> + gen_statem:call(Pid, {user_renegotiate, WriteState}). + renegotiate(#state{role = client} = State, Actions) -> %% Handle same way as if server requested %% the renegotiation Hs0 = ssl_handshake:init_handshake_history(), {next_state, connection, State#state{tls_handshake_history = Hs0}, [{next_event, internal, #hello_request{}} | Actions]}; - renegotiate(#state{role = server, socket = Socket, transport_cb = Transport, @@ -286,6 +298,12 @@ queue_change_cipher(Msg, #state{negotiated_version = Version, State0#state{connection_states = ConnectionStates, flight_buffer = Flight0 ++ [BinChangeCipher]}. +reinit(#state{protocol_specific = #{sender := {_,Sender}}, + negotiated_version = Version, + connection_states = #{current_write := Write}} = State) -> + tls_sender:update_connection_state(Sender, Write, Version), + reinit_handshake_data(State). + reinit_handshake_data(State) -> %% premaster_secret, public_key_info and tls_handshake_info %% are only needed during the handshake phase. @@ -307,15 +325,6 @@ empty_connection_state(ConnectionEnd, BeastMitigation) -> %%==================================================================== %% Alert and close handling %%==================================================================== -send_alert(Alert, #state{negotiated_version = Version, - socket = Socket, - transport_cb = Transport, - connection_states = ConnectionStates0} = State0) -> - {BinMsg, ConnectionStates} = - encode_alert(Alert, Version, ConnectionStates0), - send(Transport, Socket, BinMsg), - State0#state{connection_states = ConnectionStates}. - %%-------------------------------------------------------------------- -spec encode_alert(#alert{}, ssl_record:ssl_version(), ssl_record:connection_states()) -> {iolist(), ssl_record:connection_states()}. @@ -324,6 +333,20 @@ send_alert(Alert, #state{negotiated_version = Version, %%-------------------------------------------------------------------- encode_alert(#alert{} = Alert, Version, ConnectionStates) -> tls_record:encode_alert_record(Alert, Version, ConnectionStates). + +send_alert(Alert, #state{negotiated_version = Version, + socket = Socket, + protocol_cb = Connection, + transport_cb = Transport, + connection_states = ConnectionStates0} = StateData0) -> + {BinMsg, ConnectionStates} = + Connection:encode_alert(Alert, Version, ConnectionStates0), + Connection:send(Transport, Socket, BinMsg), + StateData0#state{connection_states = ConnectionStates}. + +send_alert_in_connection(Alert, #state{protocol_specific = #{sender := {_, Sender}}}) -> + tls_sender:send_alert(Sender, Alert). + %% User closes or recursive call! close({close, Timeout}, Socket, Transport = gen_tcp, _,_) -> tls_socket:setopts(Transport, Socket, [{active, false}]), @@ -378,8 +401,8 @@ next_record_if_active(State) -> send(Transport, Socket, Data) -> tls_socket:send(Transport, Socket, Data). -socket(Pid, Transport, Socket, Connection, Tracker) -> - tls_socket:socket(Pid, Transport, Socket, Connection, Tracker). +socket(Pids, Transport, Socket, Connection, Tracker) -> + tls_socket:socket(Pids, Transport, Socket, Connection, Tracker). setopts(Transport, Socket, Other) -> tls_socket:setopts(Transport, Socket, Other). @@ -448,15 +471,17 @@ error(_, _, _) -> #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- -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(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}, + hello = Hello}, [{reply, From, {ok, ssl_connection:map_extensions(Extensions)}}]}; hello(internal, #client_hello{client_version = ClientVersion} = Hello, #state{connection_states = ConnectionStates0, @@ -540,14 +565,19 @@ cipher(Type, Event, State) -> %%-------------------------------------------------------------------- connection(info, Event, State) -> gen_info(Event, ?FUNCTION_NAME, State); +connection({call, From}, {user_renegotiate, WriteState}, + #state{connection_states = ConnectionStates} = State) -> + {next_state, ?FUNCTION_NAME, State#state{connection_states = ConnectionStates#{current_write => WriteState}}, + [{next_event,{call, From}, renegotiate}]}; connection(internal, #hello_request{}, - #state{role = client, host = Host, port = Port, + #state{role = client, + renegotiation = {Renegotiation, _}, + host = Host, port = Port, session = #session{own_certificate = Cert} = Session0, session_cache = Cache, session_cache_cb = CacheCb, - ssl_options = SslOpts, - connection_states = ConnectionStates0, - renegotiation = {Renegotiation, _}} = State0) -> - Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, + ssl_options = SslOpts, + 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} = @@ -556,7 +586,10 @@ connection(internal, #hello_request{}, = Hello#client_hello.session_id}}), next_event(hello, Record, State, Actions); connection(internal, #client_hello{} = Hello, - #state{role = server, allow_renegotiate = true} = State0) -> + #state{role = server, allow_renegotiate = true, connection_states = CS, + %%protocol_cb = Connection, + protocol_specific = #{sender := {_, Sender}} + } = State0) -> %% 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 @@ -565,12 +598,16 @@ connection(internal, #client_hello{} = Hello, erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate), {Record, State} = next_record(State0#state{allow_renegotiate = false, renegotiation = {true, peer}}), - next_event(hello, Record, State, [{next_event, internal, Hello}]); + {ok, Write} = tls_sender:renegotiate(Sender), + next_event(hello, Record, State#state{connection_states = CS#{current_write => Write}}, + [{next_event, internal, Hello}]); connection(internal, #client_hello{}, - #state{role = server, allow_renegotiate = false} = State0) -> + #state{role = server, allow_renegotiate = false, + protocol_cb = Connection} = State0) -> Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION), - State1 = send_alert(Alert, State0), - {Record, State} = ssl_connection:prepare_connection(State1, ?MODULE), + send_alert_in_connection(Alert, State0), + State1 = Connection:reinit_handshake_data(State0), + {Record, State} = next_record(State1), next_event(?FUNCTION_NAME, Record, State); connection(Type, Event, State) -> ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). @@ -607,7 +644,7 @@ code_change(_OldVsn, StateName, State, _) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, Tracker}, User, +initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Tracker}, User, {CbModule, DataTag, CloseTag, ErrorTag}) -> #ssl_options{beast_mitigation = BeastMitigation} = SSLOptions, ConnectionStates = tls_record:init_connection_states(Role, BeastMitigation), @@ -619,7 +656,8 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, Tracker}, Us ssl_session_cache end, - Monitor = erlang:monitor(process, User), + UserMonitor = erlang:monitor(process, User), + SendMonitor = erlang:monitor(process, Sender), #state{socket_options = SocketOptions, ssl_options = SSLOptions, @@ -634,7 +672,7 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, Tracker}, Us socket = Socket, connection_states = ConnectionStates, protocol_buffers = #protocol_buffers{}, - user_application = {Monitor, User}, + user_application = {UserMonitor, User}, user_data_buffer = <<>>, session_cache_cb = SessionCacheCb, renegotiation = {false, first}, @@ -642,9 +680,27 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, Tracker}, Us start_or_recv_from = undefined, protocol_cb = ?MODULE, tracker = Tracker, - flight_buffer = [] + flight_buffer = [], + protocol_specific = #{sender => {SendMonitor, Sender}} }. +initialize_tls_sender(#state{socket = Socket, + socket_options = SockOpts, + protocol_cb = Connection, + transport_cb = Transport, + negotiated_version = Version, + ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}, + connection_states = #{current_write := ConnectionWriteState}, + protocol_specific = #{sender := {_, Sender}}}) -> + Init = #{current_write => ConnectionWriteState, + socket => Socket, + socket_options => SockOpts, + protocol_cb => Connection, + transport_cb => Transport, + negotiated_version => Version, + renegotiate_at => RenegotiateAt}, + tls_sender:initialize(Sender, Init). + next_tls_record(Data, StateName, #state{protocol_buffers = #protocol_buffers{tls_record_buffer = Buf0, tls_cipher_texts = CT0} = Buffers} @@ -716,6 +772,8 @@ handle_info({CloseTag, Socket}, StateName, %% and then receive the final message. next_event(StateName, no_record, State) end; +handle_info({'DOWN', Mon, _, _, Reason}, _, #state{protocol_specific = #{sender:= {Mon, _}}} = State) -> + {stop, {shudown, sender_died, Reason}, State}; handle_info(Msg, StateName, State) -> ssl_connection:StateName(info, Msg, State, ?MODULE). @@ -784,7 +842,8 @@ unprocessed_events(Events) -> erlang:length(Events)-1. -assert_buffer_sanity(<>, #ssl_options{max_handshake_size = Max}) when +assert_buffer_sanity(<>, + #ssl_options{max_handshake_size = Max}) when Length =< Max -> case size(Rest) of N when N < Length -> diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl new file mode 100644 index 0000000000..4aeb13284f --- /dev/null +++ b/lib/ssl/src/tls_sender.erl @@ -0,0 +1,281 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(tls_sender). + +-behaviour(gen_statem). + +-include("ssl_internal.hrl"). +-include("ssl_alert.hrl"). +-include("ssl_handshake.hrl"). + +%% API +-export([start/0, initialize/2, send_data/2, send_alert/2, renegotiate/1, + update_connection_state/3]). + +%% gen_statem callbacks +-export([callback_mode/0, init/1, terminate/3, code_change/4]). +-export([init/3, connection/3, handshake/3]). + +-define(SERVER, ?MODULE). + +-record(data, {connection_pid, + connection_states = #{}, + socket, + socket_options, + protocol_cb, + transport_cb, + negotiated_version, + renegotiate_at, + connection_monitor + }). + +%%%=================================================================== +%%% API +%%%=================================================================== +-spec start() -> {ok, Pid :: pid()} | + ignore | + {error, Error :: term()}. +start() -> + gen_statem:start(?MODULE, [], []). + +initialize(Pid, InitMsg) -> + gen_statem:call(Pid, {self(), InitMsg}). + +send_data(Pid, AppData) -> + %% Needs error handling for external API + call(Pid, {application_data, AppData}). + +send_alert(Pid, Alert) -> + gen_statem:cast(Pid, Alert). + +%%-------------------------------------------------------------------- +-spec renegotiate(pid()) -> {ok, WriteState::map()} | {error, closed}. +%% Description: So TLS connection process can synchronize the +%% encryption state to be used when handshaking. +%%-------------------------------------------------------------------- +renegotiate(Pid) -> + %% Needs error handling for external API + call(Pid, renegotiate). +%%-------------------------------------------------------------------- +-spec update_connection_state(pid(), WriteState::map(), tls_record:tls_version()) -> ok. +%% Description: So TLS connection process can synchronize the +%% encryption state to be used when sending application data. +%%-------------------------------------------------------------------- +update_connection_state(Pid, NewState, Version) -> + gen_statem:cast(Pid, {new_write, NewState, Version}). + +%%%=================================================================== +%%% gen_statem callbacks +%%%=================================================================== +%%-------------------------------------------------------------------- +-spec callback_mode() -> gen_statem:callback_mode_result(). +%%-------------------------------------------------------------------- +callback_mode() -> + state_functions. + +%%-------------------------------------------------------------------- +-spec init(Args :: term()) -> + gen_statem:init_result(atom()). +%%-------------------------------------------------------------------- +init(_) -> + {ok, init, #data{}}. + +%%-------------------------------------------------------------------- +-spec init(gen_statem:event_type(), + Msg :: term(), + StateData :: term()) -> + gen_statem:event_handler_result(atom()). +%%-------------------------------------------------------------------- +init({call, From}, {Pid, #{current_write := WriteState, + socket := Socket, + socket_options := SockOpts, + protocol_cb := Connection, + transport_cb := Transport, + negotiated_version := Version, + renegotiate_at := RenegotiateAt}}, + #data{connection_states = ConnectionStates} = StateData0) -> + Monitor = erlang:monitor(process, Pid), + StateData = + StateData0#data{connection_pid = Pid, + connection_monitor = Monitor, + connection_states = + ConnectionStates#{current_write => WriteState}, + socket = Socket, + socket_options = SockOpts, + protocol_cb = Connection, + transport_cb = Transport, + negotiated_version = Version, + renegotiate_at = RenegotiateAt}, + {next_state, handshake, StateData, [{reply, From, ok}]}; +init(info, Msg, StateData) -> + handle_info(Msg, ?FUNCTION_NAME, StateData). +%%-------------------------------------------------------------------- +-spec connection(gen_statem:event_type(), + Msg :: term(), + StateData :: term()) -> + gen_statem:event_handler_result(atom()). +%%-------------------------------------------------------------------- +connection({call, From}, renegotiate, + #data{connection_states = #{current_write := Write}} = StateData) -> + {next_state, handshake, StateData, [{reply, From, {ok, Write}}]}; +connection({call, From}, {application_data, AppData}, + #data{socket_options = SockOpts} = StateData) -> + case encode_packet(AppData, SockOpts) of + {error, _} = Error -> + {next_state, ?FUNCTION_NAME, StateData, [{reply, From, Error}]}; + Data -> + send_application_data(Data, From, ?FUNCTION_NAME, StateData) + end; +connection(cast, #alert{} = Alert, StateData0) -> + StateData = send_tls_alert(Alert, StateData0), + {next_state, ?FUNCTION_NAME, StateData}; +connection(cast, {new_write, WritesState, Version}, + #data{connection_states = ConnectionStates0} = StateData) -> + {next_state, connection, + StateData#data{connection_states = + ConnectionStates0#{current_write => WritesState}, + negotiated_version = Version}}; +connection(info, Msg, StateData) -> + handle_info(Msg, ?FUNCTION_NAME, StateData). +%%-------------------------------------------------------------------- +-spec handshake(gen_statem:event_type(), + Msg :: term(), + StateData :: term()) -> + gen_statem:event_handler_result(atom()). +%%-------------------------------------------------------------------- +handshake({call, _}, _, _) -> + {keep_state_and_data, [postpone]}; +handshake(cast, {new_write, WritesState, Version}, + #data{connection_states = ConnectionStates0} = StateData) -> + {next_state, connection, + StateData#data{connection_states = + ConnectionStates0#{current_write => WritesState}, + negotiated_version = Version}}; +handshake(info, Msg, StateData) -> + handle_info(Msg, ?FUNCTION_NAME, StateData). + +%%-------------------------------------------------------------------- +-spec terminate(Reason :: term(), State :: term(), Data :: term()) -> + any(). +%%-------------------------------------------------------------------- +terminate(_Reason, _State, _Data) -> + void. + +%%-------------------------------------------------------------------- +-spec code_change( + OldVsn :: term() | {down,term()}, + State :: term(), Data :: term(), Extra :: term()) -> + {ok, NewState :: term(), NewData :: term()} | + (Reason :: term()). +%% Convert process state when code is changed +%%-------------------------------------------------------------------- +code_change(_OldVsn, State, Data, _Extra) -> + {ok, State, Data}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +handle_info({'DOWN', Monitor, _, _, Reason}, _, + #data{connection_monitor = Monitor} = StateData) -> + {stop, {shutdown, Reason}, StateData}; +handle_info(_,_,_) -> + {keep_state_and_data}. + +send_tls_alert(Alert, #data{negotiated_version = Version, + socket = Socket, + protocol_cb = Connection, + transport_cb = Transport, + connection_states = ConnectionStates0} = StateData0) -> + {BinMsg, ConnectionStates} = + Connection:encode_alert(Alert, Version, ConnectionStates0), + Connection:send(Transport, Socket, BinMsg), + StateData0#data{connection_states = ConnectionStates}. + +send_application_data(Data, {FromPid, _} = From, StateName, + #data{connection_pid = Pid, + socket = Socket, + negotiated_version = Version, + protocol_cb = Connection, + transport_cb = Transport, + connection_states = ConnectionStates0, + renegotiate_at = RenegotiateAt} = StateData0) -> + case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of + true -> + ssl_connection:internal_renegotiation(Pid, ConnectionStates0), + {next_state, handshake, StateData0, + [{next_event, {call, From}, {application_data, Data}}]}; + false -> + {Msgs, ConnectionStates} = + Connection:encode_data(Data, Version, ConnectionStates0), + StateData = StateData0#data{connection_states = ConnectionStates}, + case Connection:send(Transport, Socket, Msgs) of + ok when FromPid =:= Pid -> + {next_state, StateName, StateData, []}; + Error when FromPid =:= Pid -> + ssl_connection:stop({shutdown, Error}, StateData); + ok -> + {next_state, StateName, StateData, [{reply, From, ok}]}; + Result -> + {next_state, StateName, StateData, [{reply, From, Result}]} + end + end. + +encode_packet(Data, #socket_options{packet=Packet}) -> + case Packet of + 1 -> encode_size_packet(Data, 8, (1 bsl 8) - 1); + 2 -> encode_size_packet(Data, 16, (1 bsl 16) - 1); + 4 -> encode_size_packet(Data, 32, (1 bsl 32) - 1); + _ -> Data + end. + +encode_size_packet(Bin, Size, Max) -> + Len = erlang:byte_size(Bin), + case Len > Max of + true -> + {error, {badarg, {packet_to_large, Len, Max}}}; + false -> + <> + end. +time_to_renegotiate(_Data, + #{current_write := #{sequence_number := Num}}, + RenegotiateAt) -> + + %% We could do test: + %% is_time_to_renegotiate((erlang:byte_size(_Data) div + %% ?MAX_PLAIN_TEXT_LENGTH) + 1, RenegotiateAt), but we chose to + %% have a some what lower renegotiateAt and a much cheaper test + is_time_to_renegotiate(Num, RenegotiateAt). + +is_time_to_renegotiate(N, M) when N < M-> + false; +is_time_to_renegotiate(_,_) -> + true. + +call(FsmPid, Event) -> + try gen_statem:call(FsmPid, Event) + catch + exit:{noproc, _} -> + {error, closed}; + exit:{normal, _} -> + {error, closed}; + exit:{{shutdown, _},_} -> + {error, closed} + end. diff --git a/lib/ssl/src/tls_socket.erl b/lib/ssl/src/tls_socket.erl index 154281f1c2..a391bc53de 100644 --- a/lib/ssl/src/tls_socket.erl +++ b/lib/ssl/src/tls_socket.erl @@ -64,11 +64,12 @@ accept(ListenSocket, #config{transport_info = {Transport,_,_,_} = CbInfo, {ok, Socket} -> {ok, EmOpts} = get_emulated_opts(Tracker), {ok, Port} = tls_socket:port(Transport, Socket), - ConnArgs = [server, "localhost", Port, Socket, + {ok, Sender} = tls_sender:start(), + ConnArgs = [server, Sender, "localhost", Port, Socket, {SslOpts, emulated_socket_options(EmOpts, #socket_options{}), Tracker}, self(), CbInfo], case tls_connection_sup:start_child(ConnArgs) of {ok, Pid} -> - ssl_connection:socket_control(ConnectionCb, Socket, Pid, Transport, Tracker); + ssl_connection:socket_control(ConnectionCb, Socket, [Pid, Sender], Transport, Tracker); {error, Reason} -> {error, Reason} end; @@ -112,8 +113,8 @@ connect(Address, Port, {error, {options, {socket_options, UserOpts}}} end. -socket(Pid, Transport, Socket, ConnectionCb, Tracker) -> - #sslsocket{pid = Pid, +socket(Pids, Transport, Socket, ConnectionCb, Tracker) -> + #sslsocket{pid = Pids, %% "The name "fd" is keept for backwards compatibility fd = {Transport, Socket, ConnectionCb, Tracker}}. setopts(gen_tcp, #sslsocket{pid = {ListenSocket, #config{emulated = Tracker}}}, Options) -> diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 930ca60c5e..b6d38ee9db 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -3270,7 +3270,7 @@ no_reuses_session_server_restart_new_cert(Config) when is_list(Config) -> ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, {from, self()}, {mfa, {ssl_test_lib, no_result, []}}, - {options, DsaServerOpts}]), + {options, [{reuseaddr, true} | DsaServerOpts]}]), Client1 = ssl_test_lib:start_client([{node, ClientNode}, @@ -3331,7 +3331,7 @@ no_reuses_session_server_restart_new_cert_file(Config) when is_list(Config) -> ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, {from, self()}, {mfa, {ssl_test_lib, no_result, []}}, - {options, NewServerOpts1}]), + {options, [{reuseaddr, true} | NewServerOpts1]}]), Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -3674,7 +3674,7 @@ hibernate(Config) -> {mfa, {ssl_test_lib, send_recv_result_active, []}}, {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), - {Client, #sslsocket{pid=Pid}} = ssl_test_lib:start_client([return_socket, + {Client, #sslsocket{pid=[Pid|_]}} = ssl_test_lib:start_client([return_socket, {node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, @@ -3717,7 +3717,7 @@ hibernate_right_away(Config) -> Server1 = ssl_test_lib:start_server(StartServerOpts), Port1 = ssl_test_lib:inet_port(Server1), - {Client1, #sslsocket{pid = Pid1}} = ssl_test_lib:start_client(StartClientOpts ++ + {Client1, #sslsocket{pid = [Pid1|_]}} = ssl_test_lib:start_client(StartClientOpts ++ [{port, Port1}, {options, [{hibernate_after, 0}|ClientOpts]}]), ssl_test_lib:check_result(Server1, ok, Client1, ok), @@ -3729,7 +3729,7 @@ hibernate_right_away(Config) -> Server2 = ssl_test_lib:start_server(StartServerOpts), Port2 = ssl_test_lib:inet_port(Server2), - {Client2, #sslsocket{pid = Pid2}} = ssl_test_lib:start_client(StartClientOpts ++ + {Client2, #sslsocket{pid = [Pid2|_]}} = ssl_test_lib:start_client(StartClientOpts ++ [{port, Port2}, {options, [{hibernate_after, 1}|ClientOpts]}]), ssl_test_lib:check_result(Server2, ok, Client2, ok), @@ -3965,13 +3965,13 @@ tls_tcp_error_propagation_in_active_mode(Config) when is_list(Config) -> {mfa, {ssl_test_lib, no_result, []}}, {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), - {Client, #sslsocket{pid=Pid} = SslSocket} = ssl_test_lib:start_client([return_socket, - {node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, receive_msg, []}}, - {options, ClientOpts}]), - + {Client, #sslsocket{pid=[Pid|_]} = SslSocket} = ssl_test_lib:start_client([return_socket, + {node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, receive_msg, []}}, + {options, ClientOpts}]), + {status, _, _, StatusInfo} = sys:get_status(Pid), [_, _,_, _, Prop] = StatusInfo, State = ssl_test_lib:state(Prop), @@ -4645,6 +4645,7 @@ renegotiate_rejected(Socket) -> ok; %% Handle 1/n-1 splitting countermeasure Rizzo/Duong-Beast {ssl, Socket, "H"} -> + receive {ssl, Socket, "ello world"} -> ok -- cgit v1.2.3 From 0078ebf6c5311edc1a07be71fb7a127a175a60fa Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Wed, 15 Aug 2018 16:54:03 +0200 Subject: ssl: Adopt distribution over TLS to use new sender process --- lib/ssl/src/inet_tls_dist.erl | 15 ++- lib/ssl/src/ssl_connection.erl | 231 +++++++++++++++++------------------------ lib/ssl/src/ssl_connection.hrl | 1 + lib/ssl/src/ssl_internal.hrl | 2 +- lib/ssl/src/tls_connection.erl | 33 +++++- lib/ssl/src/tls_sender.erl | 94 +++++++++++++++-- 6 files changed, 221 insertions(+), 155 deletions(-) (limited to 'lib') diff --git a/lib/ssl/src/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl index aa3d7e3f72..ca059603ae 100644 --- a/lib/ssl/src/inet_tls_dist.erl +++ b/lib/ssl/src/inet_tls_dist.erl @@ -69,14 +69,14 @@ is_node_name(Node) -> %% ------------------------------------------------------------------------- -hs_data_common(#sslsocket{pid = DistCtrl} = SslSocket) -> +hs_data_common(#sslsocket{pid = [_, DistCtrl|_]} = SslSocket) -> #hs_data{ f_send = - fun (Ctrl, Packet) when Ctrl == DistCtrl -> + fun (_Ctrl, Packet) -> f_send(SslSocket, Packet) end, f_recv = - fun (Ctrl, Length, Timeout) when Ctrl == DistCtrl -> + fun (_, Length, Timeout) -> f_recv(SslSocket, Length, Timeout) end, f_setopts_pre_nodeup = @@ -175,8 +175,7 @@ mf_getopts(SslSocket, Opts) -> ssl:getopts(SslSocket, Opts). f_handshake_complete(DistCtrl, Node, DHandle) -> - ssl_connection:handshake_complete(DistCtrl, Node, DHandle). - + tls_sender:dist_handshake_complete(DistCtrl, Node, DHandle). setopts_filter(Opts) -> [Opt || {K,_} = Opt <- Opts, @@ -244,7 +243,7 @@ accept_loop(Driver, Listen, Kernel, Socket) -> trace([{active, false},{packet, 4}|Opts]), net_kernel:connecttime()) of - {ok, #sslsocket{pid = DistCtrl} = SslSocket} -> + {ok, #sslsocket{pid = [_, DistCtrl| _]} = SslSocket} -> trace( Kernel ! {accept, self(), DistCtrl, @@ -404,7 +403,7 @@ gen_accept_connection( do_accept( _Driver, AcceptPid, DistCtrl, MyNode, Allowed, SetupTime, Kernel) -> - SslSocket = ssl_connection:get_sslsocket(DistCtrl), + {ok, SslSocket} = tls_sender:dist_tls_socket(DistCtrl), receive {AcceptPid, controller} -> Timer = dist_util:start_timer(SetupTime), @@ -529,7 +528,7 @@ do_setup_connect(Driver, Kernel, Node, Address, Ip, TcpPort, Version, Type, MyNo [binary, {active, false}, {packet, 4}, Driver:family(), nodelay()] ++ Opts, net_kernel:connecttime()) of - {ok, #sslsocket{pid = DistCtrl} = SslSocket} -> + {ok, #sslsocket{pid = [_, DistCtrl| _]} = SslSocket} -> _ = monitor_pid(DistCtrl), ok = ssl:controlling_process(SslSocket, self()), HSData0 = hs_data_common(SslSocket), diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index d83cc7225f..b17b7fbffe 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -64,13 +64,13 @@ %% General gen_statem state functions with extra callback argument %% to determine if it is an SSL/TLS or DTLS gen_statem machine -export([init/4, error/4, hello/4, user_hello/4, abbreviated/4, certify/4, cipher/4, - connection/4, death_row/4, downgrade/4]). + connection/4, death_row/4, death_row/2, downgrade/4]). %% gen_statem callbacks -export([terminate/3, format_status/2]). %% Erlang Distribution export --export([get_sslsocket/1, handshake_complete/3]). +-export([get_sslsocket/1, dist_handshake_complete/2]). %%==================================================================== %% Setup @@ -318,8 +318,8 @@ internal_renegotiation(ConnectionPid, #{current_write := WriteState}) -> get_sslsocket(ConnectionPid) -> call(ConnectionPid, get_sslsocket). -handshake_complete(ConnectionPid, Node, DHandle) -> - call(ConnectionPid, {handshake_complete, Node, DHandle}). +dist_handshake_complete(ConnectionPid, DHandle) -> + gen_statem:cast(ConnectionPid, {dist_handshake_complete, DHandle}). %%-------------------------------------------------------------------- -spec prf(pid(), binary() | 'master_secret', binary(), @@ -452,50 +452,36 @@ read_application_data(Data, #state{user_application = {_Mon, Pid}, end, case get_data(SOpts, BytesToRead, Buffer1) of {ok, ClientData, Buffer} -> % Send data - case State0 of - #state{ - ssl_options = #ssl_options{erl_dist = true}, - protocol_specific = #{d_handle := DHandle}} -> - State = - State0#state{ - user_data_buffer = Buffer, - bytes_to_read = undefined}, - try erlang:dist_ctrl_put_data(DHandle, ClientData) of - _ - when SOpts#socket_options.active =:= false; - Buffer =:= <<>> -> - %% Passive mode, wait for active once or recv - %% Active and empty, get more data - Connection:next_record_if_active(State); - _ -> %% We have more data - read_application_data(<<>>, State) - catch error:_ -> - death_row(State, disconnect) - end; - _ -> - SocketOpt = - deliver_app_data(Connection:pids(State0), - Transport, Socket, SOpts, - ClientData, Pid, RecvFrom, Tracker, Connection), - cancel_timer(Timer), - State = - State0#state{ - user_data_buffer = Buffer, + #state{ssl_options = #ssl_options{erl_dist = Dist}, + erl_dist_data = DistData} = State0, + case Dist andalso is_dist_up(DistData) of + true -> + dist_app_data(ClientData, State0#state{user_data_buffer = Buffer, + bytes_to_read = undefined}); + _ -> + SocketOpt = + deliver_app_data(Connection:pids(State0), + Transport, Socket, SOpts, + ClientData, Pid, RecvFrom, Tracker, Connection), + cancel_timer(Timer), + State = + State0#state{ + user_data_buffer = Buffer, start_or_recv_from = undefined, timer = undefined, bytes_to_read = undefined, socket_options = SocketOpt - }, - if - SocketOpt#socket_options.active =:= false; - Buffer =:= <<>> -> - %% Passive mode, wait for active once or recv + }, + if + SocketOpt#socket_options.active =:= false; + Buffer =:= <<>> -> + %% Passive mode, wait for active once or recv %% Active and empty, get more data - Connection:next_record_if_active(State); - true -> %% We have more data - read_application_data(<<>>, State) - end - end; + Connection:next_record_if_active(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}); {passive, Buffer} -> @@ -505,6 +491,35 @@ read_application_data(Data, #state{user_application = {_Mon, Pid}, 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_buffer := DistBuff} = DistData} = State) -> + Connection:next_record_if_active(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), + try erlang:dist_ctrl_put_data(DHandle, Data) of + _ when SOpts#socket_options.active =:= false; + 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 => <<>>}}); + _ -> %% We have more data + read_application_data(<<>>, State) + catch error:_ -> + death_row(State, disconnect) + end. + +merge_dist_data(<<>>, ClientData) -> + ClientData; +merge_dist_data(DistBuff, <<>>) -> + DistBuff; +merge_dist_data(DistBuff, ClientData) -> + [DistBuff, ClientData]. %%==================================================================== %% Help functions for tls|dtls_connection.erl %%==================================================================== @@ -604,12 +619,6 @@ init({call, From}, {start, {Opts, EmOpts}, Timeout}, socket_options = SockOpts} = State0, Connection) -> try SslOpts = ssl:handle_options(Opts, OrigSSLOptions), - case SslOpts of - #ssl_options{erl_dist = true} -> - process_flag(priority, max); - _ -> - ok - end, State = ssl_config(SslOpts, Role, State0), init({call, From}, {start, Timeout}, State#state{ssl_options = SslOpts, @@ -1045,25 +1054,6 @@ connection({call, From}, negotiated_protocol, #state{negotiated_protocol = SelectedProtocol} = State, _) -> hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, SelectedProtocol}}]); -connection({call, From}, {handshake_complete, _Node, DHandle}, - #state{ssl_options = #ssl_options{erl_dist = true}, - socket_options = SockOpts, - protocol_specific = ProtocolSpecific} = State, Connection) -> - %% From now on we execute on normal priority - process_flag(priority, normal), - try erlang:dist_ctrl_get_data_notification(DHandle) of - _ -> - NewState = - State#state{ - socket_options = - SockOpts#socket_options{active = true}, - protocol_specific = - ProtocolSpecific#{d_handle => DHandle}}, - {Record, NewerState} = Connection:next_record_if_active(NewState), - Connection:next_event(connection, Record, NewerState, [{reply, From, ok}]) - catch error:_ -> - death_row(State, disconnect) - end; connection({call, From}, Msg, State, Connection) -> handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); connection(cast, {internal_renegotiate, WriteState}, #state{protocol_cb = Connection, @@ -1071,28 +1061,18 @@ connection(cast, {internal_renegotiate, WriteState}, #state{protocol_cb = Connec = State, Connection) -> Connection:renegotiate(State#state{renegotiation = {true, internal}, connection_states = ConnectionStates#{current_write => WriteState}}, []); -connection(info, dist_data = Msg, #state{ssl_options = #ssl_options{erl_dist = true}, - protocol_specific = #{d_handle := DHandle}} = State, _) -> - eat_msgs(Msg), - try send_dist_data(?FUNCTION_NAME, State, DHandle, []) - catch error:_ -> - death_row(State, disconnect) - end; -connection(info, {send, From, Ref, Data}, #state{ssl_options = #ssl_options{erl_dist = true}, - protocol_specific = #{d_handle := _}}, _) -> - %% This is for testing only! - %% - %% Needed by some OTP distribution - %% test suites... - From ! {Ref, ok}, - {keep_state_and_data, - [{next_event, {call, {self(), undefined}}, - {application_data, iolist_to_binary(Data)}}]}; -connection(info, tick = Msg, #state{ssl_options = #ssl_options{erl_dist = true}, - protocol_specific = #{d_handle := _}},_) -> - eat_msgs(Msg), - {keep_state_and_data, - [{next_event, {call, {self(), undefined}}, {application_data, <<>>}}]}; +connection(cast, {dist_handshake_complete, DHandle}, + #state{ssl_options = #ssl_options{erl_dist = true}, + erl_dist_data = ErlDistData, + socket_options = SockOpts} = State0, Connection) -> + process_flag(priority, normal), + State1 = + State0#state{ + socket_options = + SockOpts#socket_options{active = true}, + erl_dist_data = ErlDistData#{dist_handle => DHandle}}, + {Record, State} = dist_app_data(<<>>, State1), + Connection:next_event(connection, Record, State); connection(info, Msg, State, _) -> handle_info(Msg, ?FUNCTION_NAME, State); connection(internal, {recv, _}, State, Connection) -> @@ -1107,13 +1087,10 @@ connection(Type, Msg, State, Connection) -> %%-------------------------------------------------------------------- %% We just wait for the owner to die which triggers the monitor, %% or the socket may die too -death_row( - info, {'DOWN', MonitorRef, _, _, Reason}, - #state{user_application={MonitorRef,_Pid}}, - _) -> +death_row(info, {'DOWN', MonitorRef, _, _, Reason}, + #state{user_application={MonitorRef,_Pid}},_) -> {stop, {shutdown, Reason}}; -death_row( - info, {'EXIT', Socket, Reason}, #state{socket = Socket}, _) -> +death_row(info, {'EXIT', Socket, Reason}, #state{socket = Socket}, _) -> {stop, {shutdown, Reason}}; death_row(state_timeout, Reason, _State, _Connection) -> {stop, {shutdown,Reason}}; @@ -1176,7 +1153,14 @@ handle_common_event(internal, {application_data, Data}, StateName, State0, Conne {stop, _, _} = Stop-> Stop; {Record, State} -> - Connection:next_event(StateName, 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 end; handle_common_event(internal, #change_cipher_spec{type = <<1>>}, StateName, #state{negotiated_version = Version} = State, _) -> @@ -1262,12 +1246,8 @@ handle_call({set_opts, Opts0}, From, StateName, handle_call(renegotiate, From, StateName, _, _) when StateName =/= connection -> {keep_state_and_data, [{reply, From, {error, already_renegotiating}}]}; -handle_call( - get_sslsocket, From, _StateName, - #state{transport_cb = Transport, socket = Socket, tracker = Tracker}, - Connection) -> - SslSocket = - Connection:socket(self(), Transport, Socket, Connection, Tracker), +handle_call(get_sslsocket, From, _StateName, State, Connection) -> + SslSocket = Connection:socket(State), {keep_state_and_data, [{reply, From, SslSocket}]}; handle_call({prf, Secret, Label, Seed, WantedLength}, From, _, @@ -1316,23 +1296,18 @@ handle_info({ErrorTag, Socket, Reason}, StateName, #state{socket = Socket, handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), stop(normal, State); -handle_info( - {'DOWN', MonitorRef, _, _, Reason}, _, - #state{ - user_application = {MonitorRef, _Pid}, - ssl_options = #ssl_options{erl_dist = true}}) -> +handle_info({'DOWN', MonitorRef, _, _, Reason}, _, + #state{user_application = {MonitorRef, _Pid}, + ssl_options = #ssl_options{erl_dist = true}}) -> {stop, {shutdown, Reason}}; -handle_info( - {'DOWN', MonitorRef, _, _, _}, _, - #state{user_application = {MonitorRef, _Pid}}) -> +handle_info({'DOWN', MonitorRef, _, _, _}, _, + #state{user_application = {MonitorRef, _Pid}}) -> {stop, normal}; -handle_info( - {'EXIT', Pid, _Reason}, StateName, - #state{user_application = {_MonitorRef, Pid}} = State) -> +handle_info({'EXIT', Pid, _Reason}, StateName, + #state{user_application = {_MonitorRef, Pid}} = State) -> %% It seems the user application has linked to us %% - ignore that and let the monitor handle this {next_state, StateName, State}; - %%% So that terminate will be run when supervisor issues shutdown handle_info({'EXIT', _Sup, shutdown}, _StateName, State) -> stop(shutdown, State); @@ -1380,7 +1355,7 @@ terminate({shutdown, transport_closed} = Reason, socket = Socket, transport_cb = Transport} = State) -> handle_trusted_certs_db(State), Connection:close(Reason, Socket, Transport, undefined, undefined); -terminate({shutdown, own_alert}, _StateName, #state{%%send_queue = SendQueue, +terminate({shutdown, own_alert}, _StateName, #state{ protocol_cb = Connection, socket = Socket, transport_cb = Transport} = State) -> @@ -2753,25 +2728,6 @@ new_emulated([], EmOpts) -> new_emulated(NewEmOpts, _) -> NewEmOpts. %%---------------Erlang distribution -------------------------------------- - -send_dist_data(StateName, State, DHandle, Acc) -> - case erlang:dist_ctrl_get_data(DHandle) of - none -> - erlang:dist_ctrl_get_data_notification(DHandle), - hibernate_after(StateName, State, lists:reverse(Acc)); - Data -> - send_dist_data( - StateName, State, DHandle, - [{next_event, {call, {self(), undefined}}, {application_data, Data}} - |Acc]) - end. - -%% Overload mitigation -eat_msgs(Msg) -> - receive Msg -> eat_msgs(Msg) - after 0 -> ok - end. - %% When acting as distribution controller map the exit reason %% to follow the documented nodedown_reason for net_kernel stop(Reason, State) -> @@ -2791,3 +2747,8 @@ erl_dist_stop_reason( end; erl_dist_stop_reason(Reason, _State) -> Reason. + +is_dist_up(#{dist_handle := Handle}) when Handle =/= undefined -> + true; +is_dist_up(_) -> + false. diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl index 504a141a4a..66e3182313 100644 --- a/lib/ssl/src/ssl_connection.hrl +++ b/lib/ssl/src/ssl_connection.hrl @@ -75,6 +75,7 @@ cert_db_ref :: certdb_ref() | 'undefined', bytes_to_read :: undefined | integer(), %% bytes to read in passive mode user_data_buffer :: undefined | binary() | secret_printout(), + erl_dist_data = #{} :: map(), renegotiation :: undefined | {boolean(), From::term() | internal | peer}, start_or_recv_from :: term(), timer :: undefined | reference(), % start_or_recive_timer diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index ae1c3ea47c..fd246e2550 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -120,7 +120,7 @@ %% undefined if not hibernating, or number of ms of %% inactivity after which ssl_connection will go into %% hibernation - hibernate_after :: timeout(), + hibernate_after :: timeout(), %% This option should only be set to true by inet_tls_dist erl_dist = false :: boolean(), alpn_advertised_protocols = undefined :: [binary()] | undefined , diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index ef52421523..8277569281 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -69,6 +69,9 @@ %% gen_statem callbacks -export([callback_mode/0, terminate/3, code_change/4, format_status/2]). + +-define(DIST_CNTRL_SPAWN_OPTS, [{priority, max}]). + %%==================================================================== %% Internal application API %%==================================================================== @@ -93,7 +96,7 @@ start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_, Tracker} = User, {CbModule, _,_, _} = CbInfo, Timeout) -> try - {ok, Sender} = tls_sender:start(), + {ok, Sender} = tls_sender:start([{spawn_opt, ?DIST_CNTRL_SPAWN_OPTS}]), {ok, Pid} = tls_connection_sup:start_child_dist([Role, Sender, Host, Port, Socket, Opts, User, CbInfo]), {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Tracker), @@ -113,8 +116,14 @@ start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_, Tracker} = start_link(Role, Sender, Host, Port, Socket, Options, User, CbInfo) -> {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Sender, Host, Port, Socket, Options, User, CbInfo]])}. -init([Role, Sender, Host, Port, Socket, Options, User, CbInfo]) -> +init([Role, Sender, Host, Port, Socket, {SslOpts, _, _} = Options, User, CbInfo]) -> process_flag(trap_exit, true), + case SslOpts#ssl_options.erl_dist of + true -> + process_flag(priority, max); + _ -> + ok + end, State0 = #state{protocol_specific = Map} = initial_state(Role, Sender, Host, Port, Socket, Options, User, CbInfo), try @@ -646,9 +655,11 @@ code_change(_OldVsn, StateName, State, _) -> %%-------------------------------------------------------------------- initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Tracker}, User, {CbModule, DataTag, CloseTag, ErrorTag}) -> - #ssl_options{beast_mitigation = BeastMitigation} = SSLOptions, + #ssl_options{beast_mitigation = BeastMitigation, + erl_dist = IsErlDist} = SSLOptions, ConnectionStates = tls_record:init_connection_states(Role, BeastMitigation), + ErlDistData = erl_dist_data(IsErlDist), SessionCacheCb = case application:get_env(ssl, session_cb) of {ok, Cb} when is_atom(Cb) -> Cb; @@ -670,6 +681,7 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac host = Host, port = Port, socket = Socket, + erl_dist_data = ErlDistData, connection_states = ConnectionStates, protocol_buffers = #protocol_buffers{}, user_application = {UserMonitor, User}, @@ -684,8 +696,16 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac protocol_specific = #{sender => {SendMonitor, Sender}} }. -initialize_tls_sender(#state{socket = Socket, +erl_dist_data(true) -> + #{dist_handle => undefined, + dist_buffer => <<>>}; +erl_dist_data(false) -> + #{}. + +initialize_tls_sender(#state{role = Role, + socket = Socket, socket_options = SockOpts, + tracker = Tracker, protocol_cb = Connection, transport_cb = Transport, negotiated_version = Version, @@ -693,8 +713,10 @@ initialize_tls_sender(#state{socket = Socket, connection_states = #{current_write := ConnectionWriteState}, protocol_specific = #{sender := {_, Sender}}}) -> Init = #{current_write => ConnectionWriteState, + role => Role, socket => Socket, socket_options => SockOpts, + tracker => Tracker, protocol_cb => Connection, transport_cb => Transport, negotiated_version => Version, @@ -772,6 +794,9 @@ handle_info({CloseTag, Socket}, StateName, %% and then receive the final message. next_event(StateName, no_record, State) end; +handle_info({'DOWN', Mon, _, _, _}, _, #state{ssl_options = #ssl_options{erl_dist = true}, + protocol_specific = #{sender:= {Mon, _}}} = State) -> + ssl_connection:death_row(State, disconnect); handle_info({'DOWN', Mon, _, _, Reason}, _, #state{protocol_specific = #{sender:= {Mon, _}}} = State) -> {stop, {shudown, sender_died, Reason}, State}; handle_info(Msg, StateName, State) -> diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl index 4aeb13284f..2746d89048 100644 --- a/lib/ssl/src/tls_sender.erl +++ b/lib/ssl/src/tls_sender.erl @@ -27,8 +27,8 @@ -include("ssl_handshake.hrl"). %% API --export([start/0, initialize/2, send_data/2, send_alert/2, renegotiate/1, - update_connection_state/3]). +-export([start/0, start/1, initialize/2, send_data/2, send_alert/2, renegotiate/1, + update_connection_state/3, dist_tls_socket/1, dist_handshake_complete/3]). %% gen_statem callbacks -export([callback_mode/0, init/1, terminate/3, code_change/4]). @@ -38,13 +38,16 @@ -record(data, {connection_pid, connection_states = #{}, + role, socket, socket_options, + tracker, protocol_cb, transport_cb, negotiated_version, renegotiate_at, - connection_monitor + connection_monitor, + dist_handle }). %%%=================================================================== @@ -53,9 +56,24 @@ -spec start() -> {ok, Pid :: pid()} | ignore | {error, Error :: term()}. +-spec start(list()) -> {ok, Pid :: pid()} | + ignore | + {error, Error :: term()}. + +%% Description: Start sender process to avoid dead lock that +%% may happen when a socket is busy (busy port) and the +%% same process is sending and receiving +%%-------------------------------------------------------------------- start() -> gen_statem:start(?MODULE, [], []). +start(SpawnOpts) -> + gen_statem:start_link(?MODULE, [], SpawnOpts). +%%-------------------------------------------------------------------- +-spec initialize(pid(), map()) -> ok. +%% Description: So TLS connection process can initialize it sender +%% process. +%%-------------------------------------------------------------------- initialize(Pid, InitMsg) -> gen_statem:call(Pid, {self(), InitMsg}). @@ -82,6 +100,12 @@ renegotiate(Pid) -> update_connection_state(Pid, NewState, Version) -> gen_statem:cast(Pid, {new_write, NewState, Version}). +dist_handshake_complete(ConnectionPid, Node, DHandle) -> + gen_statem:call(ConnectionPid, {dist_handshake_complete, Node, DHandle}). + +dist_tls_socket(Pid) -> + gen_statem:call(Pid, dist_get_tls_socket). + %%%=================================================================== %%% gen_statem callbacks %%%=================================================================== @@ -105,8 +129,10 @@ init(_) -> gen_statem:event_handler_result(atom()). %%-------------------------------------------------------------------- init({call, From}, {Pid, #{current_write := WriteState, + role := Role, socket := Socket, socket_options := SockOpts, + tracker := Tracker, protocol_cb := Connection, transport_cb := Transport, negotiated_version := Version, @@ -118,8 +144,10 @@ init({call, From}, {Pid, #{current_write := WriteState, connection_monitor = Monitor, connection_states = ConnectionStates#{current_write => WriteState}, + role = Role, socket = Socket, socket_options = SockOpts, + tracker = Tracker, protocol_cb = Connection, transport_cb = Transport, negotiated_version = Version, @@ -137,13 +165,28 @@ connection({call, From}, renegotiate, #data{connection_states = #{current_write := Write}} = StateData) -> {next_state, handshake, StateData, [{reply, From, {ok, Write}}]}; connection({call, From}, {application_data, AppData}, - #data{socket_options = SockOpts} = StateData) -> + #data{socket_options = SockOpts} = StateData) -> case encode_packet(AppData, SockOpts) of {error, _} = Error -> {next_state, ?FUNCTION_NAME, StateData, [{reply, From, Error}]}; Data -> send_application_data(Data, From, ?FUNCTION_NAME, StateData) end; +connection({call, From}, dist_get_tls_socket, + #data{protocol_cb = Connection, + transport_cb = Transport, + socket = Socket, + connection_pid = Pid, + tracker = Tracker} = StateData) -> + TLSSocket = Connection:socket([Pid, self()], Transport, Socket, Connection, Tracker), + {next_state, ?FUNCTION_NAME, StateData, [{reply, From, {ok, TLSSocket}}]}; +connection({call, From}, {dist_handshake_complete, _Node, DHandle}, #data{connection_pid = Pid} = StateData) -> + ok = erlang:dist_ctrl_input_handler(DHandle, Pid), + ok = ssl_connection:dist_handshake_complete(Pid, DHandle), + %% From now on we execute on normal priority + process_flag(priority, normal), + Events = dist_data_events(DHandle, []), + {next_state, ?FUNCTION_NAME, StateData#data{dist_handle = DHandle}, [{reply, From, ok} | Events]}; connection(cast, #alert{} = Alert, StateData0) -> StateData = send_tls_alert(Alert, StateData0), {next_state, ?FUNCTION_NAME, StateData}; @@ -153,6 +196,23 @@ connection(cast, {new_write, WritesState, Version}, StateData#data{connection_states = ConnectionStates0#{current_write => WritesState}, negotiated_version = Version}}; +connection(info, dist_data, #data{dist_handle = DHandle} = StateData) -> + Events = dist_data_events(DHandle, []), + {next_state, ?FUNCTION_NAME, StateData, Events}; +connection(info, tick, StateData) -> + consume_ticks(), + {next_state, ?FUNCTION_NAME, StateData, + [{next_event, {call, {self(), undefined}}, + {application_data, <<>>}}]}; +connection(info, {send, From, Ref, Data}, _StateData) -> + %% This is for testing only! + %% + %% Needed by some OTP distribution + %% test suites... + From ! {Ref, ok}, + {keep_state_and_data, + [{next_event, {call, {self(), undefined}}, + {application_data, iolist_to_binary(Data)}}]}; connection(info, Msg, StateData) -> handle_info(Msg, ?FUNCTION_NAME, StateData). %%-------------------------------------------------------------------- @@ -209,9 +269,10 @@ send_tls_alert(Alert, #data{negotiated_version = Version, Connection:send(Transport, Socket, BinMsg), StateData0#data{connection_states = ConnectionStates}. -send_application_data(Data, {FromPid, _} = From, StateName, +send_application_data(Data, From, StateName, #data{connection_pid = Pid, socket = Socket, + dist_handle = DistHandle, negotiated_version = Version, protocol_cb = Connection, transport_cb = Transport, @@ -227,9 +288,9 @@ send_application_data(Data, {FromPid, _} = From, StateName, Connection:encode_data(Data, Version, ConnectionStates0), StateData = StateData0#data{connection_states = ConnectionStates}, case Connection:send(Transport, Socket, Msgs) of - ok when FromPid =:= Pid -> + ok when DistHandle =/= undefined -> {next_state, StateName, StateData, []}; - Error when FromPid =:= Pid -> + Error when DistHandle =/= undefined -> ssl_connection:stop({shutdown, Error}, StateData); ok -> {next_state, StateName, StateData, [{reply, From, ok}]}; @@ -279,3 +340,22 @@ call(FsmPid, Event) -> exit:{{shutdown, _},_} -> {error, closed} end. + +%%---------------Erlang distribution -------------------------------------- + +dist_data_events(DHandle, Events) -> + case erlang:dist_ctrl_get_data(DHandle) of + none -> + erlang:dist_ctrl_get_data_notification(DHandle), + lists:reverse(Events); + Data -> + Event = {next_event, {call, {self(), undefined}}, {application_data, Data}}, + dist_data_events(DHandle, [Event | Events]) + end. + +consume_ticks() -> + receive tick -> + consume_ticks() + after 0 -> + ok + end. -- cgit v1.2.3 From a508428df8af14437f1a4766ec68294d1d736915 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Thu, 23 Aug 2018 14:29:36 +0200 Subject: ssl: Improve close handling We want to make sure that the sender process that may get stuck in prim_inet:send will die if the tls_connection process is terminated. And we also like to make sure that it terminates as gracefully as possible. So when the tls_connection process dies it spawns a killer process that will brutaly kill the sender if it is unresponsive and does not terminate due to its monitor of the tls_connetion process triggering. When the sender process also acts as distribution controller it may also have other processess that it is linked with that it should bring down or that could bring the connection down. --- lib/ssl/src/ssl_connection.erl | 47 +++++---------------------------------- lib/ssl/src/tls_connection.erl | 44 ++++++++++++++++++++----------------- lib/ssl/src/tls_sender.erl | 50 ++++++++++++++++++++++++++++++++++++------ 3 files changed, 72 insertions(+), 69 deletions(-) (limited to 'lib') diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index b17b7fbffe..5769939ee5 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -64,7 +64,7 @@ %% General gen_statem state functions with extra callback argument %% to determine if it is an SSL/TLS or DTLS gen_statem machine -export([init/4, error/4, hello/4, user_hello/4, abbreviated/4, certify/4, cipher/4, - connection/4, death_row/4, death_row/2, downgrade/4]). + connection/4, downgrade/4]). %% gen_statem callbacks -export([terminate/3, format_status/2]). @@ -511,7 +511,7 @@ dist_app_data(ClientData, #state{erl_dist_data = #{dist_handle := DHandle, _ -> %% We have more data read_application_data(<<>>, State) catch error:_ -> - death_row(State, disconnect) + stop(State, disconnect) end. merge_dist_data(<<>>, ClientData) -> @@ -1080,29 +1080,6 @@ connection(internal, {recv, _}, State, Connection) -> connection(Type, Msg, State, Connection) -> handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). -%%-------------------------------------------------------------------- --spec death_row(gen_statem:event_type(), term(), - #state{}, tls_connection | dtls_connection) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -%% We just wait for the owner to die which triggers the monitor, -%% or the socket may die too -death_row(info, {'DOWN', MonitorRef, _, _, Reason}, - #state{user_application={MonitorRef,_Pid}},_) -> - {stop, {shutdown, Reason}}; -death_row(info, {'EXIT', Socket, Reason}, #state{socket = Socket}, _) -> - {stop, {shutdown, Reason}}; -death_row(state_timeout, Reason, _State, _Connection) -> - {stop, {shutdown,Reason}}; -death_row(_Type, _Msg, _State, _Connection) -> - %% Waste all other events - keep_state_and_data. - -%% State entry function -death_row(State, Reason) -> - {next_state, death_row, State, - [{state_timeout, 5000, Reason}]}. - %%-------------------------------------------------------------------- -spec downgrade(gen_statem:event_type(), term(), #state{}, tls_connection | dtls_connection) -> @@ -2727,26 +2704,12 @@ new_emulated([], EmOpts) -> EmOpts; new_emulated(NewEmOpts, _) -> NewEmOpts. -%%---------------Erlang distribution -------------------------------------- -%% When acting as distribution controller map the exit reason -%% to follow the documented nodedown_reason for net_kernel + stop(Reason, State) -> - {stop, erl_dist_stop_reason(Reason, State), State}. + {stop, Reason, State}. stop_and_reply(Reason, Replies, State) -> - {stop_and_reply, erl_dist_stop_reason(Reason, State), Replies, State}. - -erl_dist_stop_reason( - Reason, #state{ssl_options = #ssl_options{erl_dist = true}}) -> - case Reason of - normal -> - %% We can not exit with normal since that will not bring - %% down the rest of the distribution processes - {shutdown, normal}; - _ -> Reason - end; -erl_dist_stop_reason(Reason, _State) -> - Reason. + {stop_and_reply, Reason, Replies, State}. is_dist_up(#{dist_handle := Handle}) when Handle =/= undefined -> true; diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 8277569281..6c7511d2b3 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -65,7 +65,7 @@ %% gen_statem state functions -export([init/3, error/3, downgrade/3, %% Initiation and take down states hello/3, user_hello/3, certify/3, cipher/3, abbreviated/3, %% Handshake states - connection/3, death_row/3]). + connection/3]). %% gen_statem callbacks -export([callback_mode/0, terminate/3, code_change/4, format_status/2]). @@ -135,7 +135,7 @@ init([Role, Sender, Host, Port, Socket, {SslOpts, _, _} = Options, User, CbInfo gen_statem:enter_loop(?MODULE, [], error, EState) end. -pids(#state{protocol_specific = #{sender := {_, Sender}}}) -> +pids(#state{protocol_specific = #{sender := Sender}}) -> [self(), Sender]. %%==================================================================== @@ -307,7 +307,7 @@ queue_change_cipher(Msg, #state{negotiated_version = Version, State0#state{connection_states = ConnectionStates, flight_buffer = Flight0 ++ [BinChangeCipher]}. -reinit(#state{protocol_specific = #{sender := {_,Sender}}, +reinit(#state{protocol_specific = #{sender := Sender}, negotiated_version = Version, connection_states = #{current_write := Write}} = State) -> tls_sender:update_connection_state(Sender, Write, Version), @@ -353,7 +353,7 @@ send_alert(Alert, #state{negotiated_version = Version, Connection:send(Transport, Socket, BinMsg), StateData0#state{connection_states = ConnectionStates}. -send_alert_in_connection(Alert, #state{protocol_specific = #{sender := {_, Sender}}}) -> +send_alert_in_connection(Alert, #state{protocol_specific = #{sender := Sender}}) -> tls_sender:send_alert(Sender, Alert). %% User closes or recursive call! @@ -597,7 +597,7 @@ connection(internal, #hello_request{}, connection(internal, #client_hello{} = Hello, #state{role = server, allow_renegotiate = true, connection_states = CS, %%protocol_cb = Connection, - protocol_specific = #{sender := {_, Sender}} + protocol_specific = #{sender := Sender} } = State0) -> %% Mitigate Computational DoS attack %% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html @@ -621,13 +621,6 @@ connection(internal, #client_hello{}, connection(Type, Event, State) -> ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). -%%-------------------------------------------------------------------- --spec death_row(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -death_row(Type, Event, State) -> - ssl_connection:death_row(Type, Event, State, ?MODULE). - %%-------------------------------------------------------------------- -spec downgrade(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). @@ -642,6 +635,7 @@ callback_mode() -> state_functions. terminate(Reason, StateName, State) -> + ensure_sender_terminate(Reason, State), catch ssl_connection:terminate(Reason, StateName, State). format_status(Type, Data) -> @@ -668,7 +662,6 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac end, UserMonitor = erlang:monitor(process, User), - SendMonitor = erlang:monitor(process, Sender), #state{socket_options = SocketOptions, ssl_options = SSLOptions, @@ -693,7 +686,7 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac protocol_cb = ?MODULE, tracker = Tracker, flight_buffer = [], - protocol_specific = #{sender => {SendMonitor, Sender}} + protocol_specific = #{sender => Sender} }. erl_dist_data(true) -> @@ -711,7 +704,7 @@ initialize_tls_sender(#state{role = Role, negotiated_version = Version, ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}, connection_states = #{current_write := ConnectionWriteState}, - protocol_specific = #{sender := {_, Sender}}}) -> + protocol_specific = #{sender := Sender}}) -> Init = #{current_write => ConnectionWriteState, role => Role, socket => Socket, @@ -794,11 +787,9 @@ handle_info({CloseTag, Socket}, StateName, %% and then receive the final message. next_event(StateName, no_record, State) end; -handle_info({'DOWN', Mon, _, _, _}, _, #state{ssl_options = #ssl_options{erl_dist = true}, - protocol_specific = #{sender:= {Mon, _}}} = State) -> - ssl_connection:death_row(State, disconnect); -handle_info({'DOWN', Mon, _, _, Reason}, _, #state{protocol_specific = #{sender:= {Mon, _}}} = State) -> - {stop, {shudown, sender_died, Reason}, State}; +handle_info({'EXIT', Pid, Reason}, _, + #state{protocol_specific = Pid} = State) -> + {stop, {shutdown, sender_died, Reason}, State}; handle_info(Msg, StateName, State) -> ssl_connection:StateName(info, Msg, State, ?MODULE). @@ -888,3 +879,16 @@ assert_buffer_sanity(Bin, _) -> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data)) end. + +ensure_sender_terminate(downgrade, _) -> + ok; %% Do not terminate sender during downgrade phase +ensure_sender_terminate(_, #state{protocol_specific = #{sender := Sender}}) -> + %% Make sure TLS sender dies when connection process is terminated normally + %% This is needed if the tls_sender is blocked in prim_inet:send + Kill = fun() -> + receive + after 5000 -> + catch (exit(Sender, kill)) + end + end, + spawn(Kill). diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl index 2746d89048..007fd345dd 100644 --- a/lib/ssl/src/tls_sender.erl +++ b/lib/ssl/src/tls_sender.erl @@ -25,6 +25,7 @@ -include("ssl_internal.hrl"). -include("ssl_alert.hrl"). -include("ssl_handshake.hrl"). +-include("ssl_api.hrl"). %% API -export([start/0, start/1, initialize/2, send_data/2, send_alert/2, renegotiate/1, @@ -32,7 +33,7 @@ %% gen_statem callbacks -export([callback_mode/0, init/1, terminate/3, code_change/4]). --export([init/3, connection/3, handshake/3]). +-export([init/3, connection/3, handshake/3, death_row/3]). -define(SERVER, ?MODULE). @@ -53,6 +54,7 @@ %%%=================================================================== %%% API %%%=================================================================== +%%-------------------------------------------------------------------- -spec start() -> {ok, Pid :: pid()} | ignore | {error, Error :: term()}. @@ -65,7 +67,7 @@ %% same process is sending and receiving %%-------------------------------------------------------------------- start() -> - gen_statem:start(?MODULE, [], []). + gen_statem:start_link(?MODULE, [], []). start(SpawnOpts) -> gen_statem:start_link(?MODULE, [], SpawnOpts). @@ -77,10 +79,19 @@ start(SpawnOpts) -> initialize(Pid, InitMsg) -> gen_statem:call(Pid, {self(), InitMsg}). +%%-------------------------------------------------------------------- +-spec send_data(pid(), iodata()) -> ok. +%% Description: Send application data +%%-------------------------------------------------------------------- send_data(Pid, AppData) -> %% Needs error handling for external API call(Pid, {application_data, AppData}). +%%-------------------------------------------------------------------- +-spec send_alert(pid(), #alert{}) -> _. +%% Description: TLS connection process wants to end an Alert +%% in the connection state. +%%-------------------------------------------------------------------- send_alert(Pid, Alert) -> gen_statem:cast(Pid, Alert). @@ -99,10 +110,16 @@ renegotiate(Pid) -> %%-------------------------------------------------------------------- update_connection_state(Pid, NewState, Version) -> gen_statem:cast(Pid, {new_write, NewState, Version}). - +%%-------------------------------------------------------------------- +-spec dist_handshake_complete(pid(), node(), term()) -> ok. +%% Description: Erlang distribution callback +%%-------------------------------------------------------------------- dist_handshake_complete(ConnectionPid, Node, DHandle) -> gen_statem:call(ConnectionPid, {dist_handshake_complete, Node, DHandle}). - +%%-------------------------------------------------------------------- +-spec dist_tls_socket(pid()) -> {ok, #sslsocket{}}. +%% Description: To enable distribution startup to get a proper "#sslsocket{}" +%%-------------------------------------------------------------------- dist_tls_socket(Pid) -> gen_statem:call(Pid, dist_get_tls_socket). @@ -120,6 +137,9 @@ callback_mode() -> gen_statem:init_result(atom()). %%-------------------------------------------------------------------- init(_) -> + %% Note: Should not trap exits so that this process + %% will be terminated if tls_connection process is + %% killed brutally {ok, init, #data{}}. %%-------------------------------------------------------------------- @@ -232,6 +252,18 @@ handshake(cast, {new_write, WritesState, Version}, handshake(info, Msg, StateData) -> handle_info(Msg, ?FUNCTION_NAME, StateData). +%%-------------------------------------------------------------------- +-spec death_row(gen_statem:event_type(), + Msg :: term(), + StateData :: term()) -> + gen_statem:event_handler_result(atom()). +%%-------------------------------------------------------------------- +death_row(state_timeout, Reason, _State) -> + {stop, {shutdown, Reason}}; +death_row(_Type, _Msg, _State) -> + %% Waste all other events + keep_state_and_data. + %%-------------------------------------------------------------------- -spec terminate(Reason :: term(), State :: term(), Data :: term()) -> any(). @@ -254,8 +286,12 @@ code_change(_OldVsn, State, Data, _Extra) -> %%% Internal functions %%%=================================================================== handle_info({'DOWN', Monitor, _, _, Reason}, _, + #data{connection_monitor = Monitor, + dist_handle = Handle} = StateData) when Handle =/= undefined-> + {next_state, death_row, StateData, [{state_timeout, 5000, Reason}]}; +handle_info({'DOWN', Monitor, _, _, _}, _, #data{connection_monitor = Monitor} = StateData) -> - {stop, {shutdown, Reason}, StateData}; + {stop, normal, StateData}; handle_info(_,_,_) -> {keep_state_and_data}. @@ -290,8 +326,8 @@ send_application_data(Data, From, StateName, case Connection:send(Transport, Socket, Msgs) of ok when DistHandle =/= undefined -> {next_state, StateName, StateData, []}; - Error when DistHandle =/= undefined -> - ssl_connection:stop({shutdown, Error}, StateData); + Reason when DistHandle =/= undefined -> + {next_state, death_row, StateData, [{state_timeout, 5000, Reason}]}; ok -> {next_state, StateName, StateData, [{reply, From, ok}]}; Result -> -- cgit v1.2.3