File 1701-ssh-lsocket_sup-and-lsocket-added.patch of Package erlang

From 3d404e42ed34a9a20915fec0c214d392678986c6 Mon Sep 17 00:00:00 2001
From: Jakub Witczak <kuba@erlang.org>
Date: Mon, 21 Oct 2024 11:27:36 +0200
Subject: [PATCH 1/3] ssh: lsocket_sup and lsocket added

- ssh server side supervision tree update
- listen socket is always 1st step when daemon is started
- listen socket is provided by temporary ssh_lsocket process
- supervised by ssh_lsocket_sup
---
 lib/ssh/src/Makefile                |   2 +
 lib/ssh/src/ssh.app.src             |   2 +
 lib/ssh/src/ssh.erl                 | 203 ++++++++++++----------------
 lib/ssh/src/ssh_acceptor.erl        | 125 ++++-------------
 lib/ssh/src/ssh_acceptor_sup.erl    |  53 +++++++-
 lib/ssh/src/ssh_app.erl             |  23 ++--
 lib/ssh/src/ssh_lsocket.erl         | 169 +++++++++++++++++++++++
 lib/ssh/src/ssh_lsocket_sup.erl     |  86 ++++++++++++
 lib/ssh/src/ssh_system_sup.erl      |  62 +++++++--
 lib/ssh/test/ssh_basic_SUITE.erl    |  44 ++++--
 lib/ssh/test/ssh_dbg_SUITE.erl      |   4 +-
 lib/ssh/test/ssh_protocol_SUITE.erl | 100 ++++----------
 lib/ssh/test/ssh_sup_SUITE.erl      |  14 +-
 lib/ssh/test/ssh_test_lib.erl       |  46 ++++++-
 lib/ssh/test/ssh_test_lib.hrl       |   2 +
 15 files changed, 607 insertions(+), 328 deletions(-)
 create mode 100644 lib/ssh/src/ssh_lsocket.erl
 create mode 100644 lib/ssh/src/ssh_lsocket_sup.erl

diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile
index a8a088eb7d..f59c997a16 100644
--- a/lib/ssh/src/Makefile
+++ b/lib/ssh/src/Makefile
@@ -73,6 +73,8 @@ MODULES= \
 	ssh_info \
 	ssh_io \
 	ssh_lib \
+	ssh_lsocket \
+	ssh_lsocket_sup \
 	ssh_message \
 	ssh_no_io \
 	ssh_options \
diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src
index 93f4c2b305..48df1276a2 100644
--- a/lib/ssh/src/ssh.app.src
+++ b/lib/ssh/src/ssh.app.src
@@ -25,6 +25,8 @@
 	     ssh_daemon_channel,
 	     ssh_dbg,
              ssh_lib,
+             ssh_lsocket_sup,
+             ssh_lsocket,
 	     ssh_shell,
 	     ssh_io,
 	     ssh_info,
diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl
index 4b078a7b89..8efbe54342 100644
--- a/lib/ssh/src/ssh.erl
+++ b/lib/ssh/src/ssh.erl
@@ -167,10 +167,11 @@ The directory could be changed with the option
          
 
 %%% Internal export
--export([is_host/2]).
+-export([is_host/2, update_lsocket/3]).
 
 -behaviour(ssh_dbg).
--export([ssh_dbg_trace_points/0, ssh_dbg_flags/1, ssh_dbg_on/1, ssh_dbg_off/1, ssh_dbg_format/2, ssh_dbg_format/3]).
+-export([ssh_dbg_trace_points/0, ssh_dbg_flags/1, ssh_dbg_on/1, ssh_dbg_off/1,
+         ssh_dbg_format/2, ssh_dbg_format/3]).
 
 %%% "Deprecated" types export:
 -export_type([ssh_daemon_ref/0, ssh_connection_ref/0, ssh_channel_id/0]).
@@ -592,69 +593,54 @@ The rules for handling the two address passing options are:
            ;(socket, open_socket(), daemon_options()) -> {ok,daemon_ref()} | {error,term()}
             .
 
-daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535,
-                                        Host0 == any ; Host0 == loopback ; is_tuple(Host0) ->
-    try
-        {Host1, UserOptions} = handle_daemon_args(Host0, UserOptions0),
-        #{} = Options0 = ssh_options:handle_options(server, UserOptions),
-        %% We need to open the listen socket here before start of the system supervisor. That
-        %% is because Port0 might be 0, or if an FD is provided in the Options0, in which case
-        %% the real listening port will be known only after the gen_tcp:listen call.
-        maybe_open_listen_socket(Host1, Port0, Options0)
-    of
-        {Host, Port, ListenSocket, Options1} ->
-            try
-                %% Now Host,Port is what to use for the supervisor to register its name,
-                %% and ListenSocket, if provided,  is for listening on connections. But
-                %% it is still owned by self()...
-
-                %% throws error:Error if no usable hostkey is found
-                ssh_connection_handler:available_hkey_algorithms(server, Options1),
-                ssh_system_sup:start_system(#address{address = Host,
-                                                     port = Port,
-                                                     profile = ?GET_OPT(profile,Options1)},
-                                            Options1)
-            of
-                {ok,DaemonRef} when ListenSocket == undefined ->
-                    {ok,DaemonRef};
-                {ok,DaemonRef} ->
-                    receive
-                        {request_control, ListenSocket, ReqPid} ->
-                            ok = controlling_process(ListenSocket, ReqPid, Options1),
-                            ReqPid ! {its_yours,ListenSocket}
-                    end,
-                    {ok,DaemonRef};
-                {error, {already_started, _}} ->
-                    close_listen_socket(ListenSocket, Options1),
-                    {error, eaddrinuse};
-                {error, Error} ->
-                    close_listen_socket(ListenSocket, Options1),
-                    {error, Error}
-            catch
-                error:{shutdown,Err} ->
-                    close_listen_socket(ListenSocket, Options1),
-                    {error,Err};
-                exit:{noproc, _} ->
-                    close_listen_socket(ListenSocket, Options1),
-                    {error, ssh_not_started};
-                error:Error ->
-                    close_listen_socket(ListenSocket, Options1),
-                    error(Error);
-                exit:Exit ->
-                    close_listen_socket(ListenSocket, Options1),
-                    exit(Exit)
-            end
-    catch
-        throw:bad_fd ->
-            {error,bad_fd};
-        throw:bad_socket ->
-            {error,bad_socket};
-        error:{badmatch,{error,Error}} ->
-            {error,Error};
-        error:Error ->
-            {error,Error};
-        _C:_E ->
-            {error,{cannot_start_daemon,_C,_E}}
+daemon(Host0, Port0, UserOptions0)
+  when 0 =< Port0, Port0 =< 65535, Host0 == any ;
+       Host0 == loopback ; is_tuple(Host0) ->
+    {Host1, UserOptions} = handle_daemon_args(Host0, UserOptions0),
+    case ssh_options:handle_options(server, UserOptions) of
+        #{} = Options0 ->
+            case ssh_lsocket:get_lsocket(Host1, Port0, Options0) of
+                {ok, LSocketProvider, {ok, LSocket}} ->
+                    {Host, Port, Options1} = update_lsocket(LSocket, LSocketProvider, Options0),
+                    try
+                        %% Now Host,Port is what to use for the supervisor to register its name,
+                        %% and ListenSocket, if provided,  is for listening on connections. But
+                        %% it is still owned by self()...
+
+                        %% throws error:Error if no usable hostkey is found
+                        ssh_connection_handler:available_hkey_algorithms(server, Options1),
+                        ssh_system_sup:start_system(#address{address = Host,
+                                                             port = Port,
+                                                             profile = ?GET_OPT(profile,Options1)},
+                                                    Options1)
+                    of
+                        {ok,DaemonRef} ->
+                            {ok,DaemonRef};
+                        {error, {already_started, _}} ->
+                            close_listen_socket(LSocket, Options1),
+                            {error, eaddrinuse};
+                        {error, Error} ->
+                            close_listen_socket(LSocket, Options1),
+                            {error, Error}
+                    catch
+                        error:{shutdown,Err} ->
+                            close_listen_socket(LSocket, Options1),
+                            {error,Err};
+                        exit:{noproc, _} ->
+                            close_listen_socket(LSocket, Options1),
+                            {error, ssh_not_started};
+                        error:Error ->
+                            close_listen_socket(LSocket, Options1),
+                            error(Error);
+                        exit:Exit ->
+                            close_listen_socket(LSocket, Options1),
+                            exit(Exit)
+                    end;
+                Error = {error, _} ->
+                    Error
+            end;
+        OptionError = {error,_} ->
+            OptionError
     end;
 
 daemon(_, _, _) ->
