Search code examples
c#winformssshssh.net

SSH Commands Take Long to return Output using C# WinForms and Renci.SSH


I am using the Renci.SSH library in a C# WinForms application, to remotely access a Linux server to do two main things:

  1. Send simple commands and output the result in a text box (e.g. ls /foldername)
  2. Send sets of commands to programmatically manipulate files (add, remove, delete, move, copy) on the server.

I've pretty much gotten #1 done, however sometimes the server will respond instantly, and other times it takes up to 40 seconds, however if I use a tool like Putty or it's command line version plink, it's instantaneous. I'm trying to figure out what causes this large variance in the delay time, and if there is any way to shorten the response time (like if I was using Putty).

Side note: I am able to string multiple commands with "&&" but the response time still highly varies, even for running the same command or set of commands.

This is the function I wrote to do simple commands, like listing the directory, or changing directory:

public string SshToServer(string commands, string _ip, string username, string password)
{
    string result = "";
    SshCommand commandString;

    using (var client = new SshClient(_ip, username, password))
    {
        try
        {
            client.ConnectionInfo.Timeout = TimeSpan.FromSeconds(60); //This determines how long the server stays active
            client.KeepAliveInterval = TimeSpan.FromSeconds(30); //This determines how long individual http requests can be sent over a single connection
            client.ConnectionInfo.RetryAttempts = 15;
            client.ConnectionInfo.MaxSessions = 24;

            client.Connect();
            if (client.IsConnected)
            {
                commandString = client.CreateCommand(commands);
                commandString.Execute();
                result = commandString.Result.ToString();
            }
            else
            {
                result = "Unable to Connect";
            }
            
            //client.Disconnect();

            return result;
        }
        catch (Exception ex)
        {
            result = ex.Message.ToString();
            return result;
        }
    }
}

I dabbled with the SshStream and SshStreamNoTerminal, but didn't have any luck with those. The above seemed to work best thus far. I'm hoping once I figure this out, then I can work on the file transfer aspect of the program (#2 on my list above).

I appreciate any insight someone more versed in this can provide me!

Edit:

Waiting for the result back from the server is what takes long (sometimes) I did test it in bare-bones console app and it's the exact same, so I don't believe it's a WinForms issue. Below is my full exact stripped-down code that reproduces the issue:

using Renci.SshNet;
using System.IO;
using System.Threading;

namespace TestSSH
{
    internal class Program
    {
        public static string SshToTwo47(string commands, string _ip, string username = "iadmin", string password = "password")
        {
            string result = "";
            SshCommand commandString;

            using (var client = new SshClient(_ip, username, password))
            {
                try
                {
                    client.ConnectionInfo.Timeout = TimeSpan.FromSeconds(60); //This determines how long the server stays active
                    client.KeepAliveInterval = TimeSpan.FromSeconds(30); //This determines how long individual http requests can be sent over a single connection
                    client.ConnectionInfo.RetryAttempts = 15;
                    client.ConnectionInfo.MaxSessions = 24;

                    client.Connect();
                    if (client.IsConnected)
                    {
                        commandString = client.CreateCommand(commands);
                        commandString.Execute();
                        result = commandString.Result.ToString();
                    }
                    else
                    {
                        result = "Unable to Connect";
                    }

                    //client.Disconnect();

                    return result;
                }
                catch (Exception ex)
                {
                    result = ex.Message.ToString();
                    return result;
                }
            }
        }

        static string IP = "192.168.3.23";
        static string response = "";
        static void Main(string[] args)
        {
            for(;;)
            {
                string t47Cmd = Console.ReadLine();
                Console.WriteLine("Sending Command...");

                if (!string.IsNullOrWhiteSpace(t47Cmd))
                {
                    response = SshToTwo47(t47Cmd, IP);
                }
                Console.WriteLine(response);
            } 
        }
    }
}

The server I'm communicating with is a physical device, and not a virtual server - I doubt that should matter too much, but you never know.

Edit:

The device I'm connecting to as the server actually produces its own security certificates; I just found that out from a co-worker. I wonder if it's taking long because of something with respect to that... I'm not very familiar with how the key/certificate process works for SSH.

Edit:

I think I've answered my own question after thinking through my code a bit more. Utilizing the "using" statement to instantiate the SshClient() will always disconnect me afterward, so each successive attempt would need to reconnect me to the server, which sometimes took up to 40 seconds. I've split the function into a "connect" and a "send commands" set of functions, and removed the using statements to control when I disconnect from the server, and it's fully responsive now.

Thanks for the brainstorming!


Solution

  • I think I've answered my own question after thinking through my code a bit more. Utilizing the "using" statement to instantiate the SshClient() will always disconnect afterward, so each successive attempt would need to reconnect to the server, which sometimes took up to 40 seconds. I've split the function into a "connect" and a "send commands" set of functions, and removed the using statements to control when I disconnect from the server, and it's fully responsive now. I also had to instantiate the SshClient variable globally with the username and password, with variables for those preceding it.

    Thanks for the brainstorming!

    Below are the two edited functions which allowed me to achieve no delay in the response from the server:

    using Renci.SshNet;
    using System.IO;
    using System.Net;
    using System.Threading;
    
    namespace TestSSH
    {
        internal class Program
        {
            public static string ConnectSshToServer(string _ip, string username = "admin", string password = "password")
            {
                string result = "";
                SshCommand commandString;
    
                try
                {
                    client.ConnectionInfo.Timeout = TimeSpan.FromSeconds(60); //This determines how long the server stays active
                    client.KeepAliveInterval = TimeSpan.FromSeconds(30); //This determines how long individual http requests can be sent over a single connection
                    client.ConnectionInfo.RetryAttempts = 15;
                    client.ConnectionInfo.MaxSessions = 24;
            
                    client.Connect();
                    if (client.IsConnected)
                    {
                        Console.WriteLine("Connected Successfully:");
                    }
                    else
                    {
                        result = "Unable to Connect";
                    }
            
                    return result;
                }
                catch (Exception ex)
                {
                    result = ex.Message.ToString();
                    return result;
                }
            }
    
            public static string SshToServer(string commands, string _ip, string username = "admin", string password = "password")
            {
                string result = "";
                SshCommand commandString;
    
                try
                {
                    if (client.IsConnected)
                    {
                        commandString = client.CreateCommand(commands);
                        commandString.Execute();
                        result = commandString.Result.ToString();
                    }
                    else
                    {
                        result = "Unable to Send Command...\n";
                    }
            
                    return result;
                }
                catch (Exception ex)
                {
                    result = ex.Message.ToString();
                    return result;
                }
            }
        
            static string IP = "192.168.3.23";
            static string username = "admin";
            static string password = "password";
            static string response = "";
            static SshClient client = new SshClient(IP, username, password);
    
            static void Main(string[] args)
            {
                string result = ConnectSshToServer(IP);
    
                if (result != "Unable to Connect")
                {
                    for (;;)
                    {
                        Console.WriteLine(">");
                        string t47Cmd = Console.ReadLine();
                        Console.WriteLine("Sending Command...");
            
                        if (!string.IsNullOrWhiteSpace(t47Cmd))
                        {
                            response = SshToServer(t47Cmd, IP);
                        }
                        Console.WriteLine(response);
                    }
                
                }
            }
        }
    }