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.
The Ssh API is compatible with Server D100 and Radius NX devices, but will not run on Edge, Prism, and Solus NX DSPs.
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.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 | Called to connect 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 Ssh table and the error string. |
| SshName.Timeout(Ssh, error) | Callback | Called when there is read or write timeout with an argument of the TcpScocket table and the error string. |
SshName.ID¶
The 0-9 number of the socket in the script.
print(MySsh.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 SshName.New() method call.
SshName.EventHandler(Ssh, event, error)¶
Assign the callback function for the Ssh 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 connected. |
| “Reconnect” | 2 | The Ssh is attempting to reconnect. |
| “Data” | 3 | The Ssh has data available. |
| “Closed” | 4 | The Ssh has closed. |
| “Error” | 5 | The Ssh has errored. |
| “Timeout” | 6 | The Ssh has timed out. |
| "LoginFailed" | 7 | The SSH socket has experienced a login failure. |
See the Example1 to see how this is used.
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 from the Buffer.
print(SshName.BufferLength) >> 8
This is often used in conjunction with a Read call. 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 method call, which remove data from the Buffer. New data arriving will add to the Buffer but this will only occur between executions of the script so the Read Buffer and the value of the BufferLength property will therefore never increase while the script is being executed.
SshName.PublicKey¶
When using PKI authentication, set the property to the Public Key string in SSH format.
SshName.PublicKey = “\
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-----
\
-----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 user name 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
sock: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 disconnect 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 read into a returned table until the specified EOL enumeration case is encountered.
rxLine = Ssh:ReadLine(1) --Read until any combination of linefeeds and returns are reached
The “Ssh.EOL.” enumerations are listed in the table below.
| Enumeration | Value | Functionality |
| Any | 1 | Search for any combination of linefeeds 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 linefeeds and returns are reached
If ‘Custom’ is passed in as the EOL argument, a string delimiter must be included as the second argument.
rxLine = SshName:Readline(6, "TheEnd") --Read until "TheEnd" is reached
SshName:Search(pattern, [start])¶
Searches the unread Buffer in the socket for a string starting at an optional 1-based "start" index. If no "start" index is provided, searching will start at the beginning of the Buffer. Returns the 1-based index of the first character of the search pattern without altering the buffer.
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
end
SshName.Error = ErrorHandler
Alternately, you can use an anonymous function:
SshName.Error = function(Ssh, error)
--handle the 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 TcpScocket 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(Ssh, 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 6 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)