@@ -682,9 +668,9 @@ chapters [Configuration in SSH](configurations.md) and
 
 daemon_replace_options(DaemonRef, NewUserOptions) ->
     case ssh_system_sup:get_acceptor_options(DaemonRef) of
-        {ok, Os0} ->
-            Os1 = ssh_options:merge_options(server, NewUserOptions, Os0),
-            ssh_system_sup:replace_acceptor_options(DaemonRef, Os1);
+        {ok, Options0} ->
+            Options = ssh_options:merge_options(server, NewUserOptions, Options0),
+            ssh_system_sup:restart_acceptor(DaemonRef, Options);
         {error, _Reason} = Error ->
             Error
     end.
@@ -1277,29 +1263,6 @@ is_tcp_socket(Socket) ->
         _ -> false
     end.
 
-%%%----------------------------------------------------------------
-maybe_open_listen_socket(Host, Port, Options) ->
-    Opened =
-        case ?GET_SOCKET_OPT(fd, Options) of
-            undefined when Port == 0 ->
-                ssh_acceptor:listen(0, Options);
-            Fd when is_integer(Fd) ->
-                %% Do gen_tcp:listen with the option {fd,Fd}:
-                ssh_acceptor:listen(0, Options);
-            undefined ->
-                open_later
-        end,
-    case Opened of
-        {ok,LSock} ->
-            {ok,{LHost,LPort}} = inet:sockname(LSock),
-            {LHost, LPort, LSock, ?PUT_INTERNAL_OPT({lsocket,{LSock,self()}}, Options)};
-        open_later ->
-            {Host, Port, undefined, Options};
-        Others ->
-            Others
-    end.
-
-%%%----------------------------------------------------------------
 close_listen_socket(ListenSocket, Options) ->
     try
         {_, Callback, _} = ?GET_OPT(transport, Options),
@@ -1308,10 +1271,6 @@ close_listen_socket(ListenSocket, Options) ->
         _C:_E -> ok
     end.
 
-controlling_process(ListenSocket, ReqPid, Options) ->
-    {_, Callback, _} = ?GET_OPT(transport, Options),
-    Callback:controlling_process(ListenSocket, ReqPid).
-
 transport_connect(Host, Port, SocketOpts, Options) ->
     {_, Callback, _} = ?GET_OPT(transport, Options),
     Callback:connect(Host, Port, SocketOpts, ?GET_OPT(connect_timeout,Options)).
@@ -1365,7 +1324,12 @@ mangle_tunnel_address(X) when is_list(X) -> case catch inet:parse_address(X) of
                                      {ok, {0,0,0,0,0,0,0,0}} -> <<"">>;
                                      _ -> list_to_binary(X)
                                  end.
-
+-doc false.
+update_lsocket(LSocket, LSocketProvider, Options0) ->
+    {ok, {LHost, LPort}} = inet:sockname(LSocket),
+    Options = ?PUT_INTERNAL_OPT({lsocket,
+                                 {LSocket, LHost, LPort, LSocketProvider}}, Options0),
+    {LHost, LPort, Options}.
 
 %%%################################################################
 %%%#
@@ -1373,37 +1337,46 @@ mangle_tunnel_address(X) when is_list(X) -> case catch inet:parse_address(X) of
 %%%#
 
 -doc false.
-ssh_dbg_trace_points() -> [tcp].
+ssh_dbg_trace_points() -> [tcp, connections].
 
 -doc false.
-ssh_dbg_flags(tcp) -> [c].
+ssh_dbg_flags(tcp) -> [c];
+ssh_dbg_flags(connections) -> [c].
 
 -doc false.
-ssh_dbg_on(tcp) -> dbg:tpl(?MODULE, controlling_process, 3, x),
-                   dbg:tpl(?MODULE, transport_connect, 4, x),
-                   dbg:tpl(?MODULE, close_listen_socket, 2, x).
-                   
--doc false.
-ssh_dbg_off(tcp) ->dbg:ctpl(?MODULE, controlling_process, 3),
-                   dbg:ctpl(?MODULE, transport_connect, 4),
-                   dbg:ctpl(?MODULE, close_listen_socket, 2).
+ssh_dbg_on(tcp) ->
+    dbg:tpl(?MODULE, transport_connect, 4, x),
+    dbg:tpl(?MODULE, close_listen_socket, 2, x);
+ssh_dbg_on(connections) ->
+    dbg:tpl(?MODULE, update_lsocket, 3, x).
 
 -doc false.
-ssh_dbg_format(tcp, {call, {?MODULE,controlling_process, [ListenSocket, ReqPid, _Opts]}}) ->
-    ["TCP socket transferred to\n",
-     io_lib:format("Sock: ~p~n"
-                   "ToPid: ~p~n", [ListenSocket, ReqPid])
-    ];
-ssh_dbg_format(tcp, {return_from, {?MODULE,controlling_process,3}, _Result}) ->
-    skip;
+ssh_dbg_off(tcp) ->
+    dbg:ctpl(?MODULE, transport_connect, 4),
+    dbg:ctpl(?MODULE, close_listen_socket, 2);
+ssh_dbg_off(connections) ->
+    dbg:ctpl(?MODULE, update_lsocket, 3).
 
+-doc false.
 ssh_dbg_format(tcp, {call, {?MODULE,close_listen_socket, [ListenSocket, _Opts]}}) ->
     ["TCP socket listening closed\n",
      io_lib:format("Sock: ~p~n", [ListenSocket])
     ];
 ssh_dbg_format(tcp, {return_from, {?MODULE,close_listen_socket,2}, _Result}) ->
-    skip.
-
+    skip;
+ssh_dbg_format(Tracepoint , Event = {call, {?MODULE, Function, Args}}) ->
+    [io_lib:format("~w:~w/~w> ~s", [?MODULE, Function, length(Args)] ++
+                       ssh_dbg_comment(Tracepoint, Event))];
+ssh_dbg_format(Tracepoint, Event = {return_from, {?MODULE,Function,Arity}, Ret}) ->
+    [io_lib:format("~w:~w/~w returned ~W> ~s", [?MODULE, Function, Arity, Ret, 3] ++
+                  ssh_dbg_comment(Tracepoint, Event))].
+
+ssh_dbg_comment(connections, {call, {?MODULE, update_lsocket, [LSocket, LSocketProvider, _]}}) ->
+    [io_lib:format("LSocket = ~p, LSocketProvider = ~p", [LSocket, LSocketProvider])];
+ssh_dbg_comment(connections, {return_from, {?MODULE, update_lsocket,3}, {LHost, LPort, _}}) ->
+    [io_lib:format("LHost = ~p, LPort = ~p", [LHost, LPort])];
+ssh_dbg_comment(_, _) ->
+    [""].
 
 -doc false.
 ssh_dbg_format(tcp, {call, {?MODULE,transport_connect, [Host,Port,SockOpts,_Opts]}}, Stack) ->
diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl
index a558a888d2..60a0f52c46 100644
--- a/lib/ssh/src/ssh_acceptor.erl
+++ b/lib/ssh/src/ssh_acceptor.erl
@@ -27,14 +27,14 @@
 
 %% Internal application API
 -export([start_link/3,
-	 number_of_connections/1,
-	 listen/2]).
+	 number_of_connections/1]).
 
 %% spawn export  
 -export([acceptor_init/4, acceptor_loop/6]).
 
 -behaviour(ssh_dbg).
--export([ssh_dbg_trace_points/0, ssh_dbg_flags/1, ssh_dbg_on/1, ssh_dbg_off/1, ssh_dbg_format/2, ssh_dbg_format/3]).
+-export([ssh_dbg_trace_points/0, ssh_dbg_flags/1, ssh_dbg_on/1, ssh_dbg_off/1,
+         ssh_dbg_format/2, ssh_dbg_format/3]).
 
 -define(SLEEP_TIME, 200).
 
@@ -45,20 +45,6 @@
 start_link(SystemSup, Address, Options) ->
     proc_lib:start_link(?MODULE, acceptor_init, [self(),SystemSup,Address,Options]).
 
-%%%----------------------------------------------------------------
-listen(Port, Options) ->
-    {_, Callback, _} = ?GET_OPT(transport, Options),
-    SockOpts = ?GET_OPT(socket_options, Options) ++ [{active, false}, {reuseaddr,true}],
-    case Callback:listen(Port, SockOpts) of
-	{error, nxdomain} ->
-	    Callback:listen(Port, lists:delete(inet6, SockOpts));
-	{error, enetunreach} ->
-	    Callback:listen(Port, lists:delete(inet6, SockOpts));
-	{error, eafnosupport} ->
-	    Callback:listen(Port, lists:delete(inet6, SockOpts));
-	Other ->
-	    Other
-    end.
 
 accept(ListenSocket, AcceptTimeout, Options) ->
     {_, Callback, _} = ?GET_OPT(transport, Options),
