After sending data over SSH, "receive" times out

I have written an Erlang application to send data over SSH. After sending ssh_connection:send, I asynchronously wait for the response by calling receive function. But, the receive function is timing out. I have tried with a higher timeout value of 2 minutes as well. What is wrong with my code? Please help me in debugging the issue.

I have debugged the issue as follows;

  1. I checked the packets exchanged using Wireshark. I see packets in Wireshark for all steps performed by my Erlang application (namely ssh:connect, ssh_connection:session_channel & ssh_connection:send).

  2. I have enabled dbg for relevant modules. I see debug logs in the Erlang shell for the steps ssh:connect, ssh_connection:session_channel. But, not for ssh_connection:send

application: start(ssh).
crypto:start().
ssh:start().

%% Creation of SSH connection
{ok, Client} = ssh:connect("10.0.2.15", 2022, [{user, "admin"}, {password, "admin"}]).

%% Creation of SSH Channel
{ok, Channel} = ssh_connection:session_channel(Client, 30000).

NetconfMessage = <<"<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n<capabilities>\n<capability>urn:ietf:params:netconf:base:1.0</capability>\n</capabilities>\n</hello>\n">>.

%% Sending data over the SSH Channel
case ssh_connection:send(Client, Channel, NetconfMessage) of
	ok ->
        io:format("Netconf message sent successfully ~n"),
		
		%% Asynchronously wait for the response
    	receive
        		{ssh, Channel, Data} ->
                io:format("Received data: ~s~n", [Data])
		after 60000 ->
        		io:format("Timeout receiving data.~n")
		end;
		
	{error, Reason} ->
		io:format("Error sending Netconf message: ~p~n", [Reason])
end.

%% Closing the SSH connection
ssh:close(Client).
2 Likes

XML-over-SSH, I have not seen that one before :slight_smile:

If my reading of the docs is right, you won’t receive {ssh, Channel, Data} messages but rather {ssh_cm, Connection, {data, Channel, DataTypeCode, Data}}. The user’s guide has an example of this:

1> ssh:start().
ok
2> {ok, ConnectionRef} = ssh:connect("tarlop", 22, []).
{ok,<0.57.0>}
3> {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity).
{ok,0}
4> success = ssh_connection:exec(ConnectionRef, ChannelId, "pwd", infinity).
5> flush(). % Get all pending messages. NOTE: ordering may vary!
Shell got {ssh_cm,<0.57.0>,{data,0,0,<<"/home/otptest\n">>}}
Shell got {ssh_cm,<0.57.0>,{eof,0}}
Shell got {ssh_cm,<0.57.0>,{exit_status,0,0}}
Shell got {ssh_cm,<0.57.0>,{closed,0}}
ok
6> ssh:connection_info(ConnectionRef, channels).
{channels,[]}
7>

Let me know if this works :slight_smile:

2 Likes

Thanks @jchrist.

I modified the pattern matching in the receive..end clause as explained in the SSH User guide shared by you. Additionally, I have added a catch-all pattern _ -> to handle unmatched messages. But, I am still facing the same issue. Any other suggestion to debug further?

Modified code is pasted below.

ssh:start().

%% Creation of SSH connection
{ok, ConnectionRef} = ssh:connect("10.0.2.15", 2022, [{user, "admin"}, {password, "admin"}]).

%% Creation of SSH Channel
{ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, 30000).

NetconfMessage = <<"<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n<capabilities>\n<capability>urn:ietf:params:netconf:base:1.0</capability>\n</capabilities>\n</hello>\n">>.

%% Sending data over the SSH Channel
case ssh_connection:send(ConnectionRef, ChannelId, NetconfMessage) of
	ok ->
		io:format("Netconf message sent successfully ~n"),
		
		%% Asynchronously wait for the response
		receive
			{ssh_cm, ConnectionRef, {data, ChannelId, Type, Result}} when Type == 0 ->
		        {ok,Result};
			{ssh_cm, ConnectionRef, {data, ChannelId, Type, Result}} when Type == 1 ->
		        {error,Result};
			_ ->
				io:format("Unexpected message received~n")
		        
		after 30000 ->
			io:format("Timeout receiving data.~n")
		end;
		
	{error, Reason} ->
		io:format("Error sending Netconf message: ~p~n", [Reason])
