aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHans Nilsson <[email protected]>2016-04-07 16:30:35 +0200
committerHans Nilsson <[email protected]>2016-04-28 20:37:30 +0200
commitb90f22861404f2a2cdd305055c786bb73464af01 (patch)
tree0f22a7b8b122e3ac6c1a7f52daeaa56d24bf9584
parentb9e3e212009162d8223436032282efbc5c826cc7 (diff)
downloadotp-b90f22861404f2a2cdd305055c786bb73464af01.tar.gz
otp-b90f22861404f2a2cdd305055c786bb73464af01.tar.bz2
otp-b90f22861404f2a2cdd305055c786bb73464af01.zip
ssh: make ssh:daemon choose port when Port=0 in the arguments
-rw-r--r--lib/ssh/doc/src/ssh.xml15
-rw-r--r--lib/ssh/src/ssh.erl85
-rw-r--r--lib/ssh/src/ssh_acceptor.erl45
-rw-r--r--lib/ssh/src/ssh_acceptor_sup.erl5
-rw-r--r--lib/ssh/src/ssh_system_sup.erl9
5 files changed, 124 insertions, 35 deletions
diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml
index 850557444d..a9e843c36c 100644
--- a/lib/ssh/doc/src/ssh.xml
+++ b/lib/ssh/doc/src/ssh.xml
@@ -359,7 +359,8 @@
</type>
<desc>
<p>Starts a server listening for SSH connections on the given
- port.</p>
+ port. If the <c>Port</c> is 0, a random free port is selected. See
+ <seealso marker="#daemon_info/1">daemon_info/1</seealso> about how to find the selected port number.</p>
<p>Options:</p>
<taglist>
<tag><c><![CDATA[{inet, inet | inet6}]]></c></tag>
@@ -680,6 +681,18 @@
</func>
<func>
+ <name>daemon_info(Daemon) -> {ok, [{port,Port}]} | {error,Error}</name>
+ <fsummary>Get info about a daemon</fsummary>
+ <type>
+ <v>Port = integer()</v>
+ <v>Error = bad_daemon_ref</v>
+ </type>
+ <desc>
+ <p>Returns a key-value list with information about the daemon. For now, only the listening port is returned. This is intended for the case the daemon is started with the port set to 0.</p>
+ </desc>
+ </func>
+
+ <func>
<name>default_algorithms() -> algs_list()</name>
<fsummary>Get a list declaring the supported algorithms</fsummary>
<desc>
diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl
index d0121e73ba..5c5d59481f 100644
--- a/lib/ssh/src/ssh.erl
+++ b/lib/ssh/src/ssh.erl
@@ -30,6 +30,7 @@
-export([start/0, start/1, stop/0, connect/3, connect/4, close/1, connection_info/2,
channel_info/3,
daemon/1, daemon/2, daemon/3,
+ daemon_info/1,
default_algorithms/0,
stop_listener/1, stop_listener/2, stop_listener/3,
stop_daemon/1, stop_daemon/2, stop_daemon/3,
@@ -153,6 +154,19 @@ daemon(HostAddr, Port, Options0) ->
start_daemon(Host, Port, Options, Inet).
%%--------------------------------------------------------------------
+daemon_info(Pid) ->
+ case catch ssh_system_sup:acceptor_supervisor(Pid) of
+ AsupPid when is_pid(AsupPid) ->
+ [Port] =
+ [Prt || {{ssh_acceptor_sup,any,Prt,default},
+ _WorkerPid,worker,[ssh_acceptor]} <- supervisor:which_children(AsupPid)],
+ {ok, [{port,Port}]};
+
+ _ ->
+ {error,bad_daemon_ref}
+ end.
+
+%%--------------------------------------------------------------------
-spec stop_listener(pid()) -> ok.
-spec stop_listener(inet:ip_address(), integer()) -> ok.
%%
@@ -243,32 +257,52 @@ start_daemon(Host, Port, Options, Inet) ->
end
end.
-do_start_daemon(Host0, Port0, Options, SocketOptions) ->
- {Host,Port} = try
- case proplists:get_value(fd, SocketOptions) of
- undefined ->
- {Host0,Port0};
- Fd when Port0==0 ->
- find_hostport(Fd);
- _ ->
- {Host0,Port0}
- end
- catch
- _:_ -> throw(bad_fd)
- end,
- Profile = proplists:get_value(profile, Options, ?DEFAULT_PROFILE),
+do_start_daemon(Host0, Port0, SshOptions, SocketOptions) ->
+ {Host,Port1} =
+ try
+ case proplists:get_value(fd, SocketOptions) of
+ undefined ->
+ {Host0,Port0};
+ Fd when Port0==0 ->
+ find_hostport(Fd);
+ _ ->
+ {Host0,Port0}
+ end
+ catch
+ _:_ -> throw(bad_fd)
+ end,
+ Profile = proplists:get_value(profile, SshOptions, ?DEFAULT_PROFILE),
+ {Port, WaitRequestControl, Opts} =
+ case Port1 of
+ 0 -> %% Allocate the socket here to get the port number...
+ {_, Callback, _} =
+ proplists:get_value(transport, SshOptions, {tcp, gen_tcp, tcp_closed}),
+ {ok,LSock} = ssh_acceptor:callback_listen(Callback, 0, SocketOptions),
+ {ok,{_,LPort}} = inet:sockname(LSock),
+ {LPort,
+ {LSock,Callback},
+ [{lsocket,LSock},{lsock_owner,self()}]
+ };
+ _ ->
+ {Port1, false, []}
+ end,
case ssh_system_sup:system_supervisor(Host, Port, Profile) of
undefined ->
%% It would proably make more sense to call the
%% address option host but that is a too big change at the
%% monent. The name is a legacy name!
try sshd_sup:start_child([{address, Host},
- {port, Port}, {role, server},
+ {port, Port},
+ {role, server},
{socket_opts, SocketOptions},
- {ssh_opts, Options}]) of
+ {ssh_opts, SshOptions}
+ | Opts]) of
{error, {already_started, _}} ->
{error, eaddrinuse};
- Result = {Code, _} when (Code == ok) or (Code == error) ->
+ Result = {ok,_} ->
+ sync_request_control(WaitRequestControl),
+ Result;
+ Result = {error, _} ->
Result
catch
exit:{noproc, _} ->
@@ -277,18 +311,31 @@ do_start_daemon(Host0, Port0, Options, SocketOptions) ->
Sup ->
AccPid = ssh_system_sup:acceptor_supervisor(Sup),
case ssh_acceptor_sup:start_child(AccPid, [{address, Host},
- {port, Port}, {role, server},
+ {port, Port},
+ {role, server},
{socket_opts, SocketOptions},
- {ssh_opts, Options}]) of
+ {ssh_opts, SshOptions}
+ | Opts]) of
{error, {already_started, _}} ->
{error, eaddrinuse};
{ok, _} ->
+ sync_request_control(WaitRequestControl),
{ok, Sup};
Other ->
Other
end
end.
+sync_request_control(false) ->
+ ok;
+sync_request_control({LSock,Callback}) ->
+ receive
+ {request_control,LSock,ReqPid} ->
+ ok = Callback:controlling_process(LSock, ReqPid),
+ ReqPid ! {its_yours,LSock},
+ ok
+ end.
+
find_hostport(Fd) ->
%% Using internal functions inet:open/8 and inet:close/0.
%% Don't try this at home unless you know what you are doing!
diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl
index d94dedf1bf..90fd951dcd 100644
--- a/lib/ssh/src/ssh_acceptor.erl
+++ b/lib/ssh/src/ssh_acceptor.erl
@@ -26,7 +26,8 @@
%% Internal application API
-export([start_link/5,
- number_of_connections/1]).
+ number_of_connections/1,
+ callback_listen/3]).
%% spawn export
-export([acceptor_init/6, acceptor_loop/6]).
@@ -46,15 +47,39 @@ start_link(Port, Address, SockOpts, Opts, AcceptTimeout) ->
acceptor_init(Parent, Port, Address, SockOpts, Opts, AcceptTimeout) ->
{_, Callback, _} =
proplists:get_value(transport, Opts, {tcp, gen_tcp, tcp_closed}),
- case (catch do_socket_listen(Callback, Port, [{active, false} | SockOpts])) of
- {ok, ListenSocket} ->
+
+ SockOwner = proplists:get_value(lsock_owner, Opts),
+ LSock = proplists:get_value(lsocket, Opts),
+ UseExistingSocket =
+ case catch inet:sockname(LSock) of
+ {ok,{_,Port}} -> is_pid(SockOwner);
+ _ -> false
+ end,
+
+ case UseExistingSocket of
+ true ->
proc_lib:init_ack(Parent, {ok, self()}),
- acceptor_loop(Callback,
- Port, Address, Opts, ListenSocket, AcceptTimeout);
- Error ->
- proc_lib:init_ack(Parent, Error),
- error
+ request_ownership(LSock, SockOwner),
+ acceptor_loop(Callback, Port, Address, Opts, LSock, AcceptTimeout);
+
+ false ->
+ case (catch do_socket_listen(Callback, Port, SockOpts)) of
+ {ok, ListenSocket} ->
+ proc_lib:init_ack(Parent, {ok, self()}),
+ acceptor_loop(Callback,
+ Port, Address, Opts, ListenSocket, AcceptTimeout);
+ Error ->
+ proc_lib:init_ack(Parent, Error),
+ error
+ end
end.
+
+request_ownership(LSock, SockOwner) ->
+ SockOwner ! {request_control,LSock,self()},
+ receive
+ {its_yours,LSock} -> ok
+ end.
+
do_socket_listen(Callback, Port0, Opts) ->
Port =
@@ -62,6 +87,10 @@ do_socket_listen(Callback, Port0, Opts) ->
undefined -> Port0;
_ -> 0
end,
+ callback_listen(Callback, Port, Opts).
+
+callback_listen(Callback, Port, Opts0) ->
+ Opts = [{active, false}, {reuseaddr,true} | Opts0],
case Callback:listen(Port, Opts) of
{error, nxdomain} ->
Callback:listen(Port, lists:delete(inet6, Opts));
diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl
index b2f489a971..4f76dbe6f0 100644
--- a/lib/ssh/src/ssh_acceptor_sup.erl
+++ b/lib/ssh/src/ssh_acceptor_sup.erl
@@ -85,10 +85,7 @@ child_spec(ServerOpts) ->
Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE),
Name = id(Address, Port, Profile),
SocketOpts = proplists:get_value(socket_opts, ServerOpts),
- StartFunc = {ssh_acceptor, start_link, [Port, Address,
- [{active, false},
- {reuseaddr, true}] ++ SocketOpts,
- ServerOpts, Timeout]},
+ StartFunc = {ssh_acceptor, start_link, [Port, Address, SocketOpts, ServerOpts, Timeout]},
Restart = transient,
Shutdown = brutal_kill,
Modules = [ssh_acceptor],
diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl
index 6314671f0d..9a9786a914 100644
--- a/lib/ssh/src/ssh_system_sup.erl
+++ b/lib/ssh/src/ssh_system_sup.erl
@@ -192,6 +192,9 @@ stop_acceptor(Sup) ->
[{Name, AcceptorSup}] =
[{SupName, ASup} || {SupName, ASup, _, [ssh_acceptor_sup]} <-
supervisor:which_children(Sup)],
- supervisor:terminate_child(AcceptorSup, Name).
-
-
+ case supervisor:terminate_child(AcceptorSup, Name) of
+ ok ->
+ supervisor:delete_child(AcceptorSup, Name);
+ Error ->
+ Error
+ end.