@@ -67,7 +53,7 @@ accept(ListenSocket, AcceptTimeout, Options) ->
 close(Socket, Options) ->
     {_, Callback, _} = ?GET_OPT(transport, Options),
     Callback:close(Socket).
-    
+
 %%--------------------------------------------------------------------
 %%% Internal functions
 %%--------------------------------------------------------------------
@@ -78,57 +64,12 @@ acceptor_init(Parent, SystemSup,
                       {acceptor,
                        list_to_binary(ssh_lib:format_address_port(Address, Port))}),
     AcceptTimeout = ?GET_INTERNAL_OPT(timeout, Opts, ?DEFAULT_TIMEOUT),
-    case ?GET_INTERNAL_OPT(lsocket, Opts, undefined) of
-        {LSock, SockOwner} ->
-            %% A listening socket (or fd option) was provided in the ssh:daemon call
-            case inet:sockname(LSock) of
-                {ok,{_,Port}} ->
-                    %% A usable, open LSock
-                    proc_lib:init_ack(Parent, {ok, self()}),
-                    request_ownership(LSock, SockOwner),
-                    acceptor_loop(Port, Address, Opts, LSock, AcceptTimeout, SystemSup);
-                {error,_Error} ->
-                    %% Not open, a restart
-                    %% Allow gen_tcp:listen to fail 4 times if eaddrinuse (It is a bug fix):
-                    case try_listen(Port, Opts, 4) of
-                        {ok,NewLSock} ->
-                            proc_lib:init_ack(Parent, {ok, self()}),
-                            Opts1 = ?DELETE_INTERNAL_OPT(lsocket, Opts),
-                            acceptor_loop(Port, Address, Opts1, NewLSock, AcceptTimeout, SystemSup);
-                        {error,Error} ->
-                            proc_lib:init_fail(Parent, {error,Error}, {exit, normal})
-                    end
-            end;
-        undefined ->
-            %% No listening socket (nor fd option) was provided; open a listening socket:
-            case try_listen(Port, Opts, 4) of
-                {ok,LSock} ->
-                    proc_lib:init_ack(Parent, {ok, self()}),
-                    acceptor_loop(Port, Address, Opts, LSock, AcceptTimeout, SystemSup);
-                {error,Error} ->
-                    proc_lib:init_fail(Parent, {error,Error}, {exit, normal})
-            end
-    end.
+    {LSock, _LHost, _LPort, _SockOwner} =
+        ?GET_INTERNAL_OPT(lsocket, Opts, undefined),
+    proc_lib:init_ack(Parent, {ok, self()}),
+    acceptor_loop(Port, Address, Opts, LSock, AcceptTimeout, SystemSup).
 
-try_listen(Port, Opts, NtriesLeft) ->
-    try_listen(Port, Opts, 1, NtriesLeft).
-
-try_listen(Port, Opts, N, Nmax) ->
-    case listen(Port, Opts) of
-        {error,eaddrinuse} when N<Nmax ->
-            timer:sleep(10*N), % Sleep 10, 20, 30,... ms
-            try_listen(Port, Opts, N+1, Nmax);
-        Other ->
-            Other
-    end.
-
-request_ownership(LSock, SockOwner) ->
-    SockOwner ! {request_control,LSock,self()},
-    receive
-	{its_yours,LSock} -> ok
-    end.
-    
-%%%----------------------------------------------------------------    
+%%%----------------------------------------------------------------
 acceptor_loop(Port, Address, Opts, ListenSocket, AcceptTimeout, SystemSup) ->
     try
         case accept(ListenSocket, AcceptTimeout, Opts) of
@@ -257,35 +198,18 @@ ssh_dbg_trace_points() -> [connections, tcp].
 ssh_dbg_flags(tcp) -> [c];
 ssh_dbg_flags(connections) -> [c].
 
-ssh_dbg_on(tcp) -> dbg:tp(?MODULE, listen, 2, x),
-                   dbg:tpl(?MODULE, accept, 3, x),
+ssh_dbg_on(tcp) -> dbg:tpl(?MODULE, accept, 3, x),
                    dbg:tpl(?MODULE, close, 2, x);
-                   
+
 ssh_dbg_on(connections) -> dbg:tp(?MODULE,  acceptor_init, 4, x),
                            dbg:tpl(?MODULE, handle_connection, 4, x).
 
-ssh_dbg_off(tcp) -> dbg:ctpg(?MODULE, listen, 2),
-                    dbg:ctpl(?MODULE, accept, 3),
+ssh_dbg_off(tcp) -> dbg:ctpl(?MODULE, accept, 3),
                     dbg:ctpl(?MODULE, close, 2);
 
 ssh_dbg_off(connections) -> dbg:ctp(?MODULE, acceptor_init, 4),
                             dbg:ctp(?MODULE, handle_connection, 4).
 
-ssh_dbg_format(tcp, {call, {?MODULE,listen, [Port,_Opts]}}, Stack) ->
-    {skip, [{port,Port}|Stack]};
-ssh_dbg_format(tcp, {return_from, {?MODULE,listen,2}, {ok,Sock}}, [{port,Port}|Stack]) ->
-    {["TCP listener started\n",
-      io_lib:format("Port: ~p~n"
-                    "ListeningSocket: ~p~n", [Port,Sock])
-     ],
-     Stack};
-ssh_dbg_format(tcp, {return_from, {?MODULE,listen,2}, Result}, [{port,Port}|Stack]) ->
-    {["TCP listener start ERROR\n",
-      io_lib:format("Port: ~p~n"
-                    "Return: ~p~n", [Port,Result])
-     ],
-     Stack};
-
 ssh_dbg_format(tcp, {call, {?MODULE,accept, [ListenSocket, _AcceptTimeout, _Options]}}, Stack) ->
     {skip, [{lsock,ListenSocket}|Stack]};
 ssh_dbg_format(tcp, {return_from, {?MODULE,accept,3}, {ok,Sock}}, [{lsock,ListenSocket}|Stack]) ->
@@ -301,21 +225,22 @@ ssh_dbg_format(tcp, {return_from, {?MODULE,accept,3}, Return}, [{lsock,ListenSoc
                     "Return: ~p~n", [ListenSocket,Return])
      ], Stack}.
 
-ssh_dbg_format(tcp, {call, {?MODULE,close, [Socket, _Options]}}) ->
-    ["TCP close listen socket\n",
-     io_lib:format("Socket: ~p~n", [Socket])];
 ssh_dbg_format(tcp, {return_from, {?MODULE,close,2}, _Return}) ->
     skip;
-
-ssh_dbg_format(connections, {call, {?MODULE,acceptor_init, [_Parent, _SysSup, Address, _Opts]}}) ->
-    [io_lib:format("Starting LISTENER on ~s\n", [ssh_lib:format_address(Address)])
-    ];
 ssh_dbg_format(connections, {return_from, {?MODULE,acceptor_init,4}, _Ret}) ->
     skip;
-
 ssh_dbg_format(connections, {call, {?MODULE,handle_connection,[_Address,_Port,_Options,_Sock]}}) ->
     skip;
-ssh_dbg_format(connections, {return_from, {?MODULE,handle_connection,4}, {error,Error}}) ->
-    ["Starting connection to server failed:\n",
-     io_lib:format("Error = ~p", [Error])
-    ].
+ssh_dbg_format(Tracepoint, Event = {call, {?MODULE, Function, Args}}) ->
+    [io_lib:format("~w:~w/~w> ~s", [?MODULE, Function, length(Args)] ++
+                       ssh_dbg_comment(Tracepoint, Event))];
+ssh_dbg_format(Tracepoint, Event = {return_from, {?MODULE,Function,Arity}, Ret}) ->
+    [io_lib:format("~w:~w/~w returned ~W> ~s", [?MODULE, Function, Arity, Ret, 2] ++
+                  ssh_dbg_comment(Tracepoint, Event))].
+
+ssh_dbg_comment(tcp, {call, {?MODULE,close, [Socket, _Options]}}) ->
+    [io_lib:format("TCP close listen socket Socket: ~p~n", [Socket])];
+ssh_dbg_comment(connections, {call, {?MODULE,acceptor_init, [_Parent, _SysSup, Address, _Opts]}}) ->
+    [io_lib:format("Starting LISTENER on ~s", [ssh_lib:format_address(Address)])];
+ssh_dbg_comment(connections, {return_from, {?MODULE,handle_connection,4}, {error,Error}}) ->
+    [io_lib:format("Starting connection to server failed: Error = ~p", [Error])].
diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl
index 1661134285..74fb5ed71c 100644
--- a/lib/ssh/src/ssh_acceptor_sup.erl
+++ b/lib/ssh/src/ssh_acceptor_sup.erl
@@ -37,6 +37,10 @@
 %% Supervisor callback
 -export([init/1]).
 
