summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHans Nilsson <hans@erlang.org>2019-08-13 11:57:42 +0200
committerHans Nilsson <hans@erlang.org>2019-09-20 10:31:47 +0200
commit0bb391db6e31d5c2672a865cfae4b7051e1cc88c (patch)
tree42bb3fff9ab53ade0b3ef8761cf126cad215b8a4
parenta5f6b3441e26919a2be67929487c07a388ae96f6 (diff)
downloaderlang-0bb391db6e31d5c2672a865cfae4b7051e1cc88c.tar.gz
ssh: tcpip-forward, server part
Added handling of SSH_MSG_GLOBAL_REQUEST(tcpip-forward) and a new boolean option tcpip_tunnel_out with default false.
-rw-r--r--lib/ssh/doc/src/ssh.xml9
-rw-r--r--lib/ssh/src/Makefile3
-rw-r--r--lib/ssh/src/ssh.app.src3
-rw-r--r--lib/ssh/src/ssh.hrl3
-rw-r--r--lib/ssh/src/ssh_connection.erl72
-rw-r--r--lib/ssh/src/ssh_connection_handler.erl11
-rw-r--r--lib/ssh/src/ssh_options.erl6
-rw-r--r--lib/ssh/src/ssh_subsystem_sup.erl21
-rw-r--r--lib/ssh/src/ssh_tcpip_forward_acceptor.erl113
-rw-r--r--lib/ssh/src/ssh_tcpip_forward_acceptor_sup.erl62
-rw-r--r--lib/ssh/src/ssh_tcpip_forward_srv.erl55
-rw-r--r--lib/ssh/test/ssh_sup_SUITE.erl9
12 files changed, 357 insertions, 10 deletions
diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml
index afcae71418..7f5be7b17d 100644
--- a/lib/ssh/doc/src/ssh.xml
+++ b/lib/ssh/doc/src/ssh.xml
@@ -693,6 +693,15 @@
</desc>
</datatype>
+ <datatype>
+ <name name="tcpip_tunnel_out_daemon_option"/>
+ <desc>
+ <p>Enables (<c>true</c>) or disables (<c>false</c>) the possibility to tunnel a TCP/IP connection out of a
+ <seealso marker="ssh:ssh#daemon-2">server</seealso>.
+ Disabled per default.
+ </p>
+ </desc>
+ </datatype>
<!--................................................................-->
<datatype_title>Options common to clients and daemons</datatype_title>
diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile
index 46bf6332e5..f1b11bc5b9 100644
--- a/lib/ssh/src/Makefile
+++ b/lib/ssh/src/Makefile
@@ -74,6 +74,9 @@ MODULES= \
ssh_subsystem_sup \
ssh_sup \
ssh_system_sup \
+ ssh_tcpip_forward_srv \
+ ssh_tcpip_forward_acceptor_sup \
+ ssh_tcpip_forward_acceptor \
ssh_transport \
ssh_xfer \
sshc_sup \
diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src
index 379cde23e3..0c70399d3f 100644
--- a/lib/ssh/src/ssh.app.src
+++ b/lib/ssh/src/ssh.app.src
@@ -36,6 +36,9 @@
ssh_sftpd_file_api,
ssh_subsystem_sup,
ssh_sup,
+ ssh_tcpip_forward_srv,
+ ssh_tcpip_forward_acceptor_sup,
+ ssh_tcpip_forward_acceptor,
ssh_system_sup,
ssh_transport,
ssh_xfer]},
diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl
index a991f72cf2..301e0d91e5 100644
--- a/lib/ssh/src/ssh.hrl
+++ b/lib/ssh/src/ssh.hrl
@@ -302,6 +302,7 @@
| shell_daemon_option()
| exec_daemon_option()
| ssh_cli_daemon_option()
+ | tcpip_tunnel_out_daemon_option()
| authentication_daemon_options()
| diffie_hellman_group_exchange_daemon_option()
| negotiation_timeout_daemon_option()
@@ -329,6 +330,8 @@
-type ssh_cli_daemon_option() :: {ssh_cli, mod_args() | no_cli }.
+-type tcpip_tunnel_out_daemon_option() :: {tcpip_tunnel_out, boolean()} .
+
-type send_ext_info_daemon_option() :: {send_ext_info, boolean()} .
-type authentication_daemon_options() ::
diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl
index bf9e178cce..0caf5cd819 100644
--- a/lib/ssh/src/ssh_connection.erl
+++ b/lib/ssh/src/ssh_connection.erl
@@ -45,6 +45,8 @@
handle_msg/3,
handle_stop/1,
+ open_channel/4,
+
channel_adjust_window_msg/2,
channel_close_msg/1,
channel_open_failure_msg/4,
@@ -57,6 +59,7 @@
channel_request_msg/4,
channel_success_msg/1,
+ request_global_msg/3,
request_failure_msg/0,
request_success_msg/1,
@@ -202,10 +205,22 @@ session_channel(ConnectionHandler, Timeout) ->
Result :: {ok, ssh:channel_id()} | {error, reason()} .
session_channel(ConnectionHandler, InitialWindowSize, MaxPacketSize, Timeout) ->
- case ssh_connection_handler:open_channel(ConnectionHandler, "session", <<>>,
- InitialWindowSize,
- MaxPacketSize, Timeout) of
- {open, Channel} ->
+ open_channel(ConnectionHandler, "session", <<>>,
+ InitialWindowSize,
+ MaxPacketSize,
+ Timeout).
+
+%%--------------------------------------------------------------------
+%% Description: Opens a channel for the given type.
+%% --------------------------------------------------------------------
+open_channel(ConnectionHandler, Type, ChanData, Timeout) ->
+ open_channel(ConnectionHandler, Type, ChanData, ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, Timeout).
+
+open_channel(ConnectionHandler, Type, ChanData, InitialWindowSize, MaxPacketSize, Timeout) ->
+ case ssh_connection_handler:open_channel(ConnectionHandler, Type, ChanData,
+ InitialWindowSize, MaxPacketSize,
+ Timeout) of
+ {open, Channel} ->
{ok, Channel};
Error ->
Error
@@ -740,9 +755,45 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
{[], Connection}
end;
+handle_msg(#ssh_msg_global_request{name = <<"tcpip-forward">>,
+ want_reply = WantReply,
+ data = <<?DEC_BIN(ListenAddrStr,_Len),?UINT32(ListenPort)>>},
+ #connection{options = Opts} = Connection, server) ->
+ case ?GET_OPT(tcpip_tunnel_out, Opts) of
+ false ->
+ %% This daemon instance has not enabled tcpip_forwarding
+ {[{connection_reply, request_failure_msg()}], Connection};
+
+ true ->
+ Sups = ?GET_INTERNAL_OPT(supervisors, Opts),
+ SubSysSup = proplists:get_value(subsystem_sup, Sups),
+ FwdSup = ssh_subsystem_sup:tcpip_fwd_supervisor(SubSysSup),
+ ConnPid = self(),
+ case ssh_tcpip_forward_acceptor:supervised_start(FwdSup,
+ {ListenAddrStr, ListenPort},
+ undefined,
+ "forwarded-tcpip", ssh_tcpip_forward_srv,
+ ConnPid) of
+ {ok,ListenPort} when WantReply==true ->
+ {[{connection_reply, request_success_msg(<<>>)}], Connection};
+
+ {ok,LPort} when WantReply==true ->
+ {[{connection_reply, request_success_msg(<<?UINT32(LPort)>>)}], Connection};
+
+ {error,_} when WantReply==true ->
+ {[{connection_reply, request_failure_msg()}], Connection};
+
+ _ when WantReply==true ->
+ {[{connection_reply, request_failure_msg()}], Connection};
+
+ _ ->
+ {[], Connection}
+ end
+ end;
+
handle_msg(#ssh_msg_global_request{name = _Type,
want_reply = WantReply,
- data = _Data}, Connection, _) ->
+ data = _Data}, Connection, _Role) ->
if WantReply == true ->
FailMsg = request_failure_msg(),
{[{connection_reply, FailMsg}], Connection};
@@ -855,8 +906,13 @@ channel_success_msg(ChannelId) ->
%%%----------------------------------------------------------------
%%% request_*_msg(...)
-%%% Returns a #ssh_msg_....{} for request responses.
+%%% Returns a #ssh_msg_....{}
%%%
+request_global_msg(Name, WantReply, Data) ->
+ #ssh_msg_global_request{name = Name,
+ want_reply = WantReply,
+ data = Data}.
+
request_failure_msg() ->
#ssh_msg_request_failure{}.
@@ -1301,6 +1357,10 @@ handle_cli_msg(C0, ChId, Reply0) ->
%%%----------------------------------------------------------------
%%%
+%%% TCP/IP forwarding
+
+%%%----------------------------------------------------------------
+%%%
%%% Request response handling on return to the calling ssh_connection_handler
%%% state machine.
%%%
diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl
index 991348b156..ae59e8b610 100644
--- a/lib/ssh/src/ssh_connection_handler.erl
+++ b/lib/ssh/src/ssh_connection_handler.erl
@@ -1544,6 +1544,17 @@ handle_event(info, {'EXIT', _Sup, Reason}, StateName, _) ->
handle_event(info, check_cache, _, D) ->
{keep_state, D, cond_set_idle_timer(D)};
+handle_event(info, {fwd_connect_received, Sock, ChId, ChanCB}, StateName, #data{connection_state = Connection}) ->
+ #connection{options = Options,
+ channel_cache = Cache,
+ sub_system_supervisor = SubSysSup} = Connection,
+ Channel = ssh_client_channel:cache_lookup(Cache, ChId),
+ {ok,Pid} = ssh_subsystem_sup:start_channel(role(StateName), SubSysSup, self(), ChanCB, ChId, [Sock], undefined, Options),
+ ssh_client_channel:cache_update(Cache, Channel#channel{user=Pid}),
+ gen_tcp:controlling_process(Sock, Pid),
+ inet:setopts(Sock, [{active,once}]),
+ keep_state_and_data;
+
handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) ->
case unexpected_fun(UnexpectedMessage, D) of
report ->
diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl
index 39f23a8b8a..7a2bb95298 100644
--- a/lib/ssh/src/ssh_options.erl
+++ b/lib/ssh/src/ssh_options.erl
@@ -329,6 +329,12 @@ default(server) ->
class => user_option
},
+ tcpip_tunnel_out =>
+ #{default => false,
+ chk => fun erlang:is_boolean/1,
+ class => user_option
+ },
+
system_dir =>
#{default => "/etc/ssh",
chk => fun(V) -> check_string(V) andalso check_dir(V) end,
diff --git a/lib/ssh/src/ssh_subsystem_sup.erl b/lib/ssh/src/ssh_subsystem_sup.erl
index db34735e1b..140b219b32 100644
--- a/lib/ssh/src/ssh_subsystem_sup.erl
+++ b/lib/ssh/src/ssh_subsystem_sup.erl
@@ -31,6 +31,7 @@
-export([start_link/5,
connection_supervisor/1,
channel_supervisor/1,
+ tcpip_fwd_supervisor/1,
start_channel/8
]).
@@ -51,6 +52,10 @@ channel_supervisor(SupPid) when is_pid(SupPid) ->
Children = supervisor:which_children(SupPid),
ssh_channel_sup(Children).
+tcpip_fwd_supervisor(SupPid) when is_pid(SupPid) ->
+ Children = supervisor:which_children(SupPid),
+ tcpip_fwd_sup(Children).
+
start_channel(Role, SupPid, ConnRef, Callback, Id, Args, Exec, Opts) ->
ChannelSup = channel_supervisor(SupPid),
ssh_channel_sup:start_child(Role, ChannelSup, ConnRef, Callback, Id, Args, Exec, Opts).
@@ -71,7 +76,8 @@ init([Role, Address, Port, Profile, Options]) ->
%%%=========================================================================
child_specs(Role, Address, Port, Profile, Options) ->
[ssh_channel_child_spec(Role, Address, Port, Profile, Options),
- ssh_connection_child_spec(Role, Address, Port, Profile, Options)].
+ ssh_connection_child_spec(Role, Address, Port, Profile, Options),
+ ssh_tcpip_forward_acceptor_child_spec()].
ssh_connection_child_spec(Role, Address, Port, _Profile, Options) ->
#{id => id(Role, ssh_connection_sup, Address, Port),
@@ -87,6 +93,14 @@ ssh_channel_child_spec(Role, Address, Port, _Profile, Options) ->
type => supervisor
}.
+ssh_tcpip_forward_acceptor_child_spec() ->
+ #{id => make_ref(),
+ start => {ssh_tcpip_forward_acceptor_sup, start_link, []},
+ restart => temporary,
+ type => supervisor
+ }.
+
+
id(Role, Sup, Address, Port) ->
{Role, Sup, Address, Port}.
@@ -100,5 +114,8 @@ ssh_channel_sup([{_, Child, _, [ssh_channel_sup]} | _]) ->
ssh_channel_sup([_ | Rest]) ->
ssh_channel_sup(Rest).
-
+tcpip_fwd_sup([{_, Child, _, [ssh_tcpip_forward_acceptor_sup]} | _]) ->
+ Child;
+tcpip_fwd_sup([_ | Rest]) ->
+ tcpip_fwd_sup(Rest).
diff --git a/lib/ssh/src/ssh_tcpip_forward_acceptor.erl b/lib/ssh/src/ssh_tcpip_forward_acceptor.erl
new file mode 100644
index 0000000000..60a0a7a0e1
--- /dev/null
+++ b/lib/ssh/src/ssh_tcpip_forward_acceptor.erl
@@ -0,0 +1,113 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2018. 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 supervisor for tcpip-forwarding acceptor
+%%----------------------------------------------------------------------
+
+-module(ssh_tcpip_forward_acceptor).
+
+-export([supervised_start/6,
+ start_link/6]).
+
+-include("ssh.hrl").
+
+%%%----------------------------------------------------------------
+supervised_start(FwdSup, {ListenAddrStr, ListenPort}, ConnectToAddr, ChanType, ChanCB, ConnPid) ->
+ case get_fwd_listen_opts(ListenAddrStr) of
+ {ok,Opts} ->
+ %% start listening on Addr:BoundPort
+ case gen_tcp:listen(ListenPort, [binary,
+ {reuseaddr,true},
+ {active,false} | Opts]) of
+ {ok,LSock} ->
+ {ok,{_, TrueListenPort}} = inet:sockname(LSock),
+ ssh_tcpip_forward_acceptor_sup:start_child(FwdSup,
+ LSock,
+ {ListenAddrStr,TrueListenPort},
+ ConnectToAddr,
+ ChanType,
+ ChanCB,
+ ConnPid),
+ {ok, TrueListenPort};
+
+ {error,Error} ->
+ {error,Error}
+ end;
+
+ {error,Error} ->
+ {error,Error}
+ end.
+
+
+%%%----------------------------------------------------------------
+start_link(LSock, {ListenAddrStr,ListenPort}, ConnectToAddr, ChanType, ChanCB, ConnPid) ->
+ Pid = proc_lib:spawn_link(
+ fun() ->
+ acceptor_loop(LSock, ListenAddrStr, ListenPort, ConnectToAddr, ChanType, ChanCB, ConnPid)
+ end),
+ {ok, Pid}.
+
+%%%================================================================
+%%%
+%%% Internal
+%%%
+acceptor_loop(LSock, ListenAddrStr, ListenPort, ConnectToAddr, ChanType, ChanCB, ConnPid) ->
+ case gen_tcp:accept(LSock) of
+ {ok, Sock} ->
+ {ok, {RemHost,RemPort}} = inet:peername(Sock),
+ RemHostBin = list_to_binary(encode_ip(RemHost)),
+ Data =
+ case ConnectToAddr of
+ undefined ->
+ <<?STRING(ListenAddrStr), ?UINT32(ListenPort),
+ ?STRING(RemHostBin), ?UINT32(RemPort)>>
+ end,
+ case ssh_connection:open_channel(ConnPid, ChanType, Data, infinity) of
+ {ok,ChId} ->
+ gen_tcp:controlling_process(Sock, ConnPid),
+ ConnPid ! {fwd_connect_received, Sock, ChId, ChanCB};
+ _ ->
+ gen_tcp:close(Sock)
+ end,
+ acceptor_loop(LSock, ListenAddrStr, ListenPort, ConnectToAddr, ChanType, ChanCB, ConnPid);
+
+ {error,closed} ->
+ ok
+ end.
+
+%%%----------------------------------------------------------------
+get_fwd_listen_opts(<<"">> ) -> {ok, []};
+get_fwd_listen_opts(<<"0.0.0.0">> ) -> {ok, [inet]};
+get_fwd_listen_opts(<<"::">> ) -> {ok, [inet6]};
+get_fwd_listen_opts(<<"localhost">>) -> {ok, [{ip,loopback}]};
+get_fwd_listen_opts(AddrStr) ->
+ case inet:getaddr(binary_to_list(AddrStr), inet) of
+ {ok, Addr} -> {ok, [{ip,Addr}]};
+ {error,Error} -> {error,Error}
+ end.
+
+%%%----------------------------------------------------------------
+encode_ip(Addr) when is_tuple(Addr) ->
+ case catch inet_parse:ntoa(Addr) of
+ {'EXIT',_} -> false;
+ A -> A
+ end.
diff --git a/lib/ssh/src/ssh_tcpip_forward_acceptor_sup.erl b/lib/ssh/src/ssh_tcpip_forward_acceptor_sup.erl
new file mode 100644
index 0000000000..522ce650ff
--- /dev/null
+++ b/lib/ssh/src/ssh_tcpip_forward_acceptor_sup.erl
@@ -0,0 +1,62 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2018. 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 supervisor for tcpip-forwarding acceptor
+%%----------------------------------------------------------------------
+
+-module(ssh_tcpip_forward_acceptor_sup).
+-behaviour(supervisor).
+
+-include("ssh.hrl").
+
+-export([start_link/0, start_child/7]).
+
+%% Supervisor callback
+-export([init/1]).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+start_link() ->
+ supervisor:start_link(?MODULE, []).
+
+start_child(Sup, LSock, ListenAddr, ConnectToAddr, ChanType, ChanCB, ConnPid) ->
+ Args = [LSock, ListenAddr, ConnectToAddr, ChanType, ChanCB, ConnPid],
+ supervisor:start_child(Sup, Args).
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+init([]) ->
+ SupFlags = #{strategy => simple_one_for_one,
+ intensity => 10,
+ period => 3600
+ },
+ ChildSpecs = [#{id => undefined, % As simple_one_for_one is used.
+ start => {ssh_tcpip_forward_acceptor, start_link, []}
+ }
+ ],
+ {ok, {SupFlags,ChildSpecs}}.
+
+%%%=========================================================================
+%%% Internal functions
+%%%=========================================================================
diff --git a/lib/ssh/src/ssh_tcpip_forward_srv.erl b/lib/ssh/src/ssh_tcpip_forward_srv.erl
new file mode 100644
index 0000000000..0ce8e30732
--- /dev/null
+++ b/lib/ssh/src/ssh_tcpip_forward_srv.erl
@@ -0,0 +1,55 @@
+-module(ssh_tcpip_forward_srv).
+
+-behaviour(ssh_server_channel).
+
+-record(state, {
+ id, cm,
+ fwd_socket
+ }).
+
+-export([init/1, handle_msg/2, handle_ssh_msg/2, terminate/2]).
+
+init([FwdSocket]) ->
+ {ok, #state{fwd_socket=FwdSocket}}.
+
+
+handle_msg({ssh_channel_up, ChannelId, ConnectionManager}, State) ->
+ {ok, State#state{id = ChannelId,
+ cm = ConnectionManager}};
+
+handle_msg({tcp,Sock,Data}, #state{fwd_socket = Sock,
+ cm = CM,
+ id = ChId} = State) ->
+ ssh_connection:send(CM, ChId, Data),
+ inet:setopts(Sock, [{active,once}]),
+ {ok, State};
+
+handle_msg({tcp_closed,Sock}, #state{fwd_socket = Sock,
+ cm = CM,
+ id = ChId} = State) ->
+ ssh_connection:send_eof(CM, ChId),
+ {stop, ChId, State#state{fwd_socket=undefined}}.
+
+
+
+handle_ssh_msg({ssh_cm, _CM, {data, _ChannelId, _Type, Data}}, #state{fwd_socket=Sock} = State) ->
+ gen_tcp:send(Sock, Data),
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _CM, {eof, ChId}}, State) ->
+ {stop, ChId, State};
+
+handle_ssh_msg({ssh_cm, _CM, {signal, _, _}}, State) ->
+ %% Ignore signals according to RFC 4254 section 6.9.
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _CM, {exit_signal, ChId, _, _Error, _}}, State) ->
+ {stop, ChId, State};
+
+handle_ssh_msg({ssh_cm, _, {exit_status, ChId, _Status}}, State) ->
+ {stop, ChId, State}.
+
+
+terminate(_Reason, #state{fwd_socket=Sock}) ->
+ gen_tcp:close(Sock),
+ ok.
diff --git a/lib/ssh/test/ssh_sup_SUITE.erl b/lib/ssh/test/ssh_sup_SUITE.erl
index 0bf4d87f0a..554fb63ad8 100644
--- a/lib/ssh/test/ssh_sup_SUITE.erl
+++ b/lib/ssh/test/ssh_sup_SUITE.erl
@@ -342,7 +342,9 @@ chk_empty_con_daemon(Daemon) ->
{{ssh_acceptor_sup,_,_,_}, AccSup, supervisor,[ssh_acceptor_sup]}],
supervisor:which_children(Daemon),
[SubSysSup,AccSup]),
- ?wait_match([{{server,ssh_connection_sup, _,_},
+ ?wait_match([{_,_, supervisor,
+ [ssh_tcpip_forward_acceptor_sup]},
+ {{server,ssh_connection_sup, _,_},
ConnectionSup, supervisor,
[ssh_connection_sup]},
{{server,ssh_channel_sup,_ ,_},
@@ -375,7 +377,10 @@ check_sshd_system_tree(Daemon, Config) ->
supervisor:which_children(Daemon),
[SubSysSup,AccSup]),
- ?wait_match([{{server,ssh_connection_sup, _,_},
+ ?wait_match([{_,
+ _AcceptorSup, supervisor,
+ [ssh_tcpip_forward_acceptor_sup]},
+ {{server,ssh_connection_sup, _,_},
ConnectionSup, supervisor,
[ssh_connection_sup]},
{{server,ssh_channel_sup,_ ,_},