aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2018-12-24 16:20:18 +0100
committerLoïc Hoguin <[email protected]>2019-02-19 09:59:49 +0100
commit09276f8cbe1b62ea0c1c44c5d6eb605fd49dd80a (patch)
tree753c02566e80c33147fbe66d4a88923362dbb47d
parent579cc8e2b4ed9283c9c9422b928bbaa6996ff884 (diff)
downloadotp-09276f8cbe1b62ea0c1c44c5d6eb605fd49dd80a.tar.gz
otp-09276f8cbe1b62ea0c1c44c5d6eb605fd49dd80a.tar.bz2
otp-09276f8cbe1b62ea0c1c44c5d6eb605fd49dd80a.zip
ssl: Add support for {active,N}
-rw-r--r--lib/ssl/src/dtls_packet_demux.erl12
-rw-r--r--lib/ssl/src/dtls_socket.erl49
-rw-r--r--lib/ssl/src/ssl_connection.erl48
-rw-r--r--lib/ssl/src/tls_socket.erl52
-rw-r--r--lib/ssl/test/ssl_basic_SUITE.erl176
5 files changed, 327 insertions, 10 deletions
diff --git a/lib/ssl/src/dtls_packet_demux.erl b/lib/ssl/src/dtls_packet_demux.erl
index dccc22a448..4945ef3c10 100644
--- a/lib/ssl/src/dtls_packet_demux.erl
+++ b/lib/ssl/src/dtls_packet_demux.erl
@@ -297,6 +297,18 @@ do_set_emulated_opts([], Opts) ->
Opts;
do_set_emulated_opts([{mode, Value} | Rest], Opts) ->
do_set_emulated_opts(Rest, Opts#socket_options{mode = Value});
+do_set_emulated_opts([{active, N0} | Rest], Opts=#socket_options{active = Active}) when is_integer(N0) ->
+ N = case Active of
+ N1 when is_integer(N1), N0 + N1 =< 0 ->
+ false;
+ N1 when is_integer(N1) ->
+ N0 + N1;
+ _ when N0 =< 0 ->
+ false;
+ _ ->
+ N0
+ end,
+ do_set_emulated_opts(Rest, Opts#socket_options{active = N});
do_set_emulated_opts([{active, Value} | Rest], Opts) ->
do_set_emulated_opts(Rest, Opts#socket_options{active = Value}).
diff --git a/lib/ssl/src/dtls_socket.erl b/lib/ssl/src/dtls_socket.erl
index 2001afd02f..4d07372e31 100644
--- a/lib/ssl/src/dtls_socket.erl
+++ b/lib/ssl/src/dtls_socket.erl
@@ -38,7 +38,9 @@ listen(Port, #config{transport_info = TransportInfo,
case dtls_listener_sup:start_child([Port, TransportInfo, emulated_socket_options(EmOpts, #socket_options{}),
Options ++ internal_inet_values(), SslOpts]) of
{ok, Pid} ->
- {ok, #sslsocket{pid = {dtls, Config#config{dtls_handler = {Pid, Port}}}}};
+ Socket = #sslsocket{pid = {dtls, Config#config{dtls_handler = {Pid, Port}}}},
+ check_active_n(EmOpts, Socket),
+ {ok, Socket};
Err = {error, _} ->
Err
end.
@@ -81,8 +83,9 @@ 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) ->
- SplitOpts = tls_socket:split_options(Options),
+setopts(_, Socket = #sslsocket{pid = {dtls, #config{dtls_handler = {ListenPid, _}}}}, Options) ->
+ SplitOpts = {_, EmOpts} = tls_socket:split_options(Options),
+ check_active_n(EmOpts, Socket),
dtls_packet_demux:set_sock_opts(ListenPid, SplitOpts);
%%% Following clauses will not be called for emulated options, they are handled in the connection process
setopts(gen_udp, Socket, Options) ->
@@ -90,6 +93,32 @@ setopts(gen_udp, Socket, Options) ->
setopts(Transport, Socket, Options) ->
Transport:setopts(Socket, Options).
+check_active_n(EmulatedOpts, Socket = #sslsocket{pid = {dtls, #config{dtls_handler = {ListenPid, _}}}}) ->
+ %% We check the resulting options to send an ssl_passive message if necessary.
+ case proplists:lookup(active, EmulatedOpts) of
+ %% The provided value is out of bound.
+ {_, N} when is_integer(N), N < -32768 ->
+ throw(einval);
+ {_, N} when is_integer(N), N > 32767 ->
+ throw(einval);
+ {_, N} when is_integer(N) ->
+ {ok, #socket_options{active = Active}, _} = dtls_packet_demux:get_all_opts(ListenPid),
+ case Active of
+ Atom when is_atom(Atom), N =< 0 ->
+ self() ! {ssl_passive, Socket};
+ %% The result of the addition is out of bound.
+ %% We do not need to check < -32768 because Active can't be below 1.
+ A when is_integer(A), A + N > 32767 ->
+ throw(einval);
+ A when is_integer(A), A + N =< 0 ->
+ self() ! {ssl_passive, Socket};
+ _ ->
+ ok
+ end;
+ _ ->
+ ok
+ end.
+
getopts(_, #sslsocket{pid = {dtls, #config{dtls_handler = {ListenPid, _}}}}, Options) ->
SplitOpts = tls_socket:split_options(Options),
dtls_packet_demux:get_sock_opts(ListenPid, SplitOpts);
@@ -161,9 +190,18 @@ emulated_socket_options(InetValues, #socket_options{
mode = proplists:get_value(mode, InetValues, Mode),
packet = proplists:get_value(packet, InetValues, Packet),
packet_size = proplists:get_value(packet_size, InetValues, PacketSize),
- active = proplists:get_value(active, InetValues, Active)
+ active = emulated_active_option(InetValues, Active)
}.
+emulated_active_option([], Active) ->
+ Active;
+emulated_active_option([{active, Active} | _], _) when Active =< 0 ->
+ false;
+emulated_active_option([{active, Active} | _], _) ->
+ Active;
+emulated_active_option([_|Tail], Active) ->
+ emulated_active_option(Tail, Active).
+
emulated_options([{mode, Value} = Opt |Opts], Inet, Emulated) ->
validate_inet_option(mode, Value),
emulated_options(Opts, Inet, [Opt | proplists:delete(mode, Emulated)]);
@@ -185,6 +223,9 @@ validate_inet_option(mode, Value)
when Value =/= list, Value =/= binary ->
throw({error, {options, {mode,Value}}});
validate_inet_option(active, Value)
+ when Value >= -32768, Value =< 32767 ->
+ ok;
+validate_inet_option(active, Value)
when Value =/= true, Value =/= false, Value =/= once ->
throw({error, {options, {active,Value}}});
validate_inet_option(_, _) ->
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index 6206d15c13..9e0b0a40c2 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -1258,10 +1258,21 @@ handle_call({get_opts, OptTags}, From, _,
{keep_state_and_data, [{reply, From, OptsReply}]};
handle_call({set_opts, Opts0}, From, StateName,
#state{static_env = #static_env{socket = Socket,
- transport_cb = Transport},
+ transport_cb = Transport,
+ tracker = Tracker},
+ user_application = {_Mon, Pid},
socket_options = Opts1
} = State0, Connection) ->
{Reply, Opts} = set_socket_opts(Connection, Transport, Socket, Opts0, Opts1, []),
+ case {proplists:lookup(active, Opts0), Opts} of
+ {{_, N}, #socket_options{active=false}} when is_integer(N) ->
+ send_user(
+ Pid,
+ format_passive(
+ Connection:pids(State0), Transport, Socket, Tracker, Connection));
+ _ ->
+ ok
+ end,
State = State0#state{socket_options = Opts},
handle_active_option(Opts#socket_options.active, StateName, From, Reply, State);
@@ -2336,6 +2347,30 @@ set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active}| Opts], SockO
Active == false ->
set_socket_opts(ConnectionCb, Transport, Socket, Opts,
SockOpts#socket_options{active = Active}, Other);
+set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active1} = Opt| Opts],
+ SockOpts=#socket_options{active = Active0}, Other)
+ when Active1 >= -32768, Active1 =< 32767 ->
+ Active = if
+ is_integer(Active0), Active0 + Active1 < -32768 ->
+ error;
+ is_integer(Active0), Active0 + Active1 =< 0 ->
+ false;
+ is_integer(Active0), Active0 + Active1 > 32767 ->
+ error;
+ Active1 =< 0 ->
+ false;
+ is_integer(Active0) ->
+ Active0 + Active1;
+ true ->
+ Active1
+ end,
+ case Active of
+ error ->
+ {{error, {options, {socket_options, Opt}} }, SockOpts};
+ _ ->
+ set_socket_opts(ConnectionCb, Transport, Socket, Opts,
+ SockOpts#socket_options{active = Active}, Other)
+ end;
set_socket_opts(_,_, _, [{active, _} = Opt| _], SockOpts, _) ->
{{error, {options, {socket_options, Opt}} }, SockOpts};
set_socket_opts(ConnectionCb, Transport, Socket, [Opt | Opts], SockOpts, Other) ->
@@ -2639,6 +2674,14 @@ deliver_app_data(
case Active of
once ->
SO#socket_options{active=false};
+ 1 ->
+ send_user(
+ Pid,
+ format_passive(
+ CPids, Transport, Socket, Tracker, Connection)),
+ SO#socket_options{active=false};
+ N when is_integer(N) ->
+ SO#socket_options{active=N - 1};
_ ->
SO
end.
@@ -2675,6 +2718,9 @@ do_format_reply(list, Packet, _, Data)
do_format_reply(list, _,_, Data) ->
binary_to_list(Data).
+format_passive(CPids, Transport, Socket, Tracker, Connection) ->
+ {ssl_passive, Connection:socket(CPids, Transport, Socket, Connection, Tracker)}.
+
header(0, <<>>) ->
<<>>;
header(_, <<>>) ->
diff --git a/lib/ssl/src/tls_socket.erl b/lib/ssl/src/tls_socket.erl
index a391bc53de..9794ba39de 100644
--- a/lib/ssl/src/tls_socket.erl
+++ b/lib/ssl/src/tls_socket.erl
@@ -51,7 +51,9 @@ listen(Transport, Port, #config{transport_info = {Transport, _, _, _},
case Transport:listen(Port, Options ++ internal_inet_values()) of
{ok, ListenSocket} ->
{ok, Tracker} = inherit_tracker(ListenSocket, EmOpts, SslOpts),
- {ok, #sslsocket{pid = {ListenSocket, Config#config{emulated = Tracker}}}};
+ Socket = #sslsocket{pid = {ListenSocket, Config#config{emulated = Tracker}}},
+ check_active_n(EmOpts, Socket),
+ {ok, Socket};
Err = {error, _} ->
Err
end.
@@ -117,14 +119,16 @@ 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) ->
+setopts(gen_tcp, Socket = #sslsocket{pid = {ListenSocket, #config{emulated = Tracker}}}, Options) ->
{SockOpts, EmulatedOpts} = split_options(Options),
ok = set_emulated_opts(Tracker, EmulatedOpts),
+ check_active_n(EmulatedOpts, Socket),
inet:setopts(ListenSocket, SockOpts);
-setopts(_, #sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_},
+setopts(_, Socket = #sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_},
emulated = Tracker}}}, Options) ->
{SockOpts, EmulatedOpts} = split_options(Options),
ok = set_emulated_opts(Tracker, EmulatedOpts),
+ check_active_n(EmulatedOpts, Socket),
Transport:setopts(ListenSocket, SockOpts);
%%% Following clauses will not be called for emulated options, they are handled in the connection process
setopts(gen_tcp, Socket, Options) ->
@@ -132,6 +136,31 @@ setopts(gen_tcp, Socket, Options) ->
setopts(Transport, Socket, Options) ->
Transport:setopts(Socket, Options).
+check_active_n(EmulatedOpts, Socket = #sslsocket{pid = {_, #config{emulated = Tracker}}}) ->
+ %% We check the resulting options to send an ssl_passive message if necessary.
+ case proplists:lookup(active, EmulatedOpts) of
+ %% The provided value is out of bound.
+ {_, N} when is_integer(N), N < -32768 ->
+ throw(einval);
+ {_, N} when is_integer(N), N > 32767 ->
+ throw(einval);
+ {_, N} when is_integer(N) ->
+ case get_emulated_opts(Tracker, [active]) of
+ [{_, false}] ->
+ self() ! {ssl_passive, Socket},
+ ok;
+ %% The result of the addition is out of bound.
+ [{_, A}] when is_integer(A), A < -32768 ->
+ throw(einval);
+ [{_, A}] when is_integer(A), A > 32767 ->
+ throw(einval);
+ _ ->
+ ok
+ end;
+ _ ->
+ ok
+ end.
+
getopts(gen_tcp, #sslsocket{pid = {ListenSocket, #config{emulated = Tracker}}}, Options) ->
{SockOptNames, EmulatedOptNames} = split_options(Options),
EmulatedOpts = get_emulated_opts(Tracker, EmulatedOptNames),
@@ -209,7 +238,7 @@ start_link(Port, SockOpts, SslOpts) ->
init([Port, Opts, SslOpts]) ->
process_flag(trap_exit, true),
true = link(Port),
- {ok, #state{emulated_opts = Opts, port = Port, ssl_opts = SslOpts}}.
+ {ok, #state{emulated_opts = do_set_emulated_opts(Opts, []), port = Port, ssl_opts = SslOpts}}.
%%--------------------------------------------------------------------
-spec handle_call(msg(), from(), #state{}) -> {reply, reply(), #state{}}.
@@ -304,6 +333,18 @@ split_options([Name | Opts], Emu, SocketOptNames, EmuOptNames) ->
do_set_emulated_opts([], Opts) ->
Opts;
+do_set_emulated_opts([{active, N0} | Rest], Opts) when is_integer(N0) ->
+ N = case proplists:lookup(active, Opts) of
+ {_, N1} when is_integer(N1), N0 + N1 =< 0 ->
+ false;
+ {_, N1} when is_integer(N1) ->
+ N0 + N1;
+ _ when N0 =< 0 ->
+ false;
+ _ ->
+ N0
+ end,
+ do_set_emulated_opts(Rest, [{active, N} | proplists:delete(active, Opts)]);
do_set_emulated_opts([{Name,_} = Opt | Rest], Opts) ->
do_set_emulated_opts(Rest, [Opt | proplists:delete(Name, Opts)]).
@@ -366,6 +407,9 @@ validate_inet_option(header, Value)
when not is_integer(Value) ->
throw({error, {options, {header,Value}}});
validate_inet_option(active, Value)
+ when Value >= -32768, Value =< 32767 ->
+ ok;
+validate_inet_option(active, Value)
when Value =/= true, Value =/= false, Value =/= once ->
throw({error, {options, {active,Value}}});
validate_inet_option(_, _) ->
diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl
index d0fa0443f2..8fe28bd901 100644
--- a/lib/ssl/test/ssl_basic_SUITE.erl
+++ b/lib/ssl/test/ssl_basic_SUITE.erl
@@ -164,6 +164,7 @@ api_tests() ->
accept_pool,
prf,
socket_options,
+ active_n,
cipher_suites,
handshake_continue,
handshake_continue_timeout,
@@ -243,6 +244,7 @@ error_handling_tests()->
[close_transport_accept,
recv_active,
recv_active_once,
+ recv_active_n,
recv_error_handling,
call_in_error_state,
close_in_error_state,
@@ -1978,7 +1980,7 @@ recv_active(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
recv_active_once() ->
- [{doc,"Test recv on active socket"}].
+ [{doc,"Test recv on active (once) socket"}].
recv_active_once(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
@@ -2003,6 +2005,178 @@ recv_active_once(Config) when is_list(Config) ->
ssl_test_lib:close(Client).
%%--------------------------------------------------------------------
+recv_active_n() ->
+ [{doc,"Test recv on active (n) socket"}].
+
+recv_active_n(Config) when is_list(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, try_recv_active_once, []}},
+ {options, [{active, 1} | ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client =
+ ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, try_recv_active_once, []}},
+ {options, [{active, 1} | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+%%--------------------------------------------------------------------
+%% Test case adapted from gen_tcp_misc_SUITE.
+active_n() ->
+ [{doc,"Test {active,N} option"}].
+
+active_n(Config) when is_list(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
+ Port = ssl_test_lib:inet_port(node()),
+ N = 3,
+ LS = ok(ssl:listen(Port, [{active,N}|ServerOpts])),
+ [{active,N}] = ok(ssl:getopts(LS, [active])),
+ active_n_common(LS, N),
+ Self = self(),
+ spawn_link(fun() ->
+ S0 = ok(ssl:transport_accept(LS)),
+ {ok, S} = ssl:handshake(S0),
+ ok = ssl:setopts(S, [{active,N}]),
+ [{active,N}] = ok(ssl:getopts(S, [active])),
+ ssl:controlling_process(S, Self),
+ Self ! {server, S}
+ end),
+ C = ok(ssl:connect("localhost", Port, [{active,N}|ClientOpts])),
+ [{active,N}] = ok(ssl:getopts(C, [active])),
+ S = receive
+ {server, S0} -> S0
+ after
+ 1000 ->
+ exit({error, connect})
+ end,
+ active_n_common(C, N),
+ active_n_common(S, N),
+ ok = ssl:setopts(C, [{active,N}]),
+ ok = ssl:setopts(S, [{active,N}]),
+ ReceiveMsg = fun(Socket, Msg) ->
+ receive
+ {ssl,Socket,Msg} ->
+ ok;
+ {ssl,Socket,Begin} ->
+ receive
+ {ssl,Socket,End} ->
+ Msg = Begin ++ End,
+ ok
+ after 1000 ->
+ exit(timeout)
+ end
+ after 1000 ->
+ exit(timeout)
+ end
+ end,
+ repeat(3, fun(I) ->
+ Msg = "message "++integer_to_list(I),
+ ok = ssl:send(C, Msg),
+ ReceiveMsg(S, Msg),
+ ok = ssl:send(S, Msg),
+ ReceiveMsg(C, Msg)
+ end),
+ receive
+ {ssl_passive,S} ->
+ [{active,false}] = ok(ssl:getopts(S, [active]))
+ after
+ 1000 ->
+ exit({error,ssl_passive})
+ end,
+ receive
+ {ssl_passive,C} ->
+ [{active,false}] = ok(ssl:getopts(C, [active]))
+ after
+ 1000 ->
+ exit({error,ssl_passive})
+ end,
+ LS2 = ok(ssl:listen(0, [{active,0}])),
+ receive
+ {ssl_passive,LS2} ->
+ [{active,false}] = ok(ssl:getopts(LS2, [active]))
+ after
+ 1000 ->
+ exit({error,ssl_passive})
+ end,
+ ok = ssl:close(LS2),
+ ok = ssl:close(C),
+ ok = ssl:close(S),
+ ok = ssl:close(LS),
+ ok.
+
+active_n_common(S, N) ->
+ ok = ssl:setopts(S, [{active,-N}]),
+ receive
+ {ssl_passive, S} -> ok
+ after
+ 1000 ->
+ error({error,ssl_passive_failure})
+ end,
+ [{active,false}] = ok(ssl:getopts(S, [active])),
+ ok = ssl:setopts(S, [{active,0}]),
+ receive
+ {ssl_passive, S} -> ok
+ after
+ 1000 ->
+ error({error,ssl_passive_failure})
+ end,
+ ok = ssl:setopts(S, [{active,32767}]),
+ {error,{options,_}} = ssl:setopts(S, [{active,1}]),
+ {error,{options,_}} = ssl:setopts(S, [{active,-32769}]),
+ ok = ssl:setopts(S, [{active,-32768}]),
+ receive
+ {ssl_passive, S} -> ok
+ after
+ 1000 ->
+ error({error,ssl_passive_failure})
+ end,
+ [{active,false}] = ok(ssl:getopts(S, [active])),
+ ok = ssl:setopts(S, [{active,N}]),
+ ok = ssl:setopts(S, [{active,true}]),
+ [{active,true}] = ok(ssl:getopts(S, [active])),
+ receive
+ _ -> error({error,active_n})
+ after
+ 0 ->
+ ok
+ end,
+ ok = ssl:setopts(S, [{active,N}]),
+ ok = ssl:setopts(S, [{active,once}]),
+ [{active,once}] = ok(ssl:getopts(S, [active])),
+ receive
+ _ -> error({error,active_n})
+ after
+ 0 ->
+ ok
+ end,
+ {error,{options,_}} = ssl:setopts(S, [{active,32768}]),
+ ok = ssl:setopts(S, [{active,false}]),
+ [{active,false}] = ok(ssl:getopts(S, [active])),
+ ok.
+
+ok({ok,V}) -> V.
+
+repeat(N, Fun) ->
+ repeat(N, N, Fun).
+
+repeat(N, T, Fun) when is_integer(N), N > 0 ->
+ Fun(T-N),
+ repeat(N-1, T, Fun);
+repeat(_, _, _) ->
+ ok.
+
+%%--------------------------------------------------------------------
dh_params() ->
[{doc,"Test to specify DH-params file in server."}].