+-behaviour(ssh_dbg).
+-export([ssh_dbg_trace_points/0, ssh_dbg_flags/1, ssh_dbg_on/1, ssh_dbg_off/1,
+         ssh_dbg_format/2]).
+
 %%%=========================================================================
 %%%  API
 %%%=========================================================================
@@ -57,7 +61,10 @@ restart_child(AccSup, Address) ->
 init([SystemSup, Address, Options]) ->
     ssh_lib:set_label(server, acceptor_sup),
     %% Initial start of ssh_acceptor_sup for this port
-    SupFlags = #{strategy  => one_for_one, 
+    {LSocket, _LHost, _LPort, ProviderPid} =
+        ?GET_INTERNAL_OPT(lsocket, Options, undefined),
+    request_ownership(LSocket, ProviderPid),
+    SupFlags = #{strategy  => one_for_one,
                  intensity =>   10,
                  period    => 3600
                 },
@@ -71,3 +78,47 @@ init([SystemSup, Address, Options]) ->
 %%%=========================================================================
 %%%  Internal functions
 %%%=========================================================================
+request_ownership(LSocket, SockProvider) ->
+    SockProvider ! {request_control,LSocket,self()},
+    receive
+	{its_yours,LSocket} ->
+            ok
+    after ?DEFAULT_TIMEOUT ->
+            no_response_from_socket_provider
+    end.
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+
+ssh_dbg_trace_points() -> [connections].
+
+ssh_dbg_flags(connections) -> [c].
+
+ssh_dbg_on(connections) ->
+    dbg:tpl(?MODULE, start_link, 3, x),
+    dbg:tpl(?MODULE, restart_child, 2, x),
+    dbg:tpl(?MODULE, request_ownership, 2, x),
+    dbg:tpl(?MODULE, init, 1, x).
+
+ssh_dbg_off(connections) ->
+    dbg:ctpl(?MODULE, start_link, 3),
+    dbg:ctpl(?MODULE, restart_child, 2),
+    dbg:ctpl(?MODULE, request_ownership, 2),
+    dbg:ctpl(?MODULE, init, 1).
+
+ssh_dbg_format(Tracepoint, Event = {call, {?MODULE, Function, Args}}) ->
+    [io_lib:format("~w:~w/~w> ~s", [?MODULE, Function, length(Args)] ++
+                       ssh_dbg_comment(Tracepoint, Event))];
+ssh_dbg_format(Tracepoint, Event = {return_from, {?MODULE,Function,Arity}, Ret}) ->
+    [io_lib:format("~w:~w/~w returned ~W> ~s", [?MODULE, Function, Arity, Ret, 2] ++
+                  ssh_dbg_comment(Tracepoint, Event))].
+
+ssh_dbg_comment(connections, {call, {?MODULE, init, [[LSocket | _]]}}) ->
+    [io_lib:format("LSocket ~p", [LSocket])];
+ssh_dbg_comment(connections, {call, {?MODULE, request_ownership, [LSocket, SockProvider]}}) ->
+    [io_lib:format("LSocket ~p SockProvider ~p", [LSocket, SockProvider])];
+ssh_dbg_comment(_, _) ->
+    [""].
+
diff --git a/lib/ssh/src/ssh_app.erl b/lib/ssh/src/ssh_app.erl
index 5f0c01e7b8..91c688f64d 100644
--- a/lib/ssh/src/ssh_app.erl
+++ b/lib/ssh/src/ssh_app.erl
@@ -29,7 +29,7 @@
 %%%                      |                  :
 %%%                      |                  +--> "connection sup" (etc)
 %%%                      |
-%%%                      +-----> sshc_sup --+--> "system sup" (etc)
+%%%                      +-----> sshd_sup --+--> "lsocket sup" (etc)
 %%%                                         |
 %%%                                         +--> "system sup" (etc)
 %%%                                         :
@@ -64,21 +64,26 @@ init([ssh_sup]) ->
     add_logger_filter(),
     SupFlags = #{strategy  => one_for_one,
                  intensity =>   10,
-                 period    => 3600
-                },
+                 period    => 3600},
     ChildSpecs = [#{id       => SupName,
                     start    => {supervisor, start_link,
-                                 [{local,SupName}, ?MODULE, [sshX_sup]]},
+                                 [{local,SupName}, ?MODULE, [SupName]]},
                     type     => supervisor}
-                  || SupName <- [sshd_sup, sshc_sup]
-                 ],
+                  || SupName <- [sshd_sup, sshc_sup]],
     {ok, {SupFlags,ChildSpecs}};
 
-init([sshX_sup]) ->
+init([sshd_sup]) ->
     SupFlags = #{strategy  => one_for_one,
                  intensity =>   10,
-                 period    => 3600
-                },
+                 period    => 3600},
+    ChildSpecs = [#{id       => ssh_lsocket_sup,
+                    start    => {ssh_lsocket_sup, start_link, []},
+                    type     => supervisor}],
+    {ok, {SupFlags,ChildSpecs}};
+init([sshc_sup]) ->
+    SupFlags = #{strategy  => one_for_one,
+                 intensity =>   10,
+                 period    => 3600},
     ChildSpecs = [],
     {ok, {SupFlags,ChildSpecs}}.
 
