From f5495aa1560daf65c063a8d5b99a5ea17520ea92 Mon Sep 17 00:00:00 2001 From: Hans Nilsson Date: Mon, 30 Sep 2013 16:35:09 +0200 Subject: ssh: Add ssh:sockname/1 incl doc and tests OTP-11345, sto575, tsk374 --- lib/ssh/doc/src/ssh.xml | 14 +++++++ lib/ssh/src/ssh.erl | 12 ++++++ lib/ssh/src/ssh_connection_handler.erl | 29 +++++++------- lib/ssh/test/Makefile | 5 ++- lib/ssh/test/ssh_basic_SUITE.erl | 55 +++++++++++++++++--------- lib/ssh/test/ssh_peername_sockname_server.erl | 56 +++++++++++++++++++++++++++ 6 files changed, 137 insertions(+), 34 deletions(-) create mode 100644 lib/ssh/test/ssh_peername_sockname_server.erl (limited to 'lib/ssh') diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml index ddfb50ebd2..896b98edc2 100644 --- a/lib/ssh/doc/src/ssh.xml +++ b/lib/ssh/doc/src/ssh.xml @@ -366,6 +366,20 @@ + + sockname(ConnectionRef) -> {ok, {Address,Port}} | {error,Error} + + + ConnectionRef = ssh_connection_ref() + Address = ip_address() + Port = integer() + + +

Returns the local address and port number for a connection. +

