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()
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
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
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
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
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
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
SshName.BufferLength
The count of bytes received and waiting to be read.
print(SshName.BufferLength) >> 8
rxLine = SshName:Read(ssh.BufferLength)
SshName.PublicKey
When using PKI authentication, set the property to the Public Key string in SSH format.
SshName.PublicKey = “<encryption algorithm> <key> <optional comment>”
SshName.PublicKey = "ssh-rsa AAAAB3NzaC2yc2EAAAADAi0657Y2YoSG+CdR1h1EQa1fcYX50y8P Comment-about-the-key"
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-----]]
SshName.PrivateKey = [[-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdz
...
7euEFqyIRfs+fGQC/LefbBrFugjPPmkAAAAAAQIDBAUGBwgJCg5=
-----END OPENSSH PRIVATE KEY-----]]
SshName.PrivateKeyPassword
When using PKI authentication, there may be a password required for the Private Key.
SshName.PrivateKeyPassword = "My-unbreakable-password"
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")
SshName:Connect("169.254.179.214", port, "username", "")
currentDeviceIP = Device.RemoteUnit.DanteIP
SshName:Connect(currentDeviceIP, port, "username", "password")
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
SshName:Read(length)
Call to read an integer “length” of bytes into a returned table from the connected socket buffer.
rx = SshName:Read(10000)
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
| 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 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
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
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
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)
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)