diff --git a/lib/ssh/src/ssh_lsocket.erl b/lib/ssh/src/ssh_lsocket.erl
new file mode 100644
index 0000000000..c4d9853c0c
--- /dev/null
+++ b/lib/ssh/src/ssh_lsocket.erl
@@ -0,0 +1,169 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2024. 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%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Produce listen sockets.
+%%----------------------------------------------------------------------
+-module(ssh_lsocket).
+-moduledoc false.
+
+-include("ssh.hrl").
+-export([start_link/4, provide_lsocket/5, get_lsocket/3]).
+
+-behaviour(ssh_dbg).
+-export([ssh_dbg_trace_points/0, ssh_dbg_flags/1, ssh_dbg_on/1, ssh_dbg_off/1,
+         ssh_dbg_format/2, ssh_dbg_format/3]).
+
+get_lsocket(Host, Port, Options) ->
+    supervisor:start_child(ssh_lsocket_sup, [self(), Host, Port, Options]).
+
+start_link(Caller, Host, Port, Options) ->
+    proc_lib:start_link(?MODULE, provide_lsocket,
+                            [self(), Caller, Host, Port, Options]).
+
+provide_lsocket(Parent, _Caller, _Host1, Port0, Options) ->
+    OpenResult =
+        try
+            try_listen(Port0, Options, 4)
+        of
+            {ok, LSocket} ->
+                {ok, LSocket};
+            Others ->
+                Others
+        catch
+            throw:bad_fd ->
+                {error,bad_fd};
+            throw:bad_socket ->
+                {error,bad_socket};
+            error:{badmatch, {error,Error}} ->
+                {error,Error};
+            error:Error ->
+                {error,Error};
+            _C:_E ->
+                {error,{cannot_start_daemon,_C,_E}}
+        end,
+    case OpenResult of
+        {ok, LSocket1} ->
+            proc_lib:init_ack(Parent, {ok, self(), OpenResult}),
+            wait_for_acceptor_sup(LSocket1, Options),
+            ok;
+        {error, _} ->
+            proc_lib:init_fail(Parent, OpenResult, {exit, normal})
+    end.
+
+wait_for_acceptor_sup(ListenSocket, Options) ->
+    receive
+        {request_control, ListenSocket, AcceptorSup} ->
+            ok = controlling_process(ListenSocket, AcceptorSup, Options),
+            AcceptorSup ! {its_yours,ListenSocket},
+            ok
+    end.
+
+controlling_process(ListenSocket, ReqPid, Options) ->
+    {_, Callback, _} = ?GET_OPT(transport, Options),
+    Callback:controlling_process(ListenSocket, ReqPid).
+
+try_listen(Port, Opts, NtriesLeft) ->
+    try_listen(Port, Opts, 1, NtriesLeft).
+
+try_listen(Port, Opts, N, Nmax) ->
+    case listen(Port, Opts) of
+        {error,eaddrinuse} when N<Nmax ->
+            timer:sleep(10*N), % Sleep 10, 20, 30,... ms
+            try_listen(Port, Opts, N+1, Nmax);
+        Other ->
+            Other
+    end.
+
+listen(Port, Options) ->
+    {_, Callback, _} = ?GET_OPT(transport, Options),
+    SockOpts = ?GET_OPT(socket_options, Options) ++ [{active, false}, {reuseaddr,true}],
+    case Callback:listen(Port, SockOpts) of
+	{error, nxdomain} ->
+	    Callback:listen(Port, lists:delete(inet6, SockOpts));
+	{error, enetunreach} ->
+	    Callback:listen(Port, lists:delete(inet6, SockOpts));
+	{error, eafnosupport} ->
+	    Callback:listen(Port, lists:delete(inet6, SockOpts));
+	Other ->
+	    Other
+    end.
+
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+
+ssh_dbg_trace_points() -> [connections].
+
+ssh_dbg_flags(connections) -> [c].
+
+ssh_dbg_on(connections) ->
+    dbg:tpl(?MODULE, provide_lsocket, 5, x),
+    dbg:tpl(?MODULE, controlling_process, 3, x),
+    dbg:tpl(?MODULE, try_listen, 4, x),
+    dbg:tpl(?MODULE, wait_for_acceptor_sup, 2, x);
+ssh_dbg_on(tcp) -> dbg:tpl(?MODULE, accept, 3, x).
+
+ssh_dbg_off(connections) ->
+    dbg:ctpl(?MODULE, provide_lsocket, 5),
+    dbg:ctpl(?MODULE, controlling_process, 3),
+    dbg:ctpl(?MODULE, try_listen, 4),
+    dbg:ctpl(?MODULE, wait_for_acceptor_sup, 2);
+ssh_dbg_off(tcp) -> dbg:ctpl(?MODULE, accept, 3).
+
+ssh_dbg_format(Tracepoint, Event = {call, {?MODULE, Function, Args}}) ->
+    [io_lib:format("~w:~w/~w> ~s", [?MODULE, Function, length(Args)] ++
+                       ssh_dbg_comment(Tracepoint, Event))];
+ssh_dbg_format(Tracepoint, Event = {return_from, {?MODULE,Function,Arity}, Ret}) ->
+    [io_lib:format("~w:~w/~w returned ~W> ~s", [?MODULE, Function, Arity, Ret, 3] ++
+                  ssh_dbg_comment(Tracepoint, Event))].
+
+ssh_dbg_comment(connections, {call, {?MODULE, try_listen, [Port, _, N, Nmax]}}) ->
+    [io_lib:format("listen retry on Port:~p [~p/~p]", [Port, N, Nmax])];
+ssh_dbg_comment(connections, {call, {?MODULE, provide_lsocket, [_Parent, _Caller, _Ref, Host, Port, _UserOptions]}}) ->
+    [io_lib:format("providing LSocket for Host: ~p Port: ~p", [Host, Port])];
+ssh_dbg_comment(connections, {call, {?MODULE,controlling_process,[ListenSocket, ReqPid, _Options]}}) ->
+    [io_lib:format("changing owner for ~p to ~p", [ListenSocket, ReqPid])];
+ssh_dbg_comment(connections, {return_from, {?MODULE,controlling_process,3}, _Ret}) ->
+    [io_lib:format("ownership changed", [])];
+ssh_dbg_comment(connections, {call, {?MODULE, wait_for_acceptor_sup, [LSocket, _Options]}}) ->
+    [io_lib:format("waiting for acceptor sup to pickup LSocket ~p", [LSocket])];
+ssh_dbg_comment(connections, {return_from, {?MODULE,wait_for_acceptor_sup,2}, ok}) ->
+    [io_lib:format("LSocket provided. Bye.", [])];
+ssh_dbg_comment(_, _) ->
+    [""].
+
+ssh_dbg_format(tcp, {call, {?MODULE,listen, [Port,_Opts]}}, Stack) ->
+    {skip, [{port,Port}|Stack]};
+ssh_dbg_format(tcp, {return_from, {?MODULE,listen,2}, {ok,Sock}}, [{port,Port}|Stack]) ->
+    {["TCP listener started\n",
+      io_lib:format("Port: ~p~n"
+                    "ListeningSocket: ~p~n", [Port,Sock])
+     ],
+     Stack};
+ssh_dbg_format(tcp, {return_from, {?MODULE,listen,2}, Result}, [{port,Port}|Stack]) ->
+    {["TCP listener start ERROR\n",
+      io_lib:format("Port: ~p~n"
+                    "Return: ~p~n", [Port,Result])
+     ],
+     Stack}.
diff --git a/lib/ssh/src/ssh_lsocket_sup.erl b/lib/ssh/src/ssh_lsocket_sup.erl
new file mode 100644
index 0000000000..1922268c37
--- /dev/null
+++ b/lib/ssh/src/ssh_lsocket_sup.erl
@@ -0,0 +1,86 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2024. 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%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: The ssh_lsocket supervisor, hangs under
+%%          sshd_sup.
+%%----------------------------------------------------------------------
+
+-module(ssh_lsocket_sup).
+-moduledoc false.
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callbacks
+-export([init/1]).
+-define(SERVER, ?MODULE).
+
+%%%===================================================================
+%%% API functions
+%%%===================================================================
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts the supervisor
+%% @end
+%%--------------------------------------------------------------------
+-spec start_link() -> {ok, Pid :: pid()} |
+          {error, {already_started, Pid :: pid()}} |
+          {error, {shutdown, term()}} |
+          {error, term()} |
+          ignore.
+start_link() ->
+    supervisor:start_link({local, ?SERVER}, ?MODULE, []).
+
+%%%===================================================================
+%%% Supervisor callbacks
+%%%===================================================================
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Whenever a supervisor is started using supervisor:start_link/[2,3],
+%% this function is called by the new process to find out about
+%% restart strategy, maximum restart intensity, and child
+%% specifications.
+%% @end
+%%--------------------------------------------------------------------
+-spec init(Args :: term()) ->
+          {ok, {SupFlags :: supervisor:sup_flags(),
+                [ChildSpec :: supervisor:child_spec()]}} |
+          ignore.
+init([]) ->
+    ssh_lib:set_label(server, lsocket_sup),
+    SupFlags = #{strategy  => simple_one_for_one,
+                 intensity => 1,
+                 period    => 5},
+
+    AChild = #{id       => ssh_lsocket,
+               start    => {ssh_lsocket, start_link, []},
+               restart  => temporary,
+               shutdown => 5000,
+               type     => worker,
+               modules  => [ssh_lsocket]},
+
+    {ok, {SupFlags, [AChild]}}.
diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl
index bc1d79d0f4..676c75f27f 100644
--- a/lib/ssh/src/ssh_system_sup.erl
+++ b/lib/ssh/src/ssh_system_sup.erl
@@ -41,12 +41,16 @@
          addresses/1,
          get_options/2,
          get_acceptor_options/1,
-         replace_acceptor_options/2
+         restart_acceptor/2
         ]).
 
 %% Supervisor callback
 -export([init/1]).
 
+-behaviour(ssh_dbg).
+-export([ssh_dbg_trace_points/0, ssh_dbg_flags/1, ssh_dbg_on/1, ssh_dbg_off/1,
+         ssh_dbg_format/2]).
+
 %%%=========================================================================
 %%% API
 %%%=========================================================================
@@ -54,7 +58,7 @@
 start_system(Address0, Options) ->
     case find_system_sup(Address0) of
         {ok,{SysPid,Address}} ->
