From e20ce5b9174e5ac0e1279a1af5be80f9c1b35caa Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Tue, 21 Mar 2017 15:21:46 +0100 Subject: ssh: handle HostAddr arg and ip-option for daemons --- lib/ssh/src/ssh.erl | 221 +++++++++++++++++++++++++++++----------------------- 1 file changed, 124 insertions(+), 97 deletions(-) diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index c139556791..ff424b738c 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -182,16 +182,86 @@ daemon(Port) -> daemon(Port, []). -daemon(Port, UserOptions) when is_integer(Port), Port >= 0 -> - daemon(any, Port, UserOptions); - daemon(Socket, UserOptions) when is_port(Socket) -> - daemon(socket, Socket, UserOptions). + try + #{} = Options = ssh_options:handle_options(server, UserOptions), + case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of + ok -> + {ok, {IP,Port}} = inet:sockname(Socket), + finalize_start(fmt_host(IP), Port, ?GET_OPT(profile, Options), + ?PUT_INTERNAL_OPT({connected_socket, Socket}, Options), + fun(Opts, DefaultResult) -> + try ssh_acceptor:handle_established_connection( + ?GET_INTERNAL_OPT(address, Opts), + ?GET_INTERNAL_OPT(port, Opts), + Opts, + Socket) + of + {error,Error} -> + {error,Error}; + _ -> + DefaultResult + catch + C:R -> + {error,{could_not_start_connection,{C,R}}} + end + end); + {error,SockError} -> + {error,SockError} + 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(Port, UserOptions) when 0 =< Port, Port =< 65535 -> + daemon(any, Port, UserOptions). + +daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535 -> + try + {Host1, UserOptions} = handle_daemon_args(Host0, UserOptions0), + #{} = Options0 = ssh_options:handle_options(server, UserOptions), + + {{Host,Port}, ListenSocket} = + open_listen_socket(Host1, Port0, Options0), + + %% Now Host,Port is what to use for the supervisor to register its name, + %% and ListenSocket is for listening on connections. But it is still owned + %% by self()... + + finalize_start(fmt_host(Host), Port, ?GET_OPT(profile, Options0), + ?PUT_INTERNAL_OPT({lsocket,{ListenSocket,self()}}, Options0), + fun(Opts, Result) -> + {_, Callback, _} = ?GET_OPT(transport, Opts), + receive + {request_control, ListenSocket, ReqPid} -> + ok = Callback:controlling_process(ListenSocket, ReqPid), + ReqPid ! {its_yours,ListenSocket}, + Result + end + 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, Port, UserOptions0) -> - {Host, UserOptions} = handle_daemon_args(Host0, UserOptions0), - start_daemon(Host, Port, ssh_options:handle_options(server, UserOptions)). %%-------------------------------------------------------------------- -spec daemon_info(daemon_ref()) -> ok_error( [{atom(), term()}] ). @@ -291,21 +361,49 @@ default_algorithms() -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -handle_daemon_args(Host, UserOptions0) -> - case Host of - socket -> - {Host, UserOptions0}; - any -> - {ok, Host0} = inet:gethostname(), - Inet = proplists:get_value(inet, UserOptions0, inet), - {Host0, [Inet | UserOptions0]}; - {_,_,_,_} -> - {Host, [inet, {ip,Host} | UserOptions0]}; - {_,_,_,_,_,_,_,_} -> - {Host, [inet6, {ip,Host} | UserOptions0]}; - _ -> - error(badarg) - end. +handle_daemon_args(HostAddr, Opts) -> + IP = proplists:get_value(ip, Opts), + IPh = case inet:parse_strict_address(HostAddr) of + {ok, IPtuple} -> IPtuple; + {error, einval} when is_tuple(HostAddr), + size(HostAddr)==4 ; size(HostAddr)==6 -> HostAddr; + _ -> undefined + end, + handle_daemon_args(HostAddr, IPh, IP, Opts). + + +%% HostAddr is 'any' +handle_daemon_args(any, undefined, undefined, Opts) -> {any, Opts}; +handle_daemon_args(any, undefined, IP, Opts) -> {IP, Opts}; + +%% HostAddr is 'loopback' or "localhost" +handle_daemon_args(loopback, undefined, {127,_,_,_}=IP, Opts) -> {IP, Opts}; +handle_daemon_args(loopback, undefined, {0,0,0,0,0,0,0,1}=IP, Opts) -> {IP, Opts}; +handle_daemon_args(loopback, undefined, undefined, Opts) -> + IP = case proplists:get_value(inet,Opts) of + true -> {127,0,0,1}; + inet -> {127,0,0,1}; + inet6 -> {0,0,0,0,0,0,0,1}; + _ -> case proplists:get_value(inet6,Opts) of + true -> {0,0,0,0,0,0,0,1}; + _ -> {127,0,0,1} % default if no 'inet' nor 'inet6' + end + end, + {IP, [{ip,IP}|Opts]}; +handle_daemon_args("localhost", IPh, IP, Opts) -> + handle_daemon_args(loopback, IPh, IP, Opts); + +%% HostAddr is ip and no ip-option +handle_daemon_args(_, IP, undefined, Opts) when is_tuple(IP) -> {IP, [{ip,IP}|Opts]}; + +%% HostAddr and ip-option are equal +handle_daemon_args(_, IP, IP, Opts) when is_tuple(IP) -> {IP, Opts}; + +%% HostAddr is ip, but ip-option is different! +handle_daemon_args(_, IPh, IPo, _) when is_tuple(IPh), is_tuple(IPo) -> error({eoption,{ip,IPo}}); + +%% Something else. Whatever it is, it is wrong. +handle_daemon_args(_, _, _, _) -> error(badarg). %%%---------------------------------------------------------------- valid_socket_to_use(Socket, {tcp,_,_}) -> @@ -332,97 +430,25 @@ is_tcp_socket(Socket) -> end. %%%---------------------------------------------------------------- -start_daemon(_, _, {error,Error}) -> - {error,Error}; - -start_daemon(socket, Socket, Options) -> - case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of - ok -> - start_daemon(inet:sockname(Socket), Socket, Options); - {error,SockError} -> - {error,SockError} - end; - -start_daemon(Host, Port, Options) -> - try - do_start_daemon(Host, Port, Options) - catch - throw:bad_fd -> - {error,bad_fd}; - throw:bad_socket -> - {error,bad_socket}; - error:{badmatch,{error,Error}} -> - {error,Error}; - _C:_E -> - {error,{cannot_start_daemon,_C,_E}} - end. - -%%%---------------------------------------------------------------- -do_start_daemon({error,Error}, _, _) -> - {error,Error}; - -do_start_daemon({ok, {IP,Port}}, Socket, Options0) -> - finalize_start(fmt_host(IP), - Port, - ?PUT_INTERNAL_OPT({connected_socket, Socket}, Options0), - fun(Opts, DefaultResult) -> - try ssh_acceptor:handle_established_connection( - ?GET_INTERNAL_OPT(address, Opts), - ?GET_INTERNAL_OPT(port, Opts), - Opts, - Socket) - of - {error,Error} -> - {error,Error}; - _ -> - DefaultResult - catch - C:R -> - {error,{could_not_start_connection,{C,R}}} - end - end); - -do_start_daemon(Host0, Port0, Options0) -> - {{Host,Port}, ListenSocket} = - open_listen_socket(Host0, Port0, Options0), - - %% Now Host,Port is what to use for the supervisor to register its name, - %% and ListenSocket is for listening on connections. But it is still owned - %% by self()... - - finalize_start(Host, Port, - ?PUT_INTERNAL_OPT({lsocket,{ListenSocket,self()}}, Options0), - fun(Opts, Result) -> - {_, Callback, _} = ?GET_OPT(transport, Opts), - receive - {request_control, ListenSocket, ReqPid} -> - ok = Callback:controlling_process(ListenSocket, ReqPid), - ReqPid ! {its_yours,ListenSocket}, - Result - end - end). - - open_listen_socket(Host0, Port0, Options0) -> case ?GET_SOCKET_OPT(fd, Options0) of undefined -> {ok,LSock} = ssh_acceptor:listen(Port0, Options0), {ok,{_,LPort}} = inet:sockname(LSock), - {{fmt_host(Host0),LPort}, LSock}; + {{Host0,LPort}, LSock}; Fd when is_integer(Fd) -> %% Do gen_tcp:listen with the option {fd,Fd}: {ok,LSock} = ssh_acceptor:listen(0, Options0), {ok,{LHost,LPort}} = inet:sockname(LSock), - {{fmt_host(LHost),LPort}, LSock} + {{LHost,LPort}, LSock} end. %%%---------------------------------------------------------------- -finalize_start(Host, Port, Options0, F) -> +finalize_start(Host, Port, Profile, Options0, F) -> Options = ?PUT_INTERNAL_OPT([{address, Host}, {port, Port}, {role, server}], Options0), - Profile = ?GET_OPT(profile, Options), case ssh_system_sup:system_supervisor(Host, Port, Profile) of undefined -> try sshd_sup:start_child(Options) of @@ -449,5 +475,6 @@ finalize_start(Host, Port, Options0, F) -> end. %%%---------------------------------------------------------------- +fmt_host(any) -> any; fmt_host(IP) when is_tuple(IP) -> inet:ntoa(IP); fmt_host(Str) when is_list(Str) -> Str. -- cgit v1.2.3