+
+
+ start() -> start(Type) -> ok | {error, Reason} diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 718321ef21..80d20abbbd 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -29,6 +29,7 @@ channel_info/3, daemon/1, daemon/2, daemon/3, peername/1, + sockname/1, stop_listener/1, stop_listener/2, stop_daemon/1, stop_daemon/2, shell/1, shell/2, shell/3]). @@ -256,6 +257,17 @@ peername(ConnectionRef) -> ssh_connection_manager:connection_info(ConnectionRef, [peer]), {ok, {IP,Port}}. +%%-------------------------------------------------------------------- +%% Function: sockname(ConnectionRef) -> {ok, {Host,Port}} +%% | {error,Error} +%% +%% Description: Returns the local address of the connection +%%-------------------------------------------------------------------- +sockname(ConnectionRef) -> + [{sockname, Result}] = + ssh_connection_manager:connection_info(ConnectionRef, [sockname]), + Result. + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 9de4dd5967..c3e8a3c742 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -46,7 +46,7 @@ handle_sync_event/4, handle_info/3, terminate/3, code_change/4]). %% spawn export --export([ssh_info_handler/3]). +-export([ssh_info_handler/4]). -record(state, { transport_protocol, % ex: tcp @@ -533,7 +533,7 @@ handle_event(renegotiate, StateName, State) -> {next_state, StateName, State}; handle_event({info, From, Options}, StateName, #state{ssh_params = Ssh} = State) -> - spawn(?MODULE, ssh_info_handler, [Options, Ssh, From]), + spawn(?MODULE, ssh_info_handler, [Options, Ssh, State, From]), {next_state, StateName, State}; handle_event(data_size, connected, #state{ssh_params = Ssh0} = State) -> {ok, [{send_oct,Sent}]} = inet:getstat(State#state.socket, [send_oct]), @@ -1022,26 +1022,29 @@ retry_fun(User, Reason, Opts) -> catch Fun(User, Reason) end. -ssh_info_handler(Options, Ssh, From) -> - Info = ssh_info(Options, Ssh, []), +ssh_info_handler(Options, Ssh, State, From) -> + Info = ssh_info(Options, Ssh, State, []), ssh_connection_manager:send_msg({channel_requst_reply, From, Info}). -ssh_info([], _, Acc) -> +ssh_info([], _, _, Acc) -> Acc; ssh_info([client_version | Rest], #ssh{c_vsn = IntVsn, - c_version = StringVsn} = SshParams, Acc) -> - ssh_info(Rest, SshParams, [{client_version, {IntVsn, StringVsn}} | Acc]); + c_version = StringVsn} = SshParams, State, Acc) -> + ssh_info(Rest, SshParams, State, [{client_version, {IntVsn, StringVsn}} | Acc]); ssh_info([server_version | Rest], #ssh{s_vsn = IntVsn, - s_version = StringVsn} = SshParams, Acc) -> - ssh_info(Rest, SshParams, [{server_version, {IntVsn, StringVsn}} | Acc]); + s_version = StringVsn} = SshParams, State, Acc) -> + ssh_info(Rest, SshParams, State, [{server_version, {IntVsn, StringVsn}} | Acc]); -ssh_info([peer | Rest], #ssh{peer = Peer} = SshParams, Acc) -> - ssh_info(Rest, SshParams, [{peer, Peer} | Acc]); +ssh_info([peer | Rest], #ssh{peer = Peer} = SshParams, State, Acc) -> + ssh_info(Rest, SshParams, State, [{peer, Peer} | Acc]); -ssh_info([ _ | Rest], SshParams, Acc) -> - ssh_info(Rest, SshParams, Acc). +ssh_info([sockname | Rest], SshParams, #state{socket=Socket}=State, Acc) -> + ssh_info(Rest, SshParams, State, [{sockname,inet:sockname(Socket)}|Acc]); + +ssh_info([ _ | Rest], SshParams, State, Acc) -> + ssh_info(Rest, SshParams, State, Acc). log_error(Reason) -> Report = io_lib:format("Erlang ssh connection handler failed with reason: " diff --git a/lib/ssh/test/Makefile b/lib/ssh/test/Makefile index f5db31baee..13caafc055 100644 --- a/lib/ssh/test/Makefile +++ b/lib/ssh/test/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 2004-2012. All Rights Reserved. +# Copyright Ericsson AB 2004-2013. All Rights Reserved. # # The contents of this file are subject to the Erlang Public License, # Version 1.1, (the "License"); you may not use this file except in @@ -38,7 +38,8 @@ MODULES= \ ssh_sftpd_SUITE \ ssh_sftpd_erlclient_SUITE \ ssh_connection_SUITE \ - ssh_echo_server + ssh_echo_server \ + ssh_peername_sockname_server HRL_FILES_NEEDED_IN_TEST= \ $(ERL_TOP)/lib/ssh/src/ssh.hrl \ diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl index e13610bc2a..e8f1d5213c 100644 --- a/lib/ssh/test/ssh_basic_SUITE.erl +++ b/lib/ssh/test/ssh_basic_SUITE.erl @@ -49,7 +49,9 @@ all() -> close]. groups() -> - [{dsa_key, [], [send, peername, exec, exec_compressed, shell, known_hosts, idle_time, rekey, openssh_zlib_basic_test]}, + [{dsa_key, [], [send, + peername_sockname, + exec, exec_compressed, shell, known_hosts, idle_time, rekey, openssh_zlib_basic_test]}, {rsa_key, [], [send, exec, exec_compressed, shell, known_hosts, idle_time, rekey, openssh_zlib_basic_test]}, {dsa_pass_key, [], [pass_phrase]}, {rsa_pass_key, [], [pass_phrase]}, @@ -474,35 +476,50 @@ send(Config) when is_list(Config) -> %%-------------------------------------------------------------------- -peername() -> - [{doc, "Test ssh:peername/1"}]. -peername(Config) when is_list(Config) -> +peername_sockname() -> + [{doc, "Test ssh:peername/1 and ssh:sockname/1"}]. +peername_sockname(Config) when is_list(Config) -> process_flag(trap_exit, true), SystemDir = filename:join(?config(priv_dir, Config), system), UserDir = ?config(priv_dir, Config), - {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, + {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, {user_dir, UserDir}, - {failfun, fun ssh_test_lib:failfun/2}]), + {subsystems, [{"peername_sockname", + {ssh_peername_sockname_server, []}} + ]} + ]), ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, {user_dir, UserDir}, {user_interaction, false}]), - {ok,{IP,Port}} = ssh:peername(ConnectionRef), - host_equal(Host,IP), - ssh:stop_daemon(Pid). - + {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity), + success = ssh_connection:subsystem(ConnectionRef, ChannelId, "peername_sockname", infinity), + {ok,{HostPeerClient,PortPeerClient}} = ssh:peername(ConnectionRef), + {ok,{HostSockClient,PortSockClient}} = ssh:sockname(ConnectionRef), + receive + {ssh_cm, ConnectionRef, {data, ChannelId, _, Response}} -> + {PeerNameSrv,SockNameSrv} = binary_to_term(Response), + {ok,{HostPeerSrv,PortPeerSrv}} = PeerNameSrv, + {ok,{HostSockSrv,PortSockSrv}} = SockNameSrv, + host_equal(HostPeerSrv, HostSockClient), + PortPeerSrv = PortSockClient, + host_equal(HostSockSrv, HostPeerClient), + PortSockSrv = PortPeerClient, + host_equal(HostSockSrv, Host), + PortSockSrv = Port + after 10000 -> + throw(timeout) + end. -host_equal(Name, IP) when is_list(Name), is_tuple(IP) -> - Family = if size(IP)==4 -> inet; - size(IP)==8 -> inet6 - end, - {ok,#hostent{h_addr_list=IPs}} = inet:gethostbyname(Name,Family), - lists:any(fun(X) -> X==IP end, IPs); -host_equal(IP, Name) when is_list(Name), is_tuple(IP) -> - host_equal(Name, IP); -host_equal(X,Y) -> X==Y. +host_equal(H1, H2) -> + not ordsets:is_disjoint(ips(H1), ips(H2)). +ips(IP) when is_tuple(IP) -> ordsets:from_list([IP]); +ips(Name) when is_list(Name) -> + {ok,#hostent{h_addr_list=IPs4}} = inet:gethostbyname(Name,inet), + {ok,#hostent{h_addr_list=IPs6}} = inet:gethostbyname(Name,inet6), + ordsets:from_list(IPs4++IPs6). %%-------------------------------------------------------------------- close() -> diff --git a/lib/ssh/test/ssh_peername_sockname_server.erl b/lib/ssh/test/ssh_peername_sockname_server.erl new file mode 100644 index 0000000000..7664f3ee25 --- /dev/null +++ b/lib/ssh/test/ssh_peername_sockname_server.erl @@ -0,0 +1,56 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% + +-module(ssh_peername_sockname_server). + +%% The purpose of this module is to perform tests on the server side of an +%% ssh connection. + + +-behaviour(ssh_daemon_channel). +-record(state, {}). + +-export([init/1, handle_msg/2, handle_ssh_msg/2, terminate/2]). + +init([]) -> + {ok, #state{}}. + +handle_msg({ssh_channel_up, ChannelId, ConnectionManager}, State) -> + ssh_connection:send(ConnectionManager, ChannelId, + term_to_binary( + {catch ssh:peername(ConnectionManager), + catch ssh:sockname(ConnectionManager) + }) + ), + {ok, State}. + +handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, _Error, _}}, + State) -> + {stop, ChannelId, State}; + +handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, _Status}}, State) -> + {stop, ChannelId, State}; + +handle_ssh_msg({ssh_cm, _CM, _}, State) -> + {ok, State}. + +terminate(_Reason, _State) -> + ok. -- cgit v1.2.3