Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

kuba/ssh/server supervision tree lsocket sup #8968

Merged
merged 3 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/ssh/src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ MODULES= \
ssh_info \
ssh_io \
ssh_lib \
ssh_lsocket \
ssh_lsocket_sup \
ssh_message \
ssh_no_io \
ssh_options \
Expand Down
2 changes: 2 additions & 0 deletions lib/ssh/src/ssh.app.src
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
ssh_daemon_channel,
ssh_dbg,
ssh_lib,
ssh_lsocket_sup,
ssh_lsocket,
ssh_shell,
ssh_io,
ssh_info,
Expand Down
219 changes: 90 additions & 129 deletions lib/ssh/src/ssh.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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]).
Expand Down Expand Up @@ -552,13 +553,10 @@ daemon(Socket, UserOptions) ->
{error,SockError} ->
{error,SockError}
end;

{error,OptionError} ->
{error,OptionError}
end.



-doc """
daemon(HostAddress, Port, Options) -> Result

Expand Down Expand Up @@ -589,74 +587,57 @@ 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, {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
IngelaAndin marked this conversation as resolved.
Show resolved Hide resolved
end;
OptionError = {error,_} ->
OptionError
end;
daemon(_, _, _) ->
{error, badarg}.

Expand All @@ -682,9 +663,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 """
Expand Down Expand Up @@ -1239,21 +1220,18 @@ 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]};
IPaddr -> {IPaddr, Opts};
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),
Expand All @@ -1277,29 +1255,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),
Expand All @@ -1308,22 +1263,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=<I andalso I=<255 end,
Expand All @@ -1332,7 +1281,6 @@ is_host1(T) when tuple_size(T)==16 -> lists:all(fun(I) -> 0=<I andalso I=<65535
tuple_to_list(T));
is_host1(loopback) -> true.

%%%----------------------------------------------------------------
mangle_connect_address(A, #{socket_options := SockOpts}) ->
mangle_connect_address(A, SockOpts);
mangle_connect_address(A, SockOpts) ->
Expand All @@ -1353,7 +1301,6 @@ mangle_connect_address1(A, _) ->
_ -> A
end.

%%%----------------------------------------------------------------
mangle_tunnel_address(any) -> <<"">>;
mangle_tunnel_address(loopback) -> <<"localhost">>;
mangle_tunnel_address({0,0,0,0}) -> <<"">>;
Expand All @@ -1365,45 +1312,59 @@ 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}.

%%%################################################################
%%%#
%%%# Tracing
%%%#

-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) ->
Expand Down
Loading
Loading