Skip to content

SSH

Overview

Ssh API allows communication to other devices via SSH. The SSH API only works using the control network. The SSH API has many similarities to the TcpSocket API.

Ssh.

Return Type Comment
Ssh.New() SSH object Used to create a new SSH object.

Ssh.New()

New() creates an Ssh object as a global table.

MySsh = Ssh.New()
Up to 8 Ssh objects can be simultaneously requested with separate calls. When a socket is closed (see below), it is available for use again with another New call.

SshName

The following methods and properties are available for an Ssh object with name “SshName”. The API supports authentication via either User Name and Password or via PKI authentication.

Return Type Comment
SshName.ID Integer The number of the socket in the script.
SshName.EventHandler(Ssh, event, error) Callback Called when an event has occurred with an argument of the Ssh table, an event from the Events Table and any error string.
SshName.WriteTimeout Number The time in seconds to wait for a write to complete before the socket times out and disconnects. 0 disables the timeout.
SshName.ReconnectTimeout Number The time in seconds to wait before reconnecting if the socket disconnects externally.
SshName.IsConnected Boolean Indicates if the socket is currently connected.
SshName.IsInteractive Boolean Set to true if connection requires pseudoterminal.
SshName.BufferLength Integer The count of bytes received and waiting to be read.
SshName.PublicKey String A public key, if needed, specified in SSH format.
SshName.PrivateKey String A private key, if needed, specified in PEM format.
SshName.PrivateKeyPassword String A private key password, if needed.
SshName:Connect(ip, port, username, password) Method Connects the socket to an IP address.
SshName:Disconnect() Method Disconnects a connected socket.
SshName:Write(data) Method Writes a table of data to a connected socket.
SshName:Read(length) Method Reads an integer count of bytes into a returned table from the connected socket.
SshName:ReadLine(EOL, [delimiter]) Method Reads into a returned table until the specified EOL enumeration case is encountered. If it is a custom delimiter, then it is specified as a string.
SshName:Search(pattern, [start]) Method Searches the unread data in the socket for a string starting at an optional 1-based start index. Returns the 1-based index but not the data.
SshName.LoginFailed(Ssh, error) Callback Called when a connection has a failed login attempt with an argument of the Ssh table and the error string.
SshName.Connected(Ssh) Callback Called when a socket connects with an argument of the Ssh table.
SshName.Reconnect(Ssh) Callback Called when a socket is attempting to reconnect with an argument of the Ssh table.
SshName.Data(Ssh, data) Callback Called when unread data is available with an argument of the Ssh table and the data table.
SshName.Closed(Ssh) Callback Called when a socket is closed with an argument of the Ssh table.
SshName.Error(Ssh, error) Callback Called when there is an error with an argument of the SshSocket table and the error string.
SshName.Timeout(Ssh, error) Callback Called when there is read or write timeout with an argument of the Ssh table and the error string.

SshName.ID

The 1-8 number of the socket in the script.

print(SshName.ID)                     >> 1
Up to 8 Ssh objects can be simultaneously requested with separate calls. When a socket is closed (see below), it is available for use again with another New call.

SshName.EventHandler(Ssh, event, error)

Assign the callback function for the Ssh socket that will be called whenever an event occurs with an argument of the Ssh table, an event from the Events Table and any error string.

This can be done in two ways. With a dedicated function:

function SshHandler (sock, evt, err)
      --handle the event
end

SshName.EventHandler = SshHandler
Alternately, you can use an anonymous function:
SshName.EventHandler = function(sock, evt, err)
      --handle the event
end

Usually, you will be handling the multiple possible event types with if-elseif-else statements. To help with readability in your comparisons, there is a table of “Ssh.Events.” enumerations.

Enumeration Value Functionality
“Connected” 1 The Ssh socket connected.
“Reconnect” 2 The Ssh socket is attempting to reconnect.
“Data” 3 The Ssh socket has data available.
“Closed” 4 The Ssh socket has closed.
“Error” 5 The Ssh socket has errored.
“Timeout” 6 The Ssh socket has timed out.
"LoginFailed" 7 The SSH socket has experienced a login failure.

See the Example 1 to see how this is used.

SshName.WriteTimeout

Set the WriteTimeout for the Ssh socket. This configures the time in seconds to wait for a write to complete before the socket times out. The default is 0, which disables the write timeout so the socket will wait indefinitely.

SshName.WriteTimeout = 5
A write may not complete immediately if the connection is busy or the remote device is slow to accept the data. If a write does not complete within the WriteTimeout, the Timeout callback is called (or the “Timeout” case in the general EventHandler) and the socket is disconnected. Set to 0 to disable the timeout.

