From 73978663a26110121c7a7e4dc53e2481980b6b83 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= <peterdmv@erlang.org>
Date: Tue, 9 Apr 2019 13:02:24 +0200
Subject: ssl: Implement {active,N} for DTLS

Change-Id: I559624bedf3b9b9ed0316af5262f59bcad8de926
---
 lib/ssl/src/dtls_connection.erl   | 42 +++++++++++++++++++++++++++++----------
 lib/ssl/src/dtls_packet_demux.erl | 37 ++++++++++++++++++++++++----------
 lib/ssl/src/dtls_socket.erl       |  6 +++---
 lib/ssl/src/ssl.erl               |  6 +++---
 4 files changed, 64 insertions(+), 27 deletions(-)

(limited to 'lib')

diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl
index 98c15d5bf9..13fc0b25e7 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -67,7 +67,7 @@
 %% Setup
 %%====================================================================	     
 start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_, Tracker} = Opts,
-	  User, {CbModule, _,_, _} = CbInfo, 
+	  User, {CbModule, _, _, _, _} = CbInfo,
 	  Timeout) -> 
     try 
 	{ok, Pid} = dtls_connection_sup:start_child([Role, Host, Port, Socket, 
@@ -147,13 +147,16 @@ next_record(#state{static_env = #static_env{role = server,
                                             socket = {Listener, {Client, _}}}} = State) ->
     dtls_packet_demux:active_once(Listener, Client, self()),
     {no_record, State};
-next_record(#state{static_env = #static_env{role = client,
+next_record(#state{protocol_specific = #{active_n_toggle := true,
+                                         active_n := N} = ProtocolSpec,
+                   static_env = #static_env{role = client,
                                             socket = {_Server, Socket} = DTLSSocket,
                                             close_tag = CloseTag,
                                             transport_cb = Transport}} = State) ->
-    case dtls_socket:setopts(Transport, Socket, [{active,once}]) of
+    case dtls_socket:setopts(Transport, Socket, [{active,N}]) of
         ok ->
- 	    {no_record, State};
+            {no_record, State#state{protocol_specific =
+                                        ProtocolSpec#{active_n_toggle => false}}};
  	_ ->
             self() ! {CloseTag, DTLSSocket},
 	    {no_record, State}
@@ -291,9 +294,10 @@ handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) ->
 %% Handshake handling
 %%====================================================================	     
 
-renegotiate(#state{static_env = #static_env{role = client}} = State, Actions) ->
+renegotiate(#state{static_env = #static_env{role = client}} = State0, Actions) ->
     %% Handle same way as if server requested
     %% the renegotiation
+    State = reinit_handshake_data(State0),
     {next_state, connection, State,
      [{next_event, internal, #hello_request{}} | Actions]};
 
@@ -771,7 +775,7 @@ format_status(Type, Data) ->
 %%% Internal functions
 %%--------------------------------------------------------------------
 initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, _}, User,
-	      {CbModule, DataTag, CloseTag, ErrorTag}) ->
+	      {CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) ->
     #ssl_options{beast_mitigation = BeastMitigation} = SSLOptions,
     ConnectionStates = dtls_record:init_connection_states(Role, BeastMitigation),
     
@@ -781,7 +785,12 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, _}, User,
 			 _  ->
 			     ssl_session_cache
 		     end,
-    
+    InternalActiveN =  case application:get_env(ssl, internal_active_n) of
+                           {ok, N} when is_integer(N) ->
+                               N;
+                           _  ->
+                               ?INTERNAL_ACTIVE_N
+                       end,
     Monitor = erlang:monitor(process, User),
     InitStatEnv = #static_env{
                      role = Role,
@@ -790,6 +799,7 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, _}, User,
                      data_tag = DataTag,
                      close_tag = CloseTag,
                      error_tag = ErrorTag,
+                     passive_tag = PassiveTag,
                      host = Host,
                      port = Port,
                      socket = Socket,
@@ -813,7 +823,9 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, _}, User,
 	   user_data_buffer = {[],0,[]},
 	   start_or_recv_from = undefined,
 	   flight_buffer = new_flight(),
-           protocol_specific = #{flight_state => initial_flight_state(DataTag)}
+           protocol_specific = #{active_n => InternalActiveN,
+                                 active_n_toggle => true,
+                                 flight_state => initial_flight_state(DataTag)}
 	  }.
 
 initial_flight_state(udp)->
