Quantcast
Viewing all articles
Browse latest Browse all 1729

New Post: Need help getting started with this library

Response to Marc Clifton - Part 1

Marc,

I didn't want your post/question/issue to hang out there for too long without someone saying something from the community. I'm far from an expert, so bear with me. You have the following questions in your post:

1. How do I instantiate a shell?

There are probably more ways, but i have found/used 2 of them to make things happen in my code with Ssh.Net, and one of them (the first I used) is with a shell. In the above, you are not using a shell, I think. Here is how the docs say:
public ShellStream CreateShellStream(
    string terminalName,
    uint columns,
    uint rows,
    uint width,
    uint height,
    int bufferSize,
    IDictionary<TerminalModes, uint> terminalModeValues
)
I use this way when I have to enter a command, wait for the response, then do another command based on that response-all in the same session, which it looks like you were trying to do in your post, creating an interactive shell, of sorts(?). My implementation is a bit involved, but it boils down to:

Private ss As Renci.SshNet.ShellStream
Public Property Client As Renci.SshNet.SshClient
Private _shStream As Renci.SshNet.ShellStream
    Public Property ShStream As Renci.SshNet.ShellStream Implements IASASecureComms.ShStream
        Get
            If Me.ss Is Nothing Then
                _shStream = Me.GetShellStream
            End If
            Return _shStream
        End Get
        Set(value As Renci.SshNet.ShellStream)
            _shStream = value
        End Set
    End Property

    Public Function GetShellStream() As Renci.SshNet.ShellStream
        Try
            If Me.Client Is Nothing Then
                log.Error("GetShellStream() given an uninstantiated SshClient!")
                Return Nothing
            End If
            '
            ' are we at least connected?
            '
            If Not Me.Client.IsConnected Then
                log.Error("GetShellStream() trying to connect client...")
                Me.Client.Connect()
                log.Error("GetShellStream() could we connect? " & Client.IsConnected)
            End If
            '
            ' only create ShellStream if connected
            '
            If Me.Client.IsConnected And ss Is Nothing Then ' class level variable XXX change to param, so we can auto-instantiate in Get()
                Me.ss = Me.Client.CreateShellStream("dumb", 80, 24, 800, 600, 102400)
            ElseIf Me.Client.IsConnected And Not ss Is Nothing Then
                ' all is well! We are both connected, and ss is instantiated!
                log.Debug("GetShellStream() all is well, nothing to do here...")
            Else
                log.Fatal("GetShellStream() Client is not connected!")
                Throw New Exception("GetShellStream() Client is not connected!")
            End If
        Catch ex As Exception
            log.Fatal("GetShellStream() cannot create new ShellStream: ", ex.ToString)
        End Try
        Return ss
    End Function
... as you can see from this line:
Me.ss = Me.Client.CreateShellStream("dumb", 80, 24, 800, 600, 102400)
...I'm literally creating a shell, complete with height, width, and a buffer. Here is how I would use it for small, interactive exchanges (by small, I mean no more than 1/2 the buffer (no idea why)):

NOTE: For the purists out there, I am trying to both cleanse the code pasted, but also not give fragments, if I can get away with it. I get very frustrated with fragments because they never work as-is, and not everybody already understands the missing bits... :)
Public Function GetRunningConfig() As String
    Dim sb As StringBuilder = Nothing
    Dim line As String = ""
    Try
        Dim reader = New StreamReader(Me.ss)
        Dim writer = New StreamWriter(Me.ss)
        writer.AutoFlush = True
        sb = New StringBuilder(System.String.Empty)
        ss.Expect(New Regex(":.*>#"), TimeSpan.FromSeconds(3))
        ss.WriteLine("terminal pager 0")
        Thread.Sleep(1000)
        While ss.DataAvailable = False
            Thread.Sleep(500)
            Debug.WriteLine("No data available")
        End While
        ss.WriteLine("show running-config")
        Thread.Sleep(1000)
        While ss.DataAvailable = False
            Thread.Sleep(500)
            Debug.WriteLine("No data available")
        End While