SshName.ReconnectTimeout

Set the ReconnectTimeout for the Ssh socket, overriding the default value of 5 seconds. This configures the time in seconds to wait before attempting to reconnect if the socket disconnects externally. The Reconnect callback will be called when it attempts to reconnect. If successful, the Connected callback will be called. Set to 0 to disable trying to reconnect.

SshName.ReconnectTimeout = 1
Normally the default is acceptable, but you may need to adjust this if you know the device you are connecting to requires a longer period before reconnect.

Additionally, it is good practice to manually monitor connection status and have a reconnect path in your code.

SshName.IsConnected

Indicates if the socket is currently connected.

print(SshName.IsConnected)           >> True
This is used to test connection state and take appropriate action.

SshName.IsInteractive

The “IsInteractive” property is used to tell the SSH server to format communication for PTY mode. Basic “vanilla” terminal emulation is used when this property is set to "true". By default it is set to "false".

SshName.IsInteractive = true
Some devices or services will require this property set a certain way. If a script is having trouble connecting but all other settings appear correct, try setting this property to "true".

SshName.BufferLength

The count of bytes received and waiting to be read.

print(SshName.BufferLength)          >> 8
This is often used in conjunction with Read. See below and Example 1 for real world usage.
rxLine = SshName:Read(ssh.BufferLength)
The BufferLength property is updated after every operation which could change its value, such as a Read or ReadLine call. New data will only be provided between executions of the script. New data can’t arrive while the script is being executed so the value of the BufferLength property will therefore never increase while script is being executed.

SshName.PublicKey

When using PKI authentication, set the property to the Public Key string in SSH format.

SshName.PublicKey = <encryption algorithm> <key> <optional comment>
A typical encryption algorithm is “ssh-rsa” so for example, the PublicKey property might be set to the following:
SshName.PublicKey = "ssh-rsa AAAAB3NzaC2yc2EAAAADAi0657Y2YoSG+CdR1h1EQa1fcYX50y8P Comment-about-the-key"
The maximum Public Key length is 4095 ASCII characters.

SshName.PrivateKey

When using PKI authentication, set the property to the Private Key string in PEM format (OpenSSL). Due to the extreme length of this string, the quote-less multiline format for entering strings is used.

SshName.PrivateKey = [[-----BEGIN OPENSSH PRIVATE KEY----- 
<key>
 -----END OPENSSH PRIVATE KEY-----]]
So for example, the PrivateKey property might be set to the following:
SshName.PrivateKey = [[-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdz
...
7euEFqyIRfs+fGQC/LefbBrFugjPPmkAAAAAAQIDBAUGBwgJCg5=
-----END OPENSSH PRIVATE KEY-----]]
The maximum Private Key length is 4095 ASCII characters.

SshName.PrivateKeyPassword

When using PKI authentication, there may be a password required for the Private Key.

SshName.PrivateKeyPassword = "My-unbreakable-password"
The maximum Private Key Password length is 255 ASCII characters. If no Private Key Password is needed, this property can be left empty.

SshName:Connect(ip, port, username, password)

Called to connect the socket to an IP address or hostname and port (0-65535), with the provided username and password. Usernames and passwords are expected to be ASCII. The maximum number of characters for both are 255.

SshName:Connect("169.254.179.214", port, "username", "password")
If a Public Key/Private Key pair are being used, the "password" should be set to an empty string.
SshName:Connect("169.254.179.214", port, "username", "")
Information from the Device table can be used to ensure the correct IP address is used.
currentDeviceIP = Device.RemoteUnit.DanteIP
SshName:Connect(currentDeviceIP, port, "username", "password")
If authorization is successful, the Connected and EventHandler callbacks will be called. If unsuccessful, the LoginFailed and EventHandler callbacks will be called if used.

Once connected, the script app will poll for new data from the SSH connection. If present, the data will be buffered up to the maximum buffer size of 64K bytes. When the Read and ReadLine methods are called, the data is copied from the buffer to a secondary buffer to return to the LUA script. This is because the primary buffer must immediately make room for new data once read by the script. The return buffer is also a maximum of 64K bytes. If transmissions are larger than this, new data won’t be lost, but any ReadLine/Search call my not find the search character and stall data reception until the script reads the data.

Note

This is unlike the TcpSocket API which does not actually buffer incoming data until the Read or ReadLine methods are called. With TcpSocket, the ReadLine method does not buffer the data unless the required EOL or search character is found.

SshName:Disconnect()