-            restart_acceptor(SysPid, Address, Options);
+            start_acceptor(SysPid, Address, Options);
         {error,not_found} ->
             supervisor:start_child(sshd_sup,
                                    #{id       => {?MODULE,Address0},
@@ -164,16 +168,19 @@ get_acceptor_options(SysPid) ->
             {error,bad_daemon_ref}
     end.
 
-replace_acceptor_options(SysPid, NewOpts) ->
+restart_acceptor(SysPid, Options0) ->
     case get_daemon_listen_address(SysPid) of
         {ok,Address} ->
-            try stop_listener(SysPid)
+            try
+                stop_listener(SysPid)
             of
                 ok ->
-                    restart_acceptor(SysPid, Address, NewOpts)
+                    Options = refresh_lsocket(Options0),
+                    start_acceptor(SysPid, Address, Options)
             catch
-                error:_ ->
-                    restart_acceptor(SysPid, Address, NewOpts)
+                error:_Error ->
+                    Options = refresh_lsocket(Options0),
+                    start_acceptor(SysPid, Address, Options)
             end;
         {error,Error} ->
             {error,Error}
@@ -258,7 +265,7 @@ sup(server) -> sshd_sup.
 is_socket_server(Options) ->
     undefined =/= ?GET_INTERNAL_OPT(connected_socket,Options,undefined).
 
-restart_acceptor(SysPid, Address, Options) ->
+start_acceptor(SysPid, Address, Options) ->
     case lookup(ssh_acceptor_sup, SysPid) of
         {_,_,supervisor,_} ->
             {error, eaddrinuse};
@@ -273,3 +280,42 @@ restart_acceptor(SysPid, Address, Options) ->
                     {error,Error}
             end
     end.
+
+refresh_lsocket(Options0) ->
+    {_OldLSock, LHost, LPort, _SockOwner} =
+        ?GET_INTERNAL_OPT(lsocket, Options0, lsocket_undefined),
+    case ssh_lsocket:get_lsocket(LHost, LPort, Options0) of
+        {ok, LSocketProvider, {ok, LSocket}} ->
+            {_Host, _Port, Options} =
+                ssh:update_lsocket(LSocket, LSocketProvider, Options0),
+            Options;
+        {error, Error} ->
+            {error, Error}
+    end.
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+
+ssh_dbg_trace_points() -> [connections].
+
+ssh_dbg_flags(connections) -> [c].
+
+ssh_dbg_on(connections) ->
+    dbg:tpl(?MODULE, stop_listener, 1, x),
+    dbg:tpl(?MODULE, start_acceptor, 3, x).
+
+ssh_dbg_off(connections) ->
+    dbg:ctpl(?MODULE, stop_listener, 1),
+    dbg:ctpl(?MODULE, start_acceptor, 3).
+
+ssh_dbg_format(Tracepoint, Event = {call, {?MODULE, Function, Args}}) ->
+    [io_lib:format("~w:~w/~w> ~s", [?MODULE, Function, length(Args)] ++
+                       ssh_dbg_comment(Tracepoint, Event))];
+ssh_dbg_format(Tracepoint, Event = {return_from, {?MODULE,Function,Arity}, Ret}) ->
+    [io_lib:format("~w:~w/~w returned ~W> ~s", [?MODULE, Function, Arity, Ret, 3] ++
+                  ssh_dbg_comment(Tracepoint, Event))].
+
+ssh_dbg_comment(_, _) ->
+    [""].
diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl
index bcf4c7f859..d889bcabf0 100644
--- a/lib/ssh/test/ssh_basic_SUITE.erl
+++ b/lib/ssh/test/ssh_basic_SUITE.erl
@@ -82,6 +82,7 @@
          pass_phrase/1,
          peername_sockname/1,
          send/1,
+         parallel_login/1,
          setopts_getopts/1,
          shell/1,
          shell_exit_status/1,
@@ -113,7 +114,6 @@ all() ->
     [{group, all_tests}
     ].
 
-%%%-define(PARALLEL, ).
 -define(PARALLEL, parallel).
 
 groups() ->
@@ -121,9 +121,7 @@ groups() ->
                                {group, p_basic},
                                {group, internal_error},
                                {group, login_bad_pwd_no_retry},
-                               {group, key_cb}
-                              ]},
-
+                               {group, key_cb}]},
      {sequential, [], [app_test,
                        appup_test,
                        daemon_already_started,
@@ -149,21 +147,18 @@ groups() ->
                                             login_bad_pwd_no_retry2,
                                             login_bad_pwd_no_retry3,
                                             login_bad_pwd_no_retry4,
-                                            login_bad_pwd_no_retry5
-                                           ]},
-     
-     {p_basic, [?PARALLEL], [send, peername_sockname,
-                             exec, exec_compressed, 
+                                            login_bad_pwd_no_retry5]},
+     {p_basic, [?PARALLEL], [send, parallel_login, peername_sockname,
+                             exec, exec_compressed,
                              exec_with_io_out, exec_with_io_in,
                              cli, cli_exit_normal, cli_exit_status,
                              idle_time_client, idle_time_server,
                              max_initial_idle_time,
                              openssh_zlib_basic_test,
                              misc_ssh_options, inet_option, inet6_option,
-                             shell, shell_socket, shell_ssh_conn, shell_no_unicode, shell_unicode_string,
-                             close
-                            ]}
-    ].
+                             shell, shell_socket, shell_ssh_conn,
+                             shell_no_unicode, shell_unicode_string,
+                             close]}].
 
 %%--------------------------------------------------------------------
 init_per_suite(Config) ->
@@ -1038,6 +1033,29 @@ send(Config) when is_list(Config) ->
     ok = ssh_connection:send(ConnectionRef, ChannelId, << >>),
     ssh:stop_daemon(Pid).
 
+%%--------------------------------------------------------------------
+%%% Test parallel_login
+parallel_login(Config) when is_list(Config) ->
+    process_flag(trap_exit, true),
+    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
+    UserDir = proplists:get_value(priv_dir, Config),
+    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+                                             {preferred_algorithms, ssh_transport:supported_algorithms()},
+					     {user_dir, UserDir},
+                                             {parallel_login, true},
+					     {failfun, fun ssh_test_lib:failfun/2}]),
+    ConnectionRef =
+	ssh_test_lib:connect(Host, Port, [{preferred_algorithms, ssh_transport:supported_algorithms()},
+                                          {silently_accept_hosts, true},
+					  {user_dir, UserDir},
+					  {user_interaction, false}]),
+    {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
+    ok = ssh_connection:send(ConnectionRef, ChannelId, <<"Data">>),
+    ok = ssh_connection:send(ConnectionRef, ChannelId, << >>),
+    ssh_info:print(fun(Fmt, Args) -> io:fwrite(user, Fmt, Args) end),
+    {Parents, Conns, Handshakers} =
+        ssh_test_lib:find_handshake_parent(Port),
+    ssh:stop_daemon(Pid).
 
 %%--------------------------------------------------------------------
 %%% Test ssh:connection_info([peername, sockname])
diff --git a/lib/ssh/test/ssh_dbg_SUITE.erl b/lib/ssh/test/ssh_dbg_SUITE.erl
index adeeb07b7d..fd55c4211c 100644
--- a/lib/ssh/test/ssh_dbg_SUITE.erl
+++ b/lib/ssh/test/ssh_dbg_SUITE.erl
@@ -192,7 +192,7 @@ dbg_connections(Config) ->
                                                           end},
 					     {failfun, fun ssh_test_lib:failfun/2}]),
     
-    ?DBG_RECEIVE("Starting LISTENER on ", Ref, _, Pid),
+    ?DBG_RECEIVE("ssh_acceptor:acceptor_init/4> Starting LISTENER on ", Ref, _, Pid),
 
     C = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
 					  {user_dir, UserDir},
@@ -435,7 +435,7 @@ dbg_channels(Config) ->
                                              },
 					     {failfun, fun ssh_test_lib:failfun/2}]),
     
-    ?DBG_RECEIVE("Starting LISTENER on ", Ref, _, Pid),
+    ?DBG_RECEIVE("ssh_acceptor:acceptor_init/4> Starting LISTENER on ", Ref, _, Pid),
 
     C = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
                                           {user_dir, UserDir},
diff --git a/lib/ssh/test/ssh_protocol_SUITE.erl b/lib/ssh/test/ssh_protocol_SUITE.erl
index 7b8d4e9767..5e685aaeb4 100644
--- a/lib/ssh/test/ssh_protocol_SUITE.erl
+++ b/lib/ssh/test/ssh_protocol_SUITE.erl
@@ -103,8 +103,6 @@
                                    [{client2server,Ciphs}, {server2client,Ciphs}]
                           end)()
         ).