ADDMORE:
        line = ss.ReadLine(TimeSpan.FromSeconds(3))
        If Not IsNothing(line) Then
            If line.Length > 0 Then
                'Debug.WriteLine("adding to sb: [" & line & "]")
                sb.AppendLine(line)
                GoTo ADDMORE
            Else
                Debug.WriteLine("no more!")
            End If
        End If
        Debug.WriteLine("result: [" & sb.ToString() & "]")
    Catch ex As Exception
        log.Fatal("GetRunningConfig() failed: " & ex.ToString())
    End Try
    Return sb.ToString()
End Function
One of the things here that you should note is this line:
ss.Expect(New Regex(":.*>#"), TimeSpan.FromSeconds(3))
...which tells the SshNet what to look for to tell it is is done...
You see, SshNet has no way of knowing whether your server is still thinking or not. In fact, it's not sure what the 'prompt' looks like until you tell it what to 'expect'. That is all I was doing. The above is probably a horrible example to experts, but it took me days to get it to work consistently. I spent a lot of time in the source code, to be honest. :) As you can tell, this function just grabs a running config off a router.

Hopefully you can see that the following snippet from the above is simply waiting until ss.DataAvailable is False (meaning "we're all done!" for small commands/output), waiting a little bit (I ran into trouble when doing it rapid-fire), and saving the response:
While ss.DataAvailable = False
    Thread.Sleep(500)
    Debug.WriteLine("No data available")
End While
ss.WriteLine("show running-config")
Thread.Sleep(1000)
While ss.DataAvailable = False
    Thread.Sleep(500)
    Debug.WriteLine("No data available")
End While
Below further I'll paste in a Function() that logs you in enable, as another example of using the ShellStream.
Friend Function LoginEnable() As Boolean 
    Dim r As String = "" '  for 'response' from FW
    log.Debug("===== STARTING LoginEnable()")
    Try
        If Not Client.IsConnected Then
            Me.Client.Connect()
            Me.ss = GetShellStream()
            log.Debug("LoginEnable() connected? " & Me.Client.IsConnected)
        End If
        If Me.Client.IsConnected Then
            Me.LoggedInUser = True
        End If
        '------------------------------------------------------------------
        Me.ss.Flush()
        Dim retriesEnable As Integer = 3
        While (Not r.ToLower.Contains("password")) And (retriesEnable > 0)
            r = SendCommand("enable", "LF")
            log.Debug("LoginEnable(): r1 = [" & r & "]")
            Thread.Sleep(1500)
            retriesEnable -= 1 ' count down...
        End While
        '------------------------------------------------------------------
        Me.ss.Flush()
        r = SendCommand(Me.EnablePass)
        log.Debug("LoginEnable(): r2 = [" & r & "]")
        Thread.Sleep(1500)
        If DoesContain(r, "#") Then
            Me.LoggedInEnable = True
            Me.LoggedInUser = True
            log.Info("LoginEnable(): Logged in with enable!")
        Else
            Me.LoggedInEnable = False
            Throw New Exception("LoginEnable(): enable attempt FAILED! " & r)
        End If
        Catch ex As Exception
            log.Fatal("LoginEnable(): Cannot log into enable! " & ex.ToString)
            Me.LoggedInEnable = False
        End Try
        log.Debug("===== ENDING LoginEnable() with: " & Me.LoggedInEnable)
        Return Me.LoggedInEnable
    End Function
Now I'm sure the Function SendCommand() caught your eye... That is a very long Function I wrote to allow any part of my code to simply send a command at any time. It does things like check to mae sure we're logged on (if not, log on), logged in enable (if not, log in enable), make sure we even have a ShellStream (if not, go make one), etc. You get the idea.

I also have it do 'retry' where I catch the exceptions thrown, and analyze them. I might make a change in the SshNet parameter, or connect here or there, then try it again (up to X times, usually 3). I also had to allow the code to decide what type of line ending to use (none, LF, CR, CRLF), as devices use these in an inconsistent way. I noticed the same errors were happening (you know, like after a connection times out, etc.), and I wanted to remove the logic to manage these exceptions from every part of my biz logic that wanted to send a command (a lot of dupes doing it that way!).

My post is limited to 10k, so this will be Part 1.

pat
:(

Viewing all articles
Browse latest Browse all 1729

Trending Articles