diff --git a/lib/ssh/internal_doc/ssh_notes.md b/lib/ssh/internal_doc/ssh_notes.md index 2d3b8c25ebd4..ef8705fa153d 100644 --- a/lib/ssh/internal_doc/ssh_notes.md +++ b/lib/ssh/internal_doc/ssh_notes.md @@ -1,34 +1,73 @@ -# SSH supervision tree (prototype) +# SSH supervision tree (server side update >= OTP-28) ```mermaid --- -title: SSH supervision tree (prototype) +title: SSH supervision tree --- flowchart RL - d_sup --> sup[["ssh_sup\n(ssh_app.erl)\n[o4o]"]] + d_sup --> sup[["ssh_sup
(ssh_app.erl)
[o4o]"]] c_sup --> sup subgraph client - connection_sup --> c_sup[["sshc_sup\n(ssh_app.erl)\n[o4o]\nauto_shutdown=never"]] + connection_sup --> c_sup[["sshc_sup
(ssh_app.erl)
[o4o]
auto_shutdown=never"]] subgraph connection_c - connection_handler["ssh_connection_handler\nSIGNIFICANT"] --> connection_sup[["ssh_connection_sup\n[o4a]\nauto_shutdown=any_significant"]] - channel_sup[["ssh_channel_sup\n[o4o]"]] --> connection_sup + connection_handler["ssh_connection_handler
SIGNIFICANT"] --> connection_sup[["ssh_connection_sup
[o4a]
auto_shutdown=any_significant"]] + channel_sup[["ssh_channel_sup
[o4o]"]] --> connection_sup sftp["ssh_sftp"] --> channel_sup - tcpip_forward_acceptor_sup[["ssh_tcpip_forward_acceptor_sup\n[o4o]"]] --> connection_sup + tcpip_forward_acceptor_sup[["ssh_tcpip_forward_acceptor_sup
[o4o]"]] --> connection_sup + ssh_tcpip_forward_acceptor["ssh_tcpip_forward_acceptor"] --> tcpip_forward_acceptor_sup + end + end + + subgraph server + lsocket_sup[["ssh_lsocket_sup
[simple_one_for_one]"]] --> d_sup + ssh_lsocket_provider --> lsocket_sup + system_sup_s --> d_sup[["sshd_sup
(ssh_app.erl)
[o4o]"]] + acceptor_sup --> system_sup_s[["ssh_system_sup
[o4o]
auto_shutdown=all_significant"]] + acceptor["ssh_acceptor"] --> acceptor_sup[["ssh_acceptor_sup
[o4o?]
SIGNIFICANT"]] + + connection_sup_s --> system_sup_s + + subgraph connection_s + connection_handler_s["ssh_connection_handler
SIGNIFICANT"] --> connection_sup_s[["ssh_connection_sup
[o4a]
auto_shutdown=any_significant
SIGNIFICANT"]] + channel_sup_s[["ssh_channel_sup
[o4o]"]] --> connection_sup_s + tcpip_forward_acceptor_sup_s[["ssh_tcpip_forward_acceptor_sup
[o4o]"]] --> connection_sup_s + ssh_tcpip_forward_acceptor_s["ssh_tcpip_forward_acceptor"] --> tcpip_forward_acceptor_sup_s + sftd1["ssh_sftpd"] --> channel_sup_s + end + end +``` + +# SSH supervision tree (client side update since ssh-5.2.3, ssh-5.1.4.3, ssh-4.15.3.7) +```mermaid +--- +title: SSH supervision tree +--- +flowchart RL + d_sup --> sup[["ssh_sup
(ssh_app.erl)
[o4o]"]] + c_sup --> sup + + subgraph client + connection_sup --> c_sup[["sshc_sup
(ssh_app.erl)
[o4o]
auto_shutdown=never"]] + subgraph connection_c + connection_handler["ssh_connection_handler
SIGNIFICANT"] --> connection_sup[["ssh_connection_sup
[o4a]
auto_shutdown=any_significant"]] + channel_sup[["ssh_channel_sup
[o4o]"]] --> connection_sup + sftp["ssh_sftp"] --> channel_sup + tcpip_forward_acceptor_sup[["ssh_tcpip_forward_acceptor_sup
[o4o]"]] --> connection_sup ssh_tcpip_forward_acceptor["ssh_tcpip_forward_acceptor"] --> tcpip_forward_acceptor_sup end end subgraph server - system_sup_s --> d_sup[["sshd_sup\n(ssh_app.erl)\n[o4o]"]] - acceptor_sup --> system_sup_s[["ssh_system_sup\n[o4o]\nauto_shutdown=all_significant"]] - acceptor["ssh_acceptor"] --> acceptor_sup[["ssh_acceptor_sup\n[o4o]\nSIGNIFICANT"]] + system_sup_s --> d_sup[["sshd_sup
(ssh_app.erl)
[o4o]"]] + acceptor_sup --> system_sup_s[["ssh_system_sup
[o4o]
auto_shutdown=all_significant"]] + acceptor["ssh_acceptor"] --> acceptor_sup[["ssh_acceptor_sup
[o4o]
SIGNIFICANT"]] connection_sup_s --> system_sup_s subgraph connection_s - connection_handler_s["ssh_connection_handler\nSIGNIFICANT"] --> connection_sup_s[["ssh_connection_sup\n[o4a]\nauto_shutdown=any_significant\nSIGNIFICANT"]] - channel_sup_s[["ssh_channel_sup\n[o4o]"]] --> connection_sup_s - tcpip_forward_acceptor_sup_s[["ssh_tcpip_forward_acceptor_sup\n[o4o]"]] --> connection_sup_s + connection_handler_s["ssh_connection_handler
SIGNIFICANT"] --> connection_sup_s[["ssh_connection_sup
[o4a]
auto_shutdown=any_significant
SIGNIFICANT"]] + channel_sup_s[["ssh_channel_sup
[o4o]"]] --> connection_sup_s + tcpip_forward_acceptor_sup_s[["ssh_tcpip_forward_acceptor_sup
[o4o]"]] --> connection_sup_s ssh_tcpip_forward_acceptor_s["ssh_tcpip_forward_acceptor"] --> tcpip_forward_acceptor_sup_s sftd1["ssh_sftpd"] --> channel_sup_s end @@ -41,33 +80,32 @@ flowchart RL title: SSH supervision tree (OTP >= 24) --- flowchart RL - d_sup --> sup[["ssh_sup\n(ssh_app.erl)\n[o4o]"]] + d_sup --> sup[["ssh_sup
(ssh_app.erl)
[o4o]"]] c_sup --> sup subgraph client - system_sup --> c_sup[["sshc_sup\n(ssh_app.erl)\n[o4o]\nauto_shutdown=never"]] + system_sup --> c_sup[["sshc_sup
(ssh_app.erl)
[o4o]
auto_shutdown=never"]] subgraph connection_c - subsystem_sup --> system_sup[["ssh_system_sup\n[o4o]\nauto_shutdown=all_significant"]] - connection_handler["ssh_connection_handler\nSIGNIFICANT"] --> subsystem_sup[["ssh_subsystem_sup\n[o4a]\nauto_shutdown=any_significant\nSIGNIFICANT"]] - channel_sup[["ssh_channel_sup\n[o4o]"]] --> subsystem_sup + subsystem_sup --> system_sup[["ssh_system_sup
[o4o]
auto_shutdown=all_significant"]] + connection_handler["ssh_connection_handler
SIGNIFICANT"] --> subsystem_sup[["ssh_subsystem_sup
[o4a]
auto_shutdown=any_significant
SIGNIFICANT"]] + channel_sup[["ssh_channel_sup
[o4o]"]] --> subsystem_sup sftp["ssh_sftp"] --> channel_sup ssh_tcpip_forward_client --> channel_sup - tcpip_forward_acceptor_sup[["ssh_tcpip_forward_acceptor_sup\n[o4o]"]] --> subsystem_sup - ssh_tcpip_forward_acceptor["ssh_tcpip_forward_acceptor"] --> tcpip_forward_acceptor_sup + tcpip_forward_acceptor_sup[["ssh_tcpip_forward_acceptor_sup
[o4o]"]] --> subsystem_sup end end subgraph server - system_sup_s --> d_sup[["sshd_sup\n(ssh_app.erl)\n[o4o]"]] - acceptor_sup --> system_sup_s[["ssh_system_sup\n[o4o]\nauto_shutdown=all_significant"]] - acceptor["ssh_acceptor"] --> acceptor_sup[["ssh_acceptor_sup\n[o4o]\nSIGNIFICANT"]] - + system_sup_s --> d_sup[["sshd_sup
(ssh_app.erl)
[o4o]"]] + acceptor_sup --> system_sup_s[["ssh_system_sup
[o4o]
auto_shutdown=all_significant"]] + acceptor["ssh_acceptor"] --> acceptor_sup[["ssh_acceptor_sup
[o4o]
SIGNIFICANT"]] + acceptor_worker["acceptor
(parallel_login)"] o-. link .-o acceptor subsystem_sup_s --> system_sup_s subgraph connection_s - connection_handler_s["ssh_connection_handler\nSIGNIFICANT"] --> subsystem_sup_s[["ssh_subsystem_sup\n[o4a]\nauto_shutdown=any_significant\nSIGNIFICANT"]] - channel_sup_s[["ssh_channel_sup\n[o4o]"]] --> subsystem_sup_s - tcpip_forward_acceptor_sup_s[["ssh_tcpip_forward_acceptor_sup\n[o4o]"]] --> subsystem_sup_s + connection_handler_s["ssh_connection_handler
SIGNIFICANT"] --> subsystem_sup_s[["ssh_subsystem_sup
[o4a]
auto_shutdown=any_significant
SIGNIFICANT"]] + channel_sup_s[["ssh_channel_sup
[o4o]"]] --> subsystem_sup_s + tcpip_forward_acceptor_sup_s[["ssh_tcpip_forward_acceptor_sup
[o4o]"]] --> subsystem_sup_s ssh_tcpip_forward_acceptor_s["ssh_tcpip_forward_acceptor"] --> tcpip_forward_acceptor_sup_s sftd1["ssh_sftpd"] --> channel_sup_s ssh_tcpip_forward_srv --> channel_sup_s @@ -83,24 +121,24 @@ flowchart RL title: SSH supervision tree (OTP-22) --- flowchart RL - d_sup --> sup[["ssh_sup\n(ssh_app.erl)\n[o4o]"]] + d_sup --> sup[["ssh_sup
(ssh_app.erl)
[o4o]"]] c_sup --> sup subgraph client - connection_handler["ssh_connection_handler\nSIGNIFICANT?"] --> c_sup + connection_handler["ssh_connection_handler
SIGNIFICANT?"] --> c_sup end subgraph server - system_sup_s --> d_sup[["sshd_sup\n(ssh_app.erl)\n[o4o]"]] - acceptor_sup --> system_sup_s[["ssh_system_sup\n[o4o]\nauto_shutdown=all_significant"]] - acceptor["ssh_acceptor"] --> acceptor_sup[["ssh_acceptor_sup\n[o4o]\nSIGNIFICANT"]] + system_sup_s --> d_sup[["sshd_sup
(ssh_app.erl)
[o4o]"]] + acceptor_sup --> system_sup_s[["ssh_system_sup
[o4o]
auto_shutdown=all_significant"]] + acceptor["ssh_acceptor"] --> acceptor_sup[["ssh_acceptor_sup
[o4o]
SIGNIFICANT"]] subsystem_sup_s --> system_sup_s subgraph connection_s - connection_handler_s["ssh_connection_handler\nSIGNIFICANT"] --> subsystem_sup_s[["ssh_subsystem_sup\n[o4a]\nauto_shutdown=any_significant\nSIGNIFICANT"]] - channel_sup_s[["ssh_channel_sup\n[o4o]"]] --> subsystem_sup_s - tcpip_forward_acceptor_sup_s[["ssh_tcpip_forward_acceptor_sup\n[o4o]"]] --> subsystem_sup_s + connection_handler_s["ssh_connection_handler
SIGNIFICANT"] --> subsystem_sup_s[["ssh_subsystem_sup
[o4a]
auto_shutdown=any_significant
SIGNIFICANT"]] + channel_sup_s[["ssh_channel_sup
[o4o]"]] --> subsystem_sup_s + tcpip_forward_acceptor_sup_s[["ssh_tcpip_forward_acceptor_sup
[o4o]"]] --> subsystem_sup_s sftd1["ssh_sftpd"] --> channel_sup_s end end diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile index a8a088eb7daf..f59c997a1684 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 6cbbc168ac22..a59fd58ad956 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 4b078a7b8901..c651d336fbc0 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]). @@ -552,13 +553,10 @@ daemon(Socket, UserOptions) -> {error,SockError} -> {error,SockError} end; - {error,OptionError} -> {error,OptionError} end. - - -doc """ daemon(HostAddress, Port, Options) -> Result @@ -589,74 +587,60 @@ The rules for handling the two address passing options are: is set to the value of the 'ip'-option """. -spec daemon(any | inet:ip_address(), inet:port_number(), daemon_options()) -> {ok,daemon_ref()} | {error,term()} - ;(socket, open_socket(), daemon_options()) -> {ok,daemon_ref()} | {error,term()} - . + ;(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}} - end; +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, LSocket}} -> + {Host, Port, Options1} = + update_lsocket(LSocket, LSocketProvider, Options0), + try + %% Host,Port is what to use for the system + %% supervisor to register its name (see + %% #address record); LSocket is owned by + %% LSocketProvider process. Ownership will be + %% transferred once ssh_acceptor_sup is + %% started. + %% 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, _}} -> % ssh_system_sup with #address already register + close_listen_socket(LSocket, Options1), + {error, eaddrinuse}; + {error, Error} -> + close_listen_socket(LSocket, Options1), + {error, Error} + catch + error:{shutdown, Err} -> % no suitable host key + close_listen_socket(LSocket, Options1), + {error, Err}; + exit:{noproc, _} -> % ssh application not started + close_listen_socket(LSocket, Options1), + {error, ssh_not_started}; + error:Error -> + close_listen_socket(LSocket, Options1), + {error, Error}; + _C:_E -> + {error,{cannot_start_daemon,_C,_E}} + end; + {error, {_, LSocketError}} -> + {error, LSocketError} + end; + OptionError = {error, _} -> + OptionError + end; daemon(_, _, _) -> {error, badarg}. @@ -682,9 +666,9 @@ chapters [Configuration in SSH](configurations.md) and NewUserOptions :: daemon_options(). daemon_replace_options(DaemonRef, NewUserOptions) -> - {ok,Os0} = ssh_system_sup:get_acceptor_options(DaemonRef), - Os1 = ssh_options:merge_options(server, NewUserOptions, Os0), - ssh_system_sup:replace_acceptor_options(DaemonRef, Os1). + {ok, Options0} = ssh_system_sup:get_acceptor_options(DaemonRef), + Options = ssh_options:merge_options(server, NewUserOptions, Options0), + ssh_system_sup:restart_acceptor(DaemonRef, Options). %%-------------------------------------------------------------------- -doc """ @@ -1239,13 +1223,11 @@ fp_fmt(b64, Bin) -> %%-------------------------------------------------------------------- %% The handle_daemon_args/2 function basically only sets the ip-option in Opts %% so that it is correctly set when opening the listening socket. - handle_daemon_args(any, Opts) -> case proplists:get_value(ip, Opts) of undefined -> {any, Opts}; IP -> {IP, Opts} end; - handle_daemon_args(IPaddr, Opts) when is_tuple(IPaddr) ; IPaddr == loopback -> case proplists:get_value(ip, Opts) of undefined -> {IPaddr, [{ip,IPaddr}|Opts]}; @@ -1253,7 +1235,6 @@ handle_daemon_args(IPaddr, Opts) when is_tuple(IPaddr) ; IPaddr == loopback -> IP -> {IPaddr, [{ip,IPaddr}|Opts--[{ip,IP}]]} %% Backward compatibility end. -%%%---------------------------------------------------------------- valid_socket_to_use(Socket, {tcp,_,_}) -> %% Is this tcp-socket a valid socket? try {is_tcp_socket(Socket), @@ -1277,29 +1258,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,22 +1266,16 @@ 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)). - -%%%---------------------------------------------------------------- + -doc false. is_host(X, Opts) -> try is_host1(mangle_connect_address(X, Opts)) catch _:_ -> false end. - is_host1(L) when is_list(L) -> true; %% "string()" is_host1(T) when tuple_size(T)==4 -> lists:all(fun(I) -> 0= lists:all(fun(I) -> 0= true. -%%%---------------------------------------------------------------- mangle_connect_address(A, #{socket_options := SockOpts}) -> mangle_connect_address(A, SockOpts); mangle_connect_address(A, SockOpts) -> @@ -1353,7 +1304,6 @@ mangle_connect_address1(A, _) -> _ -> A end. -%%%---------------------------------------------------------------- mangle_tunnel_address(any) -> <<"">>; mangle_tunnel_address(loopback) -> <<"localhost">>; mangle_tunnel_address({0,0,0,0}) -> <<"">>; @@ -1365,7 +1315,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 +1328,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 a558a888d2c2..62528d2e710a 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 +%% 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 - 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 1661134285ec..9b710dc44c13 100644 --- a/lib/ssh/src/ssh_acceptor_sup.erl +++ b/lib/ssh/src/ssh_acceptor_sup.erl @@ -20,7 +20,7 @@ %% %%---------------------------------------------------------------------- -%% Purpose: The acceptor supervisor for ssh servers hangs under +%% Purpose: The acceptor supervisor for ssh servers hangs under %% ssh_system_sup. %%---------------------------------------------------------------------- @@ -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 5f0c01e7b815..91c688f64d7c 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_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 6502fed79849..8746c2bbd4e8 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -2154,16 +2154,20 @@ ssh_dbg_format(connections, {call, {?MODULE,init, [[Role, Sock, Opts]]}}) -> end end, Opts), - {ok, {IPp,Portp}} = inet:peername(Sock), - {ok, {IPs,Ports}} = inet:sockname(Sock), + Addresses = + case {inet:peername(Sock), inet:sockname(Sock)} of + {{ok, {IPp,Portp}}, {ok, {IPs,Ports}}} -> + io_lib:format("Socket = ~p, Peer = ~s, Local = ~s,~n" + "Non-default options:~n~p", + [Sock, ssh_lib:format_address_port(IPp,Portp), + ssh_lib:format_address_port(IPs,Ports), NonDefaultOpts]); + {E1, E2} -> + io_lib:format("Socket = ~p, Peer = ~p, Local = ~p,~n" + "Non-default options:~n~p", + [Sock, E1, E2, NonDefaultOpts]) + end, [io_lib:format("Starting ~p connection:\n",[Role]), - io_lib:format("Socket = ~p, Peer = ~s, Local = ~s,~n" - "Non-default options:~n~p", - [Sock, - ssh_lib:format_address_port(IPp,Portp), - ssh_lib:format_address_port(IPs,Ports), - NonDefaultOpts]) - ]; + Addresses]; ssh_dbg_format(connections, F) -> ssh_dbg_format(terminate, F); diff --git a/lib/ssh/src/ssh_lsocket.erl b/lib/ssh/src/ssh_lsocket.erl new file mode 100644 index 000000000000..e92d88bac157 --- /dev/null +++ b/lib/ssh/src/ssh_lsocket.erl @@ -0,0 +1,175 @@ +%% +%% %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/4, 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) -> + try + supervisor:start_child(ssh_lsocket_sup, [self(), Host, Port, Options]) + of + {ok, LSocketProvider} -> + receive + Result = {_, {LSocketProvider, _}} -> + Result + after + ?DEFAULT_TIMEOUT -> + {error, LSocketProvider, no_response_from_lsocket_provider} + end + catch + exit:{noproc, _} -> + {error, {no_provider_pid, ssh_not_started}} + end. + +start_link(Caller, Host, Port, Options) -> + {ok, proc_lib:spawn_link(?MODULE, provide_lsocket, + [Caller, Host, Port, Options])}. + +provide_lsocket(Caller, _Host1, Port0, Options) -> + ListenResult = + try + try_listen(Port0, Options, 4) + of + {ok, LSocket} -> + {ok, {self(), LSocket}}; + {error, Details} -> + {error, {self(), Details}} + catch + _Class:Exception -> + {error, {self(), Exception}} + end, + case ListenResult of + {ok, {_, LSocket1}} -> + Caller ! ListenResult, + wait_for_acceptor_sup(LSocket1, Options), + ok; + {error, {_, _}} -> + Caller ! ListenResult + 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 + 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, 4, 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, 4), + 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 000000000000..1922268c3734 --- /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_options.erl b/lib/ssh/src/ssh_options.erl index 7aaeb4603dee..6a55954bd0ac 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -229,7 +229,6 @@ handle_options(Role, OptsList0, Opts0) when is_map(Opts0), %% Enter the user's values into the map; unknown keys are %% treated as socket options check_and_save(OptsList2, OptionDefinitions, InitialMap) - catch error:{EO, KV, Reason} when EO == eoptions ; EO == eerl_env -> if diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl index bc1d79d0f49a..f6b78d4e738f 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,Error} 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, LSocket}} -> + {_Host, _Port, Options} = + ssh:update_lsocket(LSocket, LSocketProvider, Options0), + Options; + 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 bcf4c7f859e1..c41a200f3087 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, @@ -138,32 +136,25 @@ groups() -> known_hosts, ssh_file_is_host_key, ssh_file_is_host_key_misc, - ssh_file_is_auth_key - ]}, - + ssh_file_is_auth_key]}, {key_cb, [?PARALLEL], [key_callback, key_callback_options]}, - {internal_error, [?PARALLEL], [internal_error]}, - {login_bad_pwd_no_retry, [?PARALLEL], [login_bad_pwd_no_retry1, 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 +1029,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]) @@ -1502,7 +1516,6 @@ check_error(Error) -> ct:fail(Error). basic_test(Config) -> ClientOpts = proplists:get_value(client_opts, Config), ServerOpts = proplists:get_value(server_opts, Config), - {Pid, Host, Port} = ssh_test_lib:daemon(ServerOpts), CM = ssh_test_lib:connect(Host, Port, ClientOpts), ok = ssh:close(CM), diff --git a/lib/ssh/test/ssh_dbg_SUITE.erl b/lib/ssh/test/ssh_dbg_SUITE.erl index adeeb07b7d18..fd55c4211c37 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 7b8d4e9767be..58d1b7c310bf 100644 --- a/lib/ssh/test/ssh_protocol_SUITE.erl +++ b/lib/ssh/test/ssh_protocol_SUITE.erl @@ -94,16 +94,9 @@ [{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)). - - %%-------------------------------------------------------------------- %% 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} ]}, @@ -1010,16 +1002,15 @@ client_close_after_hello(Config0) -> ]}, {send, hello} ]) || _ <- lists:seq(1,MaxSessions+100)], - 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 +1021,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 +1135,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 +1146,24 @@ 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 +1190,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 +1223,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 e6245d65f1ad..3c2541aca4b5 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 53e916df8081..5f9fe2f5405f 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 6f782367ae5a..05ba19a1a647 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, 27000). +-define(v(Key, Config), proplists:get_value(Key, Config)). +-define(v(Key, Config, Default), proplists:get_value(Key, Config, Default)). %%------------------------------------------------------------------------- %% Check for usable crypto