--define(v(Key, Config), proplists:get_value(Key, Config)).
--define(v(Key, Config, Default), proplists:get_value(Key, Config, Default)).
 -define(HARDCODED_KEXDH_REPLY,
         #ssh_msg_kexdh_reply{
            public_host_key = {{{'ECPoint',<<73,72,235,162,96,101,154,59,217,114,123,192,96,105,250,29,214,76,60,63,167,21,221,118,246,168,152,2,7,172,137,125>>},
@@ -116,11 +114,9 @@
                      92,210,130,249,103,2,215,245,7,213,110,235,136,134,11,
                      124,248,139,79,17,225,77,125,182,204,84,137,167,99,186,
                      167,42,192,10>>}).
-
 %%--------------------------------------------------------------------
 %% Common Test interface functions -----------------------------------
 %%--------------------------------------------------------------------
-
 suite() ->
     [{ct_hooks,[ts_install_cth]},
      {timetrap,{seconds,40}}].
@@ -365,7 +358,7 @@ no_common_alg_server_disconnects(Config) ->
 	ssh_trpt_test_lib:exec(
 	  [{set_options, [print_ops, {print_messages,detail}]},
 	   {connect,
-	    server_host(Config),server_port(Config),
+	    ssh_test_lib:server_host(Config),ssh_test_lib:server_port(Config),
 	    [{silently_accept_hosts, true},
 	     {user_dir, user_dir(Config)},
 	     {user_interaction, false},
@@ -473,7 +466,7 @@ do_gex_client_init(Config, {Min,N,Max}, {G,P}) ->
 	ssh_trpt_test_lib:exec(
 	  [{set_options, [print_ops, print_seqnums, print_messages]},
 	   {connect,
-	    server_host(Config),server_port(Config),
+	    ssh_test_lib:server_host(Config),ssh_test_lib:server_port(Config),
 	    [{silently_accept_hosts, true},
 	     {user_dir, user_dir(Config)},
 	     {user_interaction, false},
@@ -508,7 +501,7 @@ do_gex_client_init_old(Config, N, {G,P}) ->
 	ssh_trpt_test_lib:exec(
 	  [{set_options, [print_ops, print_seqnums, print_messages]},
 	   {connect,
-	    server_host(Config),server_port(Config),
+	    ssh_test_lib:server_host(Config),ssh_test_lib:server_port(Config),
 	    [{silently_accept_hosts, true},
 	     {user_dir, user_dir(Config)},
 	     {user_interaction, false},
@@ -874,7 +867,7 @@ kex_strict_helper(Config, TestMessages, ExpectedReason) ->
     {ok, _AfterKexState} =
         ssh_trpt_test_lib:exec(
           [{connect,
-            server_host(Config),server_port(Config),
+            ssh_test_lib:server_host(Config),ssh_test_lib:server_port(Config),
             [{preferred_algorithms,[{kex,[?DEFAULT_KEX]},
                                     {cipher,?DEFAULT_CIPHERS}
                                    ]},
@@ -992,13 +985,12 @@ client_close_after_hello(Config0) ->
                                         {max_sessions,MaxSessions},
                                         {negotiation_timeout,SleepSec*1000}
                                        ]),
-
-    {_Parents0, Conns0, []} = find_handshake_parent(server_port(Config)),
-
+    {_Parents0, Conns0, []} =
+        ssh_test_lib:find_handshake_parent(ssh_test_lib:server_port(Config)),
     Cs =
         [ssh_trpt_test_lib:exec(
            [{connect,
-             server_host(Config),server_port(Config),
+             ssh_test_lib:server_host(Config),ssh_test_lib:server_port(Config),
              [{preferred_algorithms,[{kex,[?DEFAULT_KEX]},
                                      {cipher,?DEFAULT_CIPHERS}
                                     ]},
@@ -1014,12 +1006,13 @@ client_close_after_hello(Config0) ->
     ct:log("=== Tried to start ~p sessions.", [length(Cs)]),
 
     ssh_info:print(fun ct:log/2),
-    {Parents, Conns, Handshakers} = find_handshake_parent(server_port(Config)),
+    {Parents, Conns, Handshakers} =
+        ssh_test_lib:find_handshake_parent(ssh_test_lib:server_port(Config)),
     ct:log("Found (Port=~p):~n"
            "  Connections  (length ~p): ~p~n"
            "  Handshakers  (length ~p): ~p~n"
            "  with parents (length ~p): ~p",
-           [server_port(Config),
+           [ssh_test_lib:server_port(Config),
             length(Conns), Conns,
             length(Handshakers), Handshakers,
             length(Parents), Parents]),
@@ -1030,12 +1023,13 @@ client_close_after_hello(Config0) ->
             timer:sleep((SleepSec+15)*1000),
             ct:log("After sleeping", []),
             ssh_info:print(fun ct:log/2),
-            {Parents2, Conns2, Handshakers2} = find_handshake_parent(server_port(Config)),
+            {Parents2, Conns2, Handshakers2} =
+                ssh_test_lib:find_handshake_parent(ssh_test_lib:server_port(Config)),
             ct:log("Found (Port=~p):~n"
                    "  Connections  (length ~p): ~p~n"
                    "  Handshakers  (length ~p): ~p~n"
                    "  with parents (length ~p): ~p",
-                   [server_port(Config),
+                   [ssh_test_lib:server_port(Config),
                     length(Conns2), Conns2,
                     length(Handshakers2), Handshakers2,
                     length(Parents2), Parents2]),
@@ -1143,8 +1137,10 @@ start_std_daemon(Config, ExtraOpts) ->
 
 
 stop_std_daemon(Config) ->
-    ssh:stop_daemon(server_pid(Config)),
-    ct:log("Std server ~p at ~p:~p stopped", [server_pid(Config), server_host(Config), server_port(Config)]),
+    ssh:stop_daemon(ssh_test_lib:server_pid(Config)),
+    ct:log("Std server ~p at ~p:~p stopped",
+           [ssh_test_lib:server_pid(Config), ssh_test_lib:server_host(Config),
+            ssh_test_lib:server_port(Config)]),
     lists:keydelete(server, 1, Config).
 
 
@@ -1152,28 +1148,25 @@ check_std_daemon_works(Config, Line) ->
     case std_connect(Config) of
 	{ok,C} ->
 	    ct:log("Server ~p:~p ~p is ok at line ~p",
-		   [server_host(Config), server_port(Config), 
-		    server_pid(Config), Line]),
+		   [ssh_test_lib:server_host(Config), ssh_test_lib:server_port(Config),
+		    ssh_test_lib:server_pid(Config), Line]),
 	    ok = ssh:close(C),
 	    Config;
 	Error = {error,_} ->
 	    ct:fail("Standard server ~p:~p ~p is ill at line ~p: ~p",
-		    [server_host(Config), server_port(Config), 
-		     server_pid(Config), Line, Error])
+		    [ssh_test_lib:server_host(Config), ssh_test_lib:server_port(Config),
+		     ssh_test_lib:server_pid(Config), Line, Error])
     end.
 
-server_pid(Config)  -> element(1,?v(server,Config)).
-server_host(Config) -> element(2,?v(server,Config)).
-server_port(Config) -> element(3,?v(server,Config)).
-
 server_user_password(Config) -> server_user_password(1, Config).
 
 server_user_password(N, Config) -> lists:nth(N, ?v(user_passwords,Config)).
     
 
-std_connect(Config) -> 
-    std_connect({server_host(Config), server_port(Config)}, Config).
-    
+std_connect(Config) ->
+    std_connect({ssh_test_lib:server_host(Config),
+                 ssh_test_lib:server_port(Config)}, Config).
+
 std_connect({Host,Port}, Config) ->
     std_connect({Host,Port}, Config, []).
 
@@ -1200,7 +1193,7 @@ connect_and_kex(Config) ->
 connect_and_kex(Config, InitialState) ->
     ssh_trpt_test_lib:exec(
       [{connect,
-	server_host(Config),server_port(Config),
+	ssh_test_lib:server_host(Config),ssh_test_lib:server_port(Config),
 	[{preferred_algorithms,[{kex,[?DEFAULT_KEX]},
                                 {cipher,?DEFAULT_CIPHERS}
                                ]},
@@ -1233,44 +1226,3 @@ disconnect(Code) ->
 	   tcp_closed,
 	   {tcp_error,econnaborted}
 	  ]}.
-
-%%%----------------------------------------------------------------
-find_handshake_parent(Port) ->
-    Acc = {_Parents=[], _Connections=[], _Handshakers=[]},
-    find_handshake_parent(supervisor:which_children(sshd_sup), Port, Acc).
-
-
-find_handshake_parent([{{ssh_system_sup,{address,_,Port,_}},
-                        Pid,supervisor, [ssh_system_sup]}|_],
-                      Port, Acc) ->
-    find_handshake_parent(supervisor:which_children(Pid), Port, Acc);
-
-find_handshake_parent([{{ssh_acceptor_sup,{address,_,Port,_}},
-                        PidS,supervisor,[ssh_acceptor_sup]}|T],
-                       Port, {AccP,AccC,AccH}) ->
-    ParentHandshakers =
-        [{PidW,PidH} ||
-            {{ssh_acceptor_sup,{address,_,Port1,_}}, PidW, worker,
-             [ssh_acceptor]} <- supervisor:which_children(PidS),
-            Port1 == Port,
-            PidH <- element(2, process_info(PidW,links)),
-            is_pid(PidH),
-            process_info(PidH,current_function) ==
-                {current_function,
-                 {ssh_connection_handler,handshake,4}}],
-    {Parents,Handshakers} = lists:unzip(ParentHandshakers),
-    find_handshake_parent(T, Port, {AccP++Parents, AccC, AccH++Handshakers});
-
-find_handshake_parent([{_Ref,PidS,supervisor,[ssh_connection_sup]}|T],
-                      Port, {AccP,AccC,AccH}) ->
-    Connections =
-        [Pid ||
-            {connection,Pid,worker,[ssh_connection_handler]} <-
-                supervisor:which_children(PidS)],
-    find_handshake_parent(T, Port, {AccP, AccC++Connections, AccH});
-
-find_handshake_parent([_|T], Port, Acc) ->
-    find_handshake_parent(T, Port, Acc);
-
-find_handshake_parent(_, _,  {AccP,AccC,AccH}) ->
-    {lists:usort(AccP), lists:usort(AccC), lists:usort(AccH)}.
diff --git a/lib/ssh/test/ssh_sup_SUITE.erl b/lib/ssh/test/ssh_sup_SUITE.erl
index e6245d65f1..3c2541aca4 100644
--- a/lib/ssh/test/ssh_sup_SUITE.erl
+++ b/lib/ssh/test/ssh_sup_SUITE.erl
@@ -48,6 +48,7 @@
 
 -define(SSHC_SUP(Pid), {sshc_sup, Pid, supervisor, [supervisor]}).
 -define(SSHD_SUP(Pid), {sshd_sup, Pid, supervisor, [supervisor]}).
+-define(LSOCKET_SUP(Pid), {ssh_lsocket_sup, Pid, supervisor, [ssh_lsocket_sup]}).
 -define(SYSTEM_SUP(Pid,Address),
         {{ssh_system_sup, Address}, Pid, supervisor,[ssh_system_sup]}).
 -define(CONNECTION_SUP(Pid), {_,Pid, supervisor,[ssh_connection_sup]}).
@@ -116,7 +117,8 @@ default_tree(Config) when is_list(Config) ->
     {value, ?SSHC_SUP(_)} = lists:keysearch(sshc_sup, 1, TopSupChildren),
     {value, ?SSHD_SUP(_)} = lists:keysearch(sshd_sup, 1, TopSupChildren),
     ?wait_match([], supervisor:which_children(sshc_sup)),
-    ?wait_match([], supervisor:which_children(sshd_sup)).
+    ?wait_match([?LSOCKET_SUP(_)],
+                supervisor:which_children(sshd_sup)).
 
 %%-------------------------------------------------------------------------
 sshc_subtree(Config) when is_list(Config) ->
@@ -160,14 +162,15 @@ sshd_subtree(Config) when is_list(Config) ->
     ct:log("Expect HostIP=~p, Port=~p, Daemon=~p",[HostIP,Port,Daemon]),
     ?wait_match([?SYSTEM_SUP(Daemon, #address{address=ListenIP,
                                               port=Port,
-                                              profile=?DEFAULT_PROFILE})],
+                                              profile=?DEFAULT_PROFILE}),
+                 ?LSOCKET_SUP(_)],
 		supervisor:which_children(sshd_sup),
 		[ListenIP,Daemon]),
     true = ssh_test_lib:match_ip(HostIP, ListenIP),
     check_sshd_system_tree(Daemon, HostIP, Port, Config),
     ssh:stop_daemon(HostIP, Port),
     ct:sleep(?WAIT_FOR_SHUTDOWN),
-    ?wait_match([], supervisor:which_children(sshd_sup)).
+    ?wait_match([?LSOCKET_SUP(_)], supervisor:which_children(sshd_sup)).
 
 %%-------------------------------------------------------------------------
 sshd_subtree_profile(Config) when is_list(Config) ->
@@ -181,14 +184,15 @@ sshd_subtree_profile(Config) when is_list(Config) ->
     ct:log("Expect HostIP=~p, Port=~p, Profile=~p, Daemon=~p",[HostIP,Port,Profile,Daemon]),
     ?wait_match([?SYSTEM_SUP(Daemon, #address{address=ListenIP,
                                               port=Port,
-                                              profile=Profile})],
+                                              profile=Profile}),
+                ?LSOCKET_SUP(_)],
 		supervisor:which_children(sshd_sup),
 		[ListenIP,Daemon]),
     true = ssh_test_lib:match_ip(HostIP, ListenIP),
     check_sshd_system_tree(Daemon, HostIP, Port, Config),
     ssh:stop_daemon(HostIP, Port, Profile),
     ct:sleep(?WAIT_FOR_SHUTDOWN),
-    ?wait_match([], supervisor:which_children(sshd_sup)).
+    ?wait_match([?LSOCKET_SUP(_)], supervisor:which_children(sshd_sup)).
 
 %%-------------------------------------------------------------------------
 killed_acceptor_restarts(Config) ->
diff --git a/lib/ssh/test/ssh_test_lib.erl b/lib/ssh/test/ssh_test_lib.erl
index 53e916df80..5f9fe2f540 100644
--- a/lib/ssh/test/ssh_test_lib.erl
+++ b/lib/ssh/test/ssh_test_lib.erl
@@ -124,7 +124,11 @@ setup_known_host/3,
 get_addr_str/0,
 file_base_name/2,
 kex_strict_negotiated/2,
-event_logged/3
+event_logged/3,
+server_host/1,
+server_port/1,
+server_pid/1,
+find_handshake_parent/1
         ]).
 %% logger callbacks and related helpers
 -export([log/2,
@@ -1372,3 +1376,43 @@ log(LogEvent = #{level:=_Level,msg:=_Msg,meta:=_Meta},
     #{test_ref := TestRef, recipient := Recipient}) ->
     Recipient ! {TestRef, LogEvent},
     ok.
+
+server_pid(Config)  -> element(1,?v(server,Config)).
+server_host(Config) -> element(2,?v(server,Config)).
+server_port(Config) -> element(3,?v(server,Config)).
+
+%%%----------------------------------------------------------------
+find_handshake_parent(Port) ->
+    Acc = {_Parents=[], _Connections=[], _Handshakers=[]},
+    find_handshake_parent(supervisor:which_children(sshd_sup), Port, Acc).
+
+find_handshake_parent([{{ssh_system_sup,{address,_,Port,_}},
+                        Pid,supervisor, [ssh_system_sup]}|_],
+                      Port, Acc) ->
+    find_handshake_parent(supervisor:which_children(Pid), Port, Acc);
+find_handshake_parent([{{ssh_acceptor_sup,{address,_,Port,_}},
+                        PidS,supervisor,[ssh_acceptor_sup]}|T],
+                       Port, {AccP,AccC,AccH}) ->
+    ParentHandshakers =
+        [{PidW,PidH} ||
+            {{ssh_acceptor_sup,{address,_,Port1,_}}, PidW, worker,
+             [ssh_acceptor]} <- supervisor:which_children(PidS),
+            Port1 == Port,
+            PidH <- element(2, process_info(PidW,links)),
+            is_pid(PidH),
+            process_info(PidH,current_function) ==
+                {current_function,
+                 {ssh_connection_handler,handshake,4}}],
+    {Parents,Handshakers} = lists:unzip(ParentHandshakers),
+    find_handshake_parent(T, Port, {AccP++Parents, AccC, AccH++Handshakers});
+find_handshake_parent([{_Ref,PidS,supervisor,[ssh_connection_sup]}|T],
+                      Port, {AccP,AccC,AccH}) ->
+    Connections =
+        [Pid ||
+            {connection,Pid,worker,[ssh_connection_handler]} <-
+                supervisor:which_children(PidS)],
+    find_handshake_parent(T, Port, {AccP, AccC++Connections, AccH});
+find_handshake_parent([_|T], Port, Acc) ->
+    find_handshake_parent(T, Port, Acc);
+find_handshake_parent(_, _,  {AccP,AccC,AccH}) ->
+    {lists:usort(AccP), lists:usort(AccC), lists:usort(AccH)}.
diff --git a/lib/ssh/test/ssh_test_lib.hrl b/lib/ssh/test/ssh_test_lib.hrl
index 6f782367ae..05ba19a1a6 100644
--- a/lib/ssh/test/ssh_test_lib.hrl
+++ b/lib/ssh/test/ssh_test_lib.hrl
@@ -10,6 +10,8 @@
 %% Timeout time in ms
 %%-------------------------------------------------------------------------
 -define(TIMEOUT, 15000).
+-define(v(Key, Config), proplists:get_value(Key, Config)).
+-define(v(Key, Config, Default), proplists:get_value(Key, Config, Default)).
 
 %%-------------------------------------------------------------------------
 %% Check for usable crypto
-- 
2.43.0

openSUSE Build Service is sponsored by