Call to disconnects a connected socket.

SshName:Disconnect()                   -- socket is disconnected

SshName:Write(data)

Call to write a table of data to a connected socket.

SshName:Write('V\x0d')                 -- Data is written and sent
Once connected, the Lua script may write data to the connection. Writes must not be any larger than 64K, or an error will be returned and the write aborted.

SshName:Read(length)

Call to read an integer “length” of bytes into a returned table from the connected socket buffer.

rx = SshName:Read(10000)
The read bytes are removed from the buffer.

SshName:ReadLine(EOL, [delimiter])

Call to reads into a returned table until the specified EOL enumeration case is encountered.

rxLine = SshName:ReadLine(1)     --Read until any combination of line feeds and returns are reached
The “Ssh.EOL.” enumerations are listed in the table below.

Enumeration Value Functionality
Any 1 Search for any combination of line feeds and returns.
CrLf 2 Search for a return or linefeed and return.
CrLfStrict 3 Search for a linefeed and a return.
Lf 4 Search for a linefeed.
Null 5 Search for an ASCII zero terminator.
Custom 6 Search for a custom string.
rxLine = SshName:ReadLine(Ssh.EOL.Any) --Read until any combination of line feeds and returns are reached

If “Custom” is used for the EOL enumeration, then it is specified as a string with the optional second delimiter argument.

rxLine = SshName:Readline(6, "TheEnd") --Read until "TheEnd" is reached

SshName:Search(pattern, [start])

Searches the unread data in the socket for a string starting at an optional 1-based start index. Returns the 1-based index but not the data.

SshName:Search("ImportantMessage", 1)                 >> 16
SshName:Search("AnotherMessage", 17)                  >> nil
Search looks for the exact string passed in. There is no interpretation of specific characters in the search string (e.g. * or ?) and Lua pattern matching is not supported.

Search can be used to improve efficiency in situations where you are looking for looking for a particular response. This can be useful for a protocol that put out a lot of data and you only care about one thing. Instead of reading each line and parsing the data looking for a particular string, the script can search for it, and if it doesn’t exist, throw all of the received data away with a single Read() command.

SshName.LoginFailed(Ssh, error)

Define a callback function that will be called when a the login fails during connection. The error provides useful information so normally it is printed to the debug or presented to the user.

This can be done in two ways. With a dedicated function:

function LoginFailedHandler (Ssh, error)
      --handle the failure
      print(error)
end

SshName.LoginFailed = LoginFailedHandler

Alternately, you can use an anonymous function:

SshName.LoginFailed = function(Ssh, error)
      --handle the failure
      print(error)
end
This can be used instead of the general EventHandler callback. See Example 2 below.

SshName.Connected(Ssh)

Define a callback function that will be called when a socket connects with an argument of the Ssh table.

This can be done in two ways. With a dedicated function:

function ConnectedHandler (Ssh)
      --handle the new Connection
end

SshName.Connected = ConnectedHandler

Alternately, you can use an anonymous function:

SshName.Connected = function(Ssh)
      --handle the new Connection
end
This can be used instead of the general EventHandler callback. See Example 2 below.

SshName.Reconnect(Ssh)

Define a callback function that will be called when a socket is attempting to reconnect with an argument of the Ssh table.

This can be done in two ways. With a dedicated function:

function ReconnectHandler (Ssh)
      --handle the Reconnection attempt
end

SshName.Reconnect = ReconnectHandler

Alternately, you can use an anonymous function:

SshName.Reconnect = function(Ssh)
      --handle the Reconnection attempt
end

This can be used instead of the general EventHandler callback. See Example 2 below.

SshName.Data(Ssh, data)

Define a callback function that will be called when unread data is available with an argument of the Ssh table and the data table.

This can be done in two ways. With a dedicated function:

function DataHandler (Ssh, data)
      --handle the data
end

SshName.Data = DataHandler

Alternately, you can use an anonymous function:

SshName.Data = function(Ssh, data)
      --handle the data
end

The “data” can be read with the Read or ReadLine functions.

This can be used instead of the general EventHandler callback. See Example 2 below.

SshName.Closed(Ssh)

Define a callback function that will be called when a socket is closed with an argument of the Ssh table.

Once connected, if it becomes disconnected due to any reason other than explicit calling of the Disconnect method, the Closed callback will be called. If a reconnection timeout is non-zero the app will attempt to reconnect after the programmed time.

This can be done in two ways. With a dedicated function:

function ClosedHandler (Ssh)
      --handle the socket closing
end

SshName.Closed = ClosedHandler