@@ -910,12 +922,21 @@ handle_info({Protocol, _, _, _, Data}, StateName,
 	    ssl_connection:handle_normal_shutdown(Alert, StateName, State0), 
             {stop, {shutdown, own_alert}, State0}
     end;
+
+handle_info({PassiveTag, Socket}, StateName,
+            #state{static_env = #static_env{socket = Socket,
+                                            passive_tag = PassiveTag},
+                   protocol_specific = PS} = State) ->
+    next_event(StateName, no_record,
+               State#state{protocol_specific = PS#{active_n_toggle => true}});
+
 handle_info({CloseTag, Socket}, StateName,
 	    #state{static_env = #static_env{socket = Socket,
                                             close_tag = CloseTag},
                    connection_env = #connection_env{negotiated_version = Version},
                    socket_options = #socket_options{active = Active},
-                   protocol_buffers = #protocol_buffers{dtls_cipher_texts = CTs}} = State) ->
+                   protocol_buffers = #protocol_buffers{dtls_cipher_texts = CTs},
+                   protocol_specific = PS} = State) ->
     %% Note that as of DTLS 1.2 (TLS 1.1),
     %% failure to properly close a connection no longer requires that a
     %% session not be resumed.	This is a change from DTLS 1.0 to conform
@@ -938,7 +959,8 @@ handle_info({CloseTag, Socket}, StateName,
             %% Fixes non-delivery of final DTLS record in {active, once}.
             %% Basically allows the application the opportunity to set {active, once} again
             %% and then receive the final message.
-            next_event(StateName, no_record, State)
+            next_event(StateName, no_record, State#state{
+                                               protocol_specific = PS#{active_n_toggle => true}})
     end;
 
 handle_info(new_cookie_secret, StateName, 
diff --git a/lib/ssl/src/dtls_packet_demux.erl b/lib/ssl/src/dtls_packet_demux.erl
index 2e9184b7ac..c6431b55a9 100644
--- a/lib/ssl/src/dtls_packet_demux.erl
+++ b/lib/ssl/src/dtls_packet_demux.erl
@@ -35,7 +35,8 @@
 	 terminate/2, code_change/3]).
 
 -record(state, 
-	{port, 
+	{active_n,
+         port,
 	 listener,
          transport,
 	 dtls_options,
@@ -76,10 +77,18 @@ set_sock_opts(PacketSocket, Opts) ->
 %%% gen_server callbacks
 %%%===================================================================
 
-init([Port, {TransportModule, _,_,_} = TransportInfo, EmOpts, InetOptions, DTLSOptions]) ->
+init([Port, {TransportModule, _,_,_,_} = TransportInfo, EmOpts, InetOptions, DTLSOptions]) ->
     try 
 	{ok, Socket} = TransportModule:open(Port, InetOptions),
-	{ok, #state{port = Port,
+        InternalActiveN =  case application:get_env(ssl, internal_active_n) of
+                               {ok, N} when is_integer(N) ->
+                                   N;
+                               _  ->
+                                   ?INTERNAL_ACTIVE_N
+                           end,
+
+	{ok, #state{active_n = InternalActiveN,
+                    port = Port,
 		    first = true,
                     transport = TransportInfo,
 		    dtls_options = DTLSOptions,
@@ -92,10 +101,11 @@ init([Port, {TransportModule, _,_,_} = TransportInfo, EmOpts, InetOptions, DTLSO
 handle_call({accept, _}, _, #state{close = true} = State) ->
     {reply, {error, closed}, State};
 
-handle_call({accept, Accepter}, From, #state{first = true,
+handle_call({accept, Accepter}, From, #state{active_n = N,
+                                             first = true,
 					     accepters = Accepters,
 					     listener = Socket} = State0) ->
-    next_datagram(Socket),
+    next_datagram(Socket, N),
     State = State0#state{first = false,
 			 accepters = queue:in({Accepter, From}, Accepters)}, 		 
     {noreply, State};
@@ -137,19 +147,24 @@ handle_cast({active_once, Client, Pid}, State0) ->
     State = handle_active_once(Client, Pid, State0),
     {noreply, State}.
 
-handle_info({Transport, Socket, IP, InPortNo, _} = Msg, #state{listener = Socket, transport = {_,Transport,_,_}} = State0) ->
+handle_info({Transport, Socket, IP, InPortNo, _} = Msg, #state{listener = Socket, transport = {_,Transport,_,_,_}} = State0) ->
     State = handle_datagram({IP, InPortNo}, Msg, State0),
-    next_datagram(Socket),
     {noreply, State};
 
+handle_info({PassiveTag, Socket},
+            #state{active_n = N,
+                   listener = Socket,
+                   transport = {_,_,_, udp_error, PassiveTag}}) ->
+    next_datagram(Socket, N);
+
 %% UDP socket does not have a connection and should not receive an econnreset
 %% This does however happens on some windows versions. Just ignoring it
 %% appears to make things work as expected! 
-handle_info({udp_error, Socket, econnreset = Error}, #state{listener = Socket, transport = {_,_,_, udp_error}} = State) ->
+handle_info({udp_error, Socket, econnreset = Error}, #state{listener = Socket, transport = {_,_,_, udp_error,_}} = State) ->
     Report = io_lib:format("Ignore SSL UDP Listener: Socket error: ~p ~n", [Error]),
     ?LOG_NOTICE(Report),
     {noreply, State};
-handle_info({ErrorTag, Socket, Error}, #state{listener = Socket, transport = {_,_,_, ErrorTag}} = State) ->
+handle_info({ErrorTag, Socket, Error}, #state{listener = Socket, transport = {_,_,_, ErrorTag,_}} = State) ->
     Report = io_lib:format("SSL Packet muliplxer shutdown: Socket error: ~p ~n", [Error]),
     ?LOG_NOTICE(Report),
     {noreply, State#state{close=true}};
@@ -211,8 +226,8 @@ dispatch(Client, Msg, #state{dtls_msq_queues = MsgQueues} = State) ->
 				    kv_update(Client, queue:in(Msg, Queue), MsgQueues)}
 	    end
     end.
-next_datagram(Socket) ->
-    inet:setopts(Socket, [{active, once}]).
+next_datagram(Socket, N) ->
+    inet:setopts(Socket, [{active, N}]).
 
 handle_active_once(Client, Pid, #state{dtls_msq_queues = MsgQueues} = State0) ->
     Queue0 = kv_get(Client, MsgQueues),
diff --git a/lib/ssl/src/dtls_socket.erl b/lib/ssl/src/dtls_socket.erl
index 4d07372e31..b305d08f70 100644
--- a/lib/ssl/src/dtls_socket.erl
+++ b/lib/ssl/src/dtls_socket.erl
@@ -45,7 +45,7 @@ listen(Port, #config{transport_info = TransportInfo,
 	    Err
     end.
 
-accept(dtls, #config{transport_info = {Transport,_,_,_},
+accept(dtls, #config{transport_info = {Transport,_,_,_,_},
 		    connection_cb = ConnectionCb,
 		    dtls_handler = {Listner, _}}, _Timeout) -> 
     case dtls_packet_demux:accept(Listner, self()) of
@@ -55,7 +55,7 @@ accept(dtls, #config{transport_info = {Transport,_,_,_},
 	    {error, Reason}
     end.
 
-connect(Address, Port, #config{transport_info = {Transport, _, _, _} = CbInfo,
+connect(Address, Port, #config{transport_info = {Transport, _, _, _, _} = CbInfo,
 				connection_cb = ConnectionCb,
 				ssl = SslOpts,
 				emulated = EmOpts,
@@ -174,7 +174,7 @@ default_inet_values() ->
     [{active, true}, {mode, list}, {packet, 0}, {packet_size, 0}].
 
 default_cb_info() ->
-    {gen_udp, udp, udp_closed, udp_error}.
+    {gen_udp, udp, udp_closed, udp_error, udp_passive}.
 
 get_emulated_opts(EmOpts, EmOptNames) -> 
     lists:map(fun(Name) -> {value, Value} = lists:keysearch(Name, 1, EmOpts),
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index 8807c575b1..998d549e52 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -929,7 +929,7 @@ groups(default) ->
 %%--------------------------------------------------------------------
 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) ->
+getopts(#sslsocket{pid = {dtls, #config{transport_info = {Transport,_,_,_,_}}}} = ListenSocket, OptionTags) when is_list(OptionTags) ->
     try dtls_socket:getopts(Transport, ListenSocket, OptionTags) of
         {ok, _} = Result ->
             Result;
@@ -986,7 +986,7 @@ setopts(#sslsocket{pid = [Pid|_]}, Options0) when is_pid(Pid), is_list(Options0)
 	_:_ ->
 	    {error, {options, {not_a_proplist, Options0}}}
     end;
-setopts(#sslsocket{pid = {dtls, #config{transport_info = {Transport,_,_,_}}}} = ListenSocket, Options) when is_list(Options) ->
+setopts(#sslsocket{pid = {dtls, #config{transport_info = {Transport,_,_,_,_}}}} = ListenSocket, Options) when is_list(Options) ->
     try dtls_socket:setopts(Transport, ListenSocket, Options) of
 	ok ->
 	    ok;
@@ -2141,7 +2141,7 @@ default_option_role(_,_,_) ->
 default_cb_info(tls) ->
     {gen_tcp, tcp, tcp_closed, tcp_error, tcp_passive};
 default_cb_info(dtls) ->
-    {gen_udp, udp, udp_closed, udp_error}.
+    {gen_udp, udp, udp_closed, udp_error, udp_passive}.
 
 include_security_info([]) ->
     false;
-- 
cgit v1.2.3