Skip to content

Commit

Permalink
Merge branch 'dgud/ssl/dtls/GH-7955/OTP-19037' into maint-26
Browse files Browse the repository at this point in the history
* dgud/ssl/dtls/GH-7955/OTP-19037:
  Add testcases.
  Monitor dtls listen caller
  Fix dtls listen on ipv6 with default ip
  • Loading branch information
Erlang/OTP committed Apr 12, 2024
2 parents 56aba08 + 1b02453 commit 4f1a24b
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 55 deletions.
59 changes: 38 additions & 21 deletions lib/ssl/src/dtls_packet_demux.erl
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@
-include_lib("kernel/include/logger.hrl").

%% API
-export([start_link/5,
-export([start_link/6,
active_once/3,
accept/2,
sockname/1,
close/1,
new_owner/1,
new_owner/2,
new_connection/2,
connection_setup/2,
get_all_opts/1,
Expand All @@ -50,7 +50,7 @@
terminate/2,
code_change/3]).

-record(state,
-record(state,
{active_n,
port,
listener,
Expand All @@ -60,6 +60,7 @@
dtls_msq_queues = kv_new(),
dtls_processes = kv_new(),
accepters = queue:new(),
owner, %% Listen process owner
first,
close,
session_id_tracker
Expand All @@ -69,8 +70,8 @@
%%% API
%%%===================================================================

start_link(Port, TransportInfo, EmOpts, InetOptions, DTLSOptions) ->
gen_server:start_link(?MODULE, [Port, TransportInfo, EmOpts, InetOptions, DTLSOptions], []).
start_link(Owner, Port, TransportInfo, EmOpts, InetOptions, DTLSOptions) ->
gen_server:start_link(?MODULE, [Owner, Port, TransportInfo, EmOpts, InetOptions, DTLSOptions], []).

active_once(PacketSocket, Client, Pid) ->
gen_server:cast(PacketSocket, {active_once, Client, Pid}).
Expand All @@ -84,8 +85,8 @@ sockname(PacketSocket) ->
close(PacketSocket) ->
call(PacketSocket, close).

new_owner(PacketSocket) ->
call(PacketSocket, new_owner).
new_owner(PacketSocket, Owner) ->
call(PacketSocket, {new_owner, Owner}).

new_connection(PacketSocket, Client) ->
call(PacketSocket, {new_connection, Client, self()}).
Expand All @@ -110,8 +111,9 @@ getstat(PacketSocket, Opts) ->
%%% gen_server callbacks
%%%===================================================================

init([Port0, TransportInfo, EmOpts, DTLSOptions, Socket]) ->
init([Owner, Port0, TransportInfo, EmOpts, DTLSOptions, Socket]) ->
InternalActiveN = get_internal_active_n(),
erlang:monitor(process, Owner),
{ok, SessionIdHandle} = session_id_tracker(Socket, DTLSOptions),
{ok, #state{active_n = InternalActiveN,
port = Port0,
Expand All @@ -120,6 +122,7 @@ init([Port0, TransportInfo, EmOpts, DTLSOptions, Socket]) ->
dtls_options = DTLSOptions,
emulated_options = EmOpts,
listener = Socket,
owner = Owner,
close = false,
session_id_tracker = SessionIdHandle}}.

Expand All @@ -141,19 +144,15 @@ handle_call({accept, Accepter}, From, #state{accepters = Accepters} = State0) ->
handle_call(sockname, _, #state{listener = Socket} = State) ->
Reply = inet:sockname(Socket),
{reply, Reply, State};
handle_call(close, _, #state{dtls_processes = Processes,
accepters = Accepters} = State) ->
case kv_empty(Processes) of
true ->
{stop, normal, ok, State#state{close=true}};
false ->
lists:foreach(fun({_, From}) ->
gen_server:reply(From, {error, closed})
end, queue:to_list(Accepters)),
{reply, ok, State#state{close = true, accepters = queue:new()}}
handle_call(close, _, State0) ->
case do_close(State0) of
{stop, State} ->
{stop, normal, ok, State};
{wait, State} ->
{reply, ok, State}
end;
handle_call(new_owner, _, State) ->
{reply, ok, State#state{close = false, first = true}};
handle_call({new_owner, Owner}, _, State) ->
{reply, ok, State#state{close = false, first = true, owner = Owner}};
handle_call({new_connection, Old, _Pid}, _,
#state{accepters = Accepters, dtls_msq_queues = MsgQs0} = State) ->
case queue:is_empty(Accepters) of
Expand All @@ -167,7 +166,7 @@ handle_call({new_connection, Old, _Pid}, _,
end;

handle_call({get_sock_opts, {SocketOptNames, EmOptNames}}, _, #state{listener = Socket,
emulated_options = EmOpts} = State) ->
emulated_options = EmOpts} = State) ->
case get_socket_opts(Socket, SocketOptNames) of
{ok, Opts} ->
{reply, {ok, emulated_opts_list(EmOpts, EmOptNames, []) ++ Opts}, State};
Expand Down Expand Up @@ -223,6 +222,13 @@ handle_info({ErrorTag, Socket, Error}, #state{listener = Socket, transport = {_,
?LOG_NOTICE(Report),
{noreply, State#state{close=true}};

handle_info({'DOWN', _, process, Owner, _}, #state{owner = Owner} = State0) ->
case do_close(State0) of
{stop, State} ->
{stop, normal, State};
{wait, State} ->
{noreply, State}
end;
handle_info({'DOWN', _, process, Pid, _},
#state{dtls_processes = Processes0,
dtls_msq_queues = MsgQueues0,
Expand Down Expand Up @@ -264,6 +270,17 @@ code_change(_OldVsn, State, _Extra) ->
%%%===================================================================
%%% Internal functions
%%%===================================================================

do_close(#state{dtls_processes = Processes, accepters = Accepters} = State) ->
case kv_empty(Processes) of
true ->
{stop, State#state{close=true}};
false ->
lists:foreach(fun({_, From}) -> gen_server:reply(From, {error, closed}) end,
queue:to_list(Accepters)),
{wait, State#state{close = true, accepters = queue:new()}}
end.

handle_datagram(Client, Msg, #state{dtls_msq_queues = MsgQueues, accepters = AcceptorsQueue0} = State) ->
case kv_lookup(Client, MsgQueues) of
none ->
Expand Down
27 changes: 15 additions & 12 deletions lib/ssl/src/dtls_socket.erl
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ listen(Port, #config{inet_ssl = SockOpts,
ssl = SslOpts,
emulated = EmOpts,
inet_user = Options} = Config) ->
IP = proplists:get_value(ip, SockOpts, {0,0,0,0}),
IP = proplists:get_value(ip, SockOpts, default_ip(SockOpts)),
case dtls_listener_sup:lookup_listener(IP, Port) of
undefined ->
start_new_listener(IP, Port, Config);
start_new_listener(IP, Port, self(), Config);
{ok, Listener} ->
dtls_packet_demux:new_owner(Listener),
dtls_packet_demux:new_owner(Listener, self()),
dtls_packet_demux:set_all_opts(
Listener, {Options,
emulated_socket_options(EmOpts,
Expand Down Expand Up @@ -95,13 +95,17 @@ connect(Address, Port, #config{transport_info = {Transport, _, _, _, _} = CbInfo

close(#sslsocket{pid = {dtls, #config{dtls_handler = {Pid, Port0},
inet_ssl = SockOpts}}}) ->
IP = proplists:get_value(ip, SockOpts, {0,0,0,0}),
IP = proplists:get_value(ip, SockOpts, default_ip(SockOpts)),
Port = get_real_port(Pid, Port0),
dtls_listener_sup:register_listener({undefined, Pid}, IP, Port),
dtls_packet_demux:close(Pid).
dtls_packet_demux:close(Pid).

default_ip(SockOpts) ->
case proplists:get_value(inet6, SockOpts, false) of
false -> {0,0,0,0};
true -> {0,0,0,0, 0,0,0,0}
end.

close(_, dtls) ->
ok;
close(gen_udp, {_Client, _Socket}) ->
ok;
close(Transport, {_Client, Socket}) ->
Expand Down Expand Up @@ -283,7 +287,7 @@ get_real_port(Listener, Port0) when is_pid(Listener) andalso
Port0
end.

start_new_listener(IP, Port0,
start_new_listener(IP, Port0, Owner,
#config{transport_info = {TransportModule, _,_,_,_},
inet_user = Options} = Config) ->
InetOptions = Options ++ internal_inet_values(),
Expand All @@ -296,7 +300,7 @@ start_new_listener(IP, Port0,
_ ->
Port0
end,
start_dtls_packet_demux(Config, IP, Port, Socket);
start_dtls_packet_demux(Config, IP, Port, Socket, Owner);
{error, eaddrinuse} ->
{error, already_listening};
Error ->
Expand All @@ -307,10 +311,9 @@ start_dtls_packet_demux(#config{
transport_info =
{TransportModule, _,_,_,_} = TransportInfo,
emulated = EmOpts0,
ssl = SslOpts} = Config, IP, Port, Socket) ->
ssl = SslOpts} = Config, IP, Port, Socket, Owner) ->
EmOpts = emulated_socket_options(EmOpts0, #socket_options{}),
case dtls_listener_sup:start_child([Port, TransportInfo, EmOpts,
SslOpts, Socket]) of
case dtls_listener_sup:start_child([Owner, Port, TransportInfo, EmOpts, SslOpts, Socket]) of
{ok, Multiplexer} ->
ok = TransportModule:controlling_process(Socket, Multiplexer),
dtls_listener_sup:register_listener({self(), Multiplexer},
Expand Down
4 changes: 2 additions & 2 deletions lib/ssl/src/ssl.erl
Original file line number Diff line number Diff line change
Expand Up @@ -875,8 +875,8 @@ close(#sslsocket{pid = [TLSPid|_]},
close(#sslsocket{pid = [TLSPid|_]}, Timeout) when is_pid(TLSPid),
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
ssl_gen_statem:close(TLSPid, {close, Timeout});
close(#sslsocket{pid = {dtls = ListenSocket, #config{transport_info={Transport,_,_,_,_}}}}, _) ->
dtls_socket:close(Transport, ListenSocket);
close(#sslsocket{pid = {dtls, #config{dtls_handler = {_, _}}}} = DTLSListen, _) ->
dtls_socket:close(DTLSListen);
close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_,_,_,_}}}}, _) ->
tls_socket:close(Transport, ListenSocket).

Expand Down
86 changes: 66 additions & 20 deletions lib/ssl/test/dtls_api_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
dtls_listen_close/1,
dtls_listen_reopen/0,
dtls_listen_reopen/1,
dtls_listen_both_family/0,
dtls_listen_both_family/1,
dtls_listen_two_sockets_1/0,
dtls_listen_two_sockets_1/1,
dtls_listen_two_sockets_2/0,
Expand Down Expand Up @@ -80,6 +82,7 @@ api_tests() ->
dtls_listen_owner_dies,
dtls_listen_close,
dtls_listen_reopen,
dtls_listen_both_family,
dtls_listen_two_sockets_1,
dtls_listen_two_sockets_2,
dtls_listen_two_sockets_3,
Expand Down Expand Up @@ -139,54 +142,60 @@ end_per_testcase(_TestCase, Config) ->
%%--------------------------------------------------------------------

dtls_listen_owner_dies() ->
[{doc, "Test that you can start new DTLS 'listner' if old owner dies"}].
[{doc, "Test that you can start new DTLS 'listener' if old owner dies"}].

dtls_listen_owner_dies(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),

Port = ssl_test_lib:inet_port(ServerNode),
Test = self(),
Pid = spawn(fun() -> {ok, _} =
ssl:listen(Port, [{protocol, dtls} | ServerOpts]),
{error, _} = ssl:listen(Port, [{protocol, dtls} | ServerOpts]),
Test ! {self(), listened}
end),
{Pid, Ref} = spawn_monitor(fun() -> {ok, _} =
ssl:listen(Port, [{protocol, dtls} | ServerOpts]),
[_] = listener_and_ports(),
{error, _} = ssl:listen(Port, [{protocol, dtls} | ServerOpts])
end),
receive
{Pid, listened} ->
{'DOWN', Ref, _, Pid, _} ->
ok
end,
[] = listener_and_ports(), %% Verify that ports are cleaned up after listener owner dies
{ok, LSocket} = ssl:listen(Port, [{protocol, dtls} | ServerOpts]),
spawn(fun() ->
[_] = listener_and_ports(),
spawn(fun() ->
{ok, ASocket} = ssl:transport_accept(LSocket),
{ok, Socket} = ssl:handshake(ASocket),
receive
{ssl, Socket, "from client"} ->
ssl:send(Socket, "from server"),
ssl:close(Socket)
end
receive
{ssl, Socket, "from client"} ->
ssl:send(Socket, "from server"),
ssl:close(Socket)
end
end),
{ok, Client} = ssl:connect(Hostname, Port, ClientOpts),

ssl:send(Client, "from client"),
receive
receive
{ssl, Client, "from server"} ->
ssl:close(Client)
end.


dtls_listen_close() ->
[{doc, "Test that you close a DTLS 'listner' socket"}].
[{doc, "Test that you close a DTLS 'listener' socket"}].

dtls_listen_close(Config) when is_list(Config) ->
dtls_listen_close(Config) when is_list(Config) ->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{_, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),

Port = ssl_test_lib:inet_port(ServerNode),
{ok, ListenSocket} = ssl:listen(Port, [{protocol, dtls} | ServerOpts]),
ok = ssl:close(ListenSocket).

[_] = listener_and_ports(),
ok = ssl:close(ListenSocket),
[] = listener_and_ports(),
{ok, ListenSocket2} = ssl:listen(Port, [{protocol, dtls} | ServerOpts]),
[_] = listener_and_ports(),
ok = ssl:close(ListenSocket2, 500),
[] = listener_and_ports(),
ok.

dtls_listen_reopen() ->
[{doc, "Test that you close a DTLS 'listner' socket and open a new one for the same port"}].
Expand Down Expand Up @@ -231,6 +240,35 @@ dtls_listen_reopen(Config) when is_list(Config) ->
ssl:close(Client2)
end.

dtls_listen_both_family() ->
[].
%% [{require, ipv6_hosts}].
dtls_listen_both_family(Config) ->
{ok, Hostname0} = inet:gethostname(),

TestIPV6 = case ct:get_config(ipv6_hosts) of
Hosts when is_list(Hosts) ->
lists:member(list_to_atom(Hostname0), Hosts);
undefined ->
ct:log("Local tests (ipv6 probably works)", []),
true
end,
case TestIPV6 of
true ->
{_, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
Port = ssl_test_lib:inet_port(ServerNode),
{ok, ListenSocket} = ssl:listen(Port, [{protocol, dtls}]),
[_] = listener_and_ports(),

{ok, ListenSocketIpV6} = ssl:listen(Port, [{protocol, dtls}, inet6, {ipv6_v6only,true}]),
[_,_] = listener_and_ports(),

ok = ssl:close(ListenSocket),
ok = ssl:close(ListenSocketIpV6);
false ->
{skip, "Host does not support IPv6"}
end.

dtls_listen_two_sockets_1() ->
[{doc, "Test with two DTLS dockets: 127.0.0.2:Port, 127.0.0.3:Port"}].
dtls_listen_two_sockets_1(_Config) when is_list(_Config) ->
Expand Down Expand Up @@ -307,6 +345,14 @@ dtls_listen_two_sockets_6(_Config) when is_list(_Config) ->
ssl:close(S1),
ok.

listener_and_ports() ->
timer:sleep(200), %% Allow some time to start och delete dead children
Pids = [Pid || {_, Pid, _, _} <- supervisor:which_children(dtls_listener_sup)],
PidPorts = [{element(2, erlang:port_info(P, connected)), P}
|| P <- erlang:ports(), {name, "udp_inet"} == erlang:port_info(P, name)],
PidWithoutPort = [Pid || Pid <- Pids, not lists:keymember(Pid, 1, PidPorts)],
PidPorts ++ PidWithoutPort.

replay_window() ->
[{doc, "Whitebox test of replay window"}].
replay_window(_Config) ->
Expand Down

0 comments on commit 4f1a24b

Please sign in to comment.