Alternately, you can use an anonymous function:

SshName.Closed = function(Ssh)
      --handle the socket closing
end

This can be used instead of the general EventHandler callback. See Example 2 below.

SshName.Error(Ssh, error)

Define a callback function that will be called when there is an error with an argument of the Ssh table and the error string. The error string can be useful in diagnosing the problem so it is often printed to the debug log or otherwise presented to the user.

This can be done in two ways. With a dedicated function:

function ErrorHandler (Ssh, error)
      --handle the error
      print(error)
end

SshName.Error = ErrorHandler
Alternately, you can use an anonymous function:
SshName.Error = function(Ssh, error)
      --handle the error
      print(error)
end

This can be used instead of the general EventHandler callback. See Example 2 below.

SshName.Timeout(Ssh, error)

Define a callback function that will be called when there is read or write timeout with an argument of the SSH table and the error string.

This can be done in two ways. With a dedicated function:

function TimeoutHandler (Ssh, error)
      --handle the Timeout
end

SshName.Timeout = TimeoutHandler

Alternately, you can use an anonymous function:

SshName.Timeout = function(Ssh, error)
      --handle the Timeout
end

This can be used instead of the general EventHandler callback. See Example 2 below.

Usage Examples

The following examples illustrate how these can be used:

Example 1 – SSH Connection with Password

This example shows how Ssh is typically used with a simple password connection. This example also uses a single EventHandler.

SshName = Ssh.New()
address = "server.address" --enter correct address
port = 22 --enter correct port number
user = "user_name" --enter correct user name
password = "password" --enter correct password

--EventHandler
SshName.EventHandler = function(TcpSocket, evt, error)
    if evt == Ssh.Events.Connected then
        --handle the new Connection
        print("socket connected\r")
        --begin sending data using ssh:Write as needed
    elseif evt == Ssh.Events.Reconnect then
        --handle the Reconnection attempt
        print("socket reconnecting...\r")
    elseif evt == Ssh.Events.Data then
        --handle the data
        rxLine = sock:Read(ssh.BufferLength)
        if (nil ~= rxLine) then
            print(rxLine)
        end
    elseif evt == Ssh.Events.Closed then
        --handle the socket closing
        print("socket closed by remote\r")
    elseif evt == Ssh.Events.Error then
        --handle the error
        print(string.format("Error: '%s'\r", error))
    elseif evt == Ssh.Events.Timeout then
        --handle the Timeout
        print("socket closed due to timeout\r")
    elseif evt == Ssh.Events.LoginFailed then
        print("login failed with " .. string.format("error: '%s'\r", error))
    else
        print("unknown socket event\r")
  end
end

SshName:Connect(address, port, user, password)
The Events enumeration table described above is used to compare the received “evt” and perform the appropriate action. You should generally always handle all 7 possible event types in the enumeration table as shown.

Example 2 - SSH Connection with Keys using Discrete Handlers

Instead of handling all the events with a single handler, you can alternately use a separate handler per event type. This may be preferable as it tends to be easier to read since there isn’t a large section of if-ifelse needed. As before, you should generally handle all seven events.

SshName = Ssh.New()
address = "server.address" --enter correct address
port = 22 --enter correct port number
user = "user_name" --enter correct user name
password = "" --for key connection, password should be blank

-- public key in SSH format
SshName.PublicKey = "ssh-rsa Your_Public_Key"

-- private key in OpenSSL PEM format
SshName.PrivateKey = [[-----BEGIN OPENSSH PRIVATE KEY-----
Your Private Key
-----END OPENSSH PRIVATE KEY-----]]

SshName.PrivateKeyPassword = "password" --if needed

--setup timer
function TimerClick()
    --do stuff with ssh:Write as needed
end

MyTimer = Timer.New()
MyTimer.EventHandler = TimerClick

--individual SSH handler functions
SshName.Connected = function()
    print("ssh connected")
    MyTimer:Start(10)
end

SshName.Reconnect = function()
    print("ssh reconnecting...")
end

SshName.Closed = function()
    print("ssh closed")
end

SshName.Error = function(s, err)
    print(string.format("Error: '%s'\r", error))
end

SshName.Timeout = function()
    print("ssh timeout")
end

SshName.LoginFailed = function()
    print("login failed with " .. string.format("error: '%s'\r", error))
end

SshName.Data = function()
    --Handle the data line by line
    line = SshName:ReadLine(Ssh.EOL.Any)
    while line do
        print(line)
        line = SshName:ReadLine(Ssh.EOL.Any)
    end
end

SshName:Connect(address, port, user, password)