end.

%% Closing the SSH connection
ssh:close(ConnectionRef).
2 Likes

Yes, XML data over SSH is a popular mode for Network management. It has been popularized and standardized by one protocol called Netconf.

3 Likes

If you want to see crazy, look at JSONx

It is trivial to find a SAX parser in most if not all languages for XML whilst for other serialisation formats such as JSON this seems to rarely an option.

SAX parsers are useful as they allow you to avoid reading the entire document into RAM and only react to the elements you are interested in.

It feels ‘right’ to wire incoming HTTP body messages directly into a SAX parser plus it really works well with Erlang’s pattern matching.

2 Likes

After some testing I think I figured this out.

If you ssh_connection:send a message to a channel and you’re not running in any subsystem on the remote side, the connection won’t do anything about it. In my test, I had the default Erlang SSH server running:

27> {ok, ChannelId4} = ssh_connection:session_channel(Conn2, infinity).                                                                                                  
{ok,0}                                                                                                                                                                   
28> ssh_connection:send(Conn2, ChannelId4, "2 + 2").                                                                                                                     
ok                                                                                                                                                                       
29> ssh_connection:send(Conn2, ChannelId4, "\n").                                                                                                                        
ok                                                                                                                                                                       
30> ssh_connection:send(Conn2, ChannelId4, "\n").
ok
31> flush().
ok

- so like you reported (same thing happened with binaries). But when I start a shell first:

32> ssh_connection:shell(Conn2, ChannelId4).     
ok
33> flush().
Shell got {ssh_cm,<0.208.0>,
                  {data,0,0,<<"Eshell V13.2.2  (abort with ^G)\n">>}}
Shell got {ssh_cm,<0.208.0>,{data,0,0,<<"1> ">>}}
ok
34> flush().
ok
35> flush().
ok
37> ssh_connection:send(Conn2, ChannelId4, <<"2 + 2.\n">>).
ok
38> flush().
Shell got {ssh_cm,<0.208.0>,{data,0,0,<<"2 + 2.\n">>}}
Shell got {ssh_cm,<0.208.0>,{data,0,0,<<"4">>}}
Shell got {ssh_cm,<0.208.0>,{data,0,0,<<"\n">>}}
Shell got {ssh_cm,<0.208.0>,{data,0,0,<<"2> ">>}}
ok

I’m not 100% sure this will work with Netconf, but it’s worth a try. If that doesn’t work, I think you need to implement your own subsystem via ssh_client_channel (here are the docs for implementing a server subsystem).

I’m not entirely sure what’s going inside the SSH connection here… maybe a documentation update would help

2 Likes

Thanks @jchrist. I tried to establish a shell before sending data over the channel. And, I got an error " Error sending Netconf message: Closed. When I debugged it, I found that the channel was closed after creating the shell.

(node1@vm-alarm)1> ssh:start().
ok
(node1@vm-alarm)2> 
(node1@vm-alarm)2> %% Creation of SSH connection
(node1@vm-alarm)2> {ok, ConnectionRef} = ssh:connect("10.0.2.15", 2022, [{user, "admin"}, {password, "admin"}]).
{ok,<0.107.0>}
(node1@vm-alarm)3> 
(node1@vm-alarm)3> %% Creation of SSH Channel
(node1@vm-alarm)3> {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, 30000).
{ok,0}
(node1@vm-alarm)4> 
(node1@vm-alarm)4> ssh:connection_info(ConnectionRef, [channels]).
[{channels,[[{type,"session"},
             {sys,"none"},
             {user,<0.90.0>},
             {flow_control,undefined},
             {local_id,0},
             {recv_window_size,425984},
             {recv_window_pending,0},
             {recv_packet_size,65536},
             {recv_close,false},
             {remote_id,0},
             {send_window_size,655360},
             {send_packet_size,65536},
             {sent_close,false},
             {send_buf,{[],[]}}]]}]
(node1@vm-alarm)5> ssh_connection:shell(ConnectionRef, ChannelId).     
ok
(node1@vm-alarm)6>     
(node1@vm-alarm)6> ssh:connection_info(ConnectionRef, [channels]).     
[{channels,[]}]
(node1@vm-alarm)7>
1 Like

Let me try this.

1 Like