U++ Core/SSH Tutorial
A practical guide to using the SSH package in U++ applications.
Table of Contents
1 Introduction
2 Getting Started
3 Command Execution
4 File Transfer with SFTP
5 Interactive Shell
6 Port Forwarding
7 Advanced Features
8 Best Practices
9 Summary
10 Additional Resources
11 Note on Test Server
1. Introduction
The U++/SSH package provides a clean, RAII-based wrapper around libssh2, making SSH operations natural and safe in U++ applications. The package handles the complexity of SSH's non-blocking state machines, error handling, and resource management so you can focus on your application logic.
2. Getting Started
2.1 Prerequisites
Before using the SSH package, ensure you have:
U++ IDE or umk installed
Core/SSH package in your workspace
A test SSH server (local or remote)
2.2 Basic Application Structure
All examples in this tutorial follow this pattern:
#include "SshBasics.h"
CONSOLE_APP_MAIN
{
StdLogSetup(LOG_COUT|LOG_FILE);
SshSession session;
if(session.Timeout(30000).Connect("demo:password@test.rebex.net:22")) {
// Your SSH operations here
return;
}
LOG(session.GetErrorDesc());
}
The header file includes the necessary U++ and SSH components:
#ifndef _SshBasic_SshBasic_h
#define _SshBasic_SshBasic_h
#include <Core/Core.h>
#include <Core/SSH/SSH.h>
using namespace Upp;
2.3 Connection and Authentication
The Connect() method accepts various formats for credentials:
Inline credentials: "user:password@host:port"
Host only: "host:port" (authenticate separately)
Set a timeout (in milliseconds) before connecting to prevent indefinite hangs. The 30-second timeout used in examples is reasonable for most network conditions.
2.4 Enable Debug Tracing
For troubleshooting, enable SSH tracing in your main function:
CONSOLE_APP_MAIN
{
StdLogSetup(LOG_COUT | LOG_FILE);
Ssh::Trace(); // Enable basic SSH tracing
// Your code...
}
3. Command Execution
3.1 Basic Command Execution
The SshExec class executes a remote command and captures its output. The operator() takes a command line and returns the exit code, while filling two strings with stdout and stderr respectively.
#include "SshBasics.h"
// ExecListDir:
// Demonstrates a remote command execution.
void ExecListDir(SshSession& session)
{
const char *cmdline = "ls -l /pub/example";
SshExec exec(session);
String cout, cerr;
int exit_code = exec(cmdline, cout, cerr);
if(!exec.IsError()) {
DUMP(exit_code);
LOG("Stdout:\n" << cout);
LOG("Stderr:\n" << cerr);
return;
}
LOG(exec.GetErrorDesc());
// Or you can use one of the helper functions instead:
// LOG("Stdout:\n" << SshExecute(session, cmdline));
}
Key Points:
The exec(cmdline, cout, cerr) syntax executes the command synchronously
Check IsError() to verify success before using the output
Exit code 0 typically indicates success
The commented line shows a simpler one-line alternative for quick commands
3.2 Asynchronous Command Execution
For running commands without blocking, use U++'s Async system to execute SSH operations in background threads.
#include "SshBasics.h"
// ExecAsyncListDir: Demonstrates remote command execution in worker threads.
AsyncWork<void> AsyncListDir(SshSession& session, const String& path)
{
auto worker = Upp::Async([=, &session] {
SshExec exec(session);
String cout, cerr;
int exit_code = exec("ls -l " + path, cout, cerr);
if(exec.IsError())
throw Ssh::Error(Format("Worker #%d: %s", exec.GetId(), exec.GetErrorDesc()));
LOG("Directory: " << path);
LOG("Exit code: " << exit_code);
LOG("Stdout:\n" << cout);
LOG("Stderr:\n" << cerr);
});
return pick(worker);
}
void GetResult(AsyncWork<void>& w)
{
try {
w.Get();
}
catch(const Ssh::Error& e) {
LOG(e);
}
}
void ExecAsyncListDir(SshSession& session)
{
const char *path1 = "/";
const char *path2 = "/pub/example/";
auto worker1 = AsyncListDir(session, path1);
auto worker2 = AsyncListDir(session, path2);
GetResult(worker2);
GetResult(worker1);
}
Key Points:
Async() creates a background worker that won't block the main thread
The lambda captures the session by reference and command by value
Get() blocks until the operation completes
Errors are thrown and must be caught at the call site
Each SSH object has a unique ID accessible via GetId() for tracking
4. File Transfer with SFTP
4.1 Simple File Download
The SFtp class provides high-level file operations. LoadFile() downloads a file and returns its contents as a string.
#include "SshBasics.h"
// SFtpGet:
// Demonstrates a file download, using sftp.
void SFtpGet(SshSession& session)
{
const char *path = "/readme.txt";
SFtp sftp(session);
String file = sftp.LoadFile(path);
LOG((!sftp.IsError() ? file : sftp.GetErrorDesc()));
}
Key Points:
Simple one-liner for downloading files
Suitable for text files or small binary files
The ternary operator checks for errors and displays either content or error message
4.2 Streaming File Operations
For large files or when you need to process data incrementally, use SFtpFileIn for reading. This approach keeps memory usage constant regardless of file size.
#include "SshBasics.h"
// SFtpStreamGet:
// Demonstrates a basic stream operation on an sftp remote file object.
void SFtpStreamGet(SshSession& session)
{
const char *path = "/readme.txt";
SFtp sftp(session);
SFtpFileIn fi(sftp, path);
while(!fi.IsEof()) {
int64 pos = fi.GetPos();
String line = fi.GetLine();
if(!line.IsEmpty())
LOG(Format("Offset: %3d, Line: [%s]", pos, line));
}
if(fi.IsError())
LOG(fi.GetErrorText());
}
Key Points:
SFtpFileIn opens a file for reading in stream mode
GetLine() reads line by line, perfect for text files
GetPos() returns the current file position
Stream automatically closes when object goes out of scope
Always check IsError() after the loop completes
4.3 File System Transparency
The SSH package integrates with U++'s FileSystemInfo abstraction, allowing you to work with remote and local filesystems using the same interface.
#include "SshBasics.h"
// SFtpTransparency:
// Demonstrates access to sftp directory hierarcy in a file-system-agnostic (transparent) way.
void ReadDirEntries(FileSystemInfo& fsi, const String& path)
{
int maxentry = 5;
for(FileSystemInfo::FileInfo entry : fsi.Find(path, maxentry)) {
DUMP(entry.filename);
DUMP(entry.is_folder);
DUMP(entry.length);
DUMP(entry.last_access_time);
//...
}
}
void SFtpTransparency(SshSession& session)
{
LOG("Local file system objects----------------------------------------------------------");
ReadDirEntries(StdFileSystemInfo(), GetCurrentDirectory());
SFtp sftp(session);
SFtpFileSystemInfo sfsi(sftp);
LOG("Remote file system objects---------------------------------------------------------");
ReadDirEntries((FileSystemInfo&) sfsi, "/pub/example/*.png");
if(sftp.IsError())
LOG(sftp.GetErrorDesc());
}
Key Points:
SFtpFileSystemInfo wraps SFtp in a FileSystemInfo interface
The same ReadDirEntries() function works for both local and remote filesystems
Supports wildcards like *.png for filtering
maxentry parameter limits results to prevent overwhelming output
Cast to FileSystemInfo& enables polymorphic usage
4.4 Multi-threaded SFTP Downloads
For downloading multiple files in parallel, create async workers that each perform a download operation.
#include "SshBasics.h"
// SFtpAsyncGet: DEmonstrates multiple file downloads, using worker threads.
AsyncWork<void> AsyncGet(SshSession& session, const String& path)
{
auto worker = Upp::Async([=, &session] {
LOG("Downloading " << path);
SFtp sftp(session);
String file = sftp.LoadFile(path);
if(sftp.IsError())
throw Ssh::Error(Format("Worker #%d: %s", sftp.GetId(), sftp.GetErrorDesc()));
LOG("File " << GetFileName(path) << " is successfully downloaded.");
});
return pick(worker);
}
void CheckError(AsyncWork<void>& w)
{
try {
w.Get();
}
catch(const Ssh::Error& e) {
LOG(e);
}
}
void SFtpAsyncGet(SshSession& session)
{
const int MAXDOWNLOADS = 4;
const char *path = "/pub/example/";
SFtp browser(session);
SFtp::DirList ls;
if(!browser.ListDir(path, ls)) { // Get a dir listing to extract file names on-the-fly.
LOG(browser.GetErrorDesc());
return;
}
Array<AsyncWork<void>> workers;
for(const auto& e : ls) {
if(!e.IsFile() || (e.GetSize() > 65535))
continue;
if(workers.GetCount() == MAXDOWNLOADS)
break;
workers.Add(AsyncGet(session, AppendFileName(path, e.GetName())));
}
while(!workers.IsEmpty()) {
for(int i = 0; i < workers.GetCount(); i++) {
auto& worker = workers[i];
if(worker.IsFinished()) {
CheckError(worker);
workers.Remove(i);
break;
}
Sleep(1);
}
}
}
Key Points:
First, use a "browser" SFtp instance to list directory contents
Filter entries to download only files under a size limit
Create up to MAXDOWNLOADS concurrent workers
Poll workers in a loop, removing finished ones
Each worker gets its own SFtp instance for thread safety
4.5 Alternative: Using CoFor for Parallelization
U++'s CoFor provides a cleaner way to parallelize operations with automatic thread pool management.
#include "SshBasics.h"
// SFtpAsyncGet2: Demonstrates multiple file downloads, using a parallelization loop.
void SFtpAsyncGet2(SshSession& session)
{
const int MAXDOWNLOADS = 4;
const char *path = "/pub/example/";
SFtp::DirList ls;
{
// Get a remote dir listing.
SFtp browser(session);
if(!browser.ListDir(path, ls)) {
RLOG(browser.GetErrorDesc());
return;
}
}
// Filter the dir list.
auto files = FilterRange(ls, [](const SFtp::DirEntry& e) { return e.IsFile() && e.GetSize() <= 65536; });
// Loop over.
CoFor(min(files.GetCount(), MAXDOWNLOADS), [&files, &path, &session](int i){
const SFtp::DirEntry& e = files[i];
String fpath = AppendFileName(path, e.GetName());
RLOG("Downloading " << fpath);
SFtp sftp(session);
String file = sftp.LoadFile(fpath);
if(sftp.IsError())
RLOG(Format("Worker #%d: %s", sftp.GetId(), sftp.GetErrorDesc()));
else
RLOG("File " << e.GetName() << " is successfully downloaded.");
});
}
Key Points:
FilterRange() creates a filtered view without copying
CoFor() automatically manages the thread pool
Much cleaner than manual worker array management
Lambda receives index i to access the file list
5. Interactive Shell
5.1 Console Shell
The Console() method provides a fully interactive terminal session. It handles terminal emulation and passes input/output between your console and the remote shell.
#include "SshBasics.h"
// ShellConsole:
// Demonstrates an interactive shell in console mode.
void ShellConsole(SshSession& session)
{
SshShell shell(session);
shell.Timeout(Null);
if(!shell.Console("ansi"))
LOG(shell.GetErrorDesc());
}
Key Points:
Timeout(Null) disables timeout for interactive sessions
Terminal type "ansi" provides broad compatibility
Other options include "xterm", "vt100", "linux"
The method blocks until the shell session ends
User types commands directly; output appears immediately
5.2 X11 Forwarding
To run graphical applications on the remote server with the display forwarded to your local machine, enable X11 forwarding.
#include "SshBasics.h"
// X11Forwarding:
// Demonstrates an interactive shell with X11 forwarding, in console mode.
// This example requires a running X server.
void X11Forwarding(SshSession& session)
{
SshShell x11shell(session);
x11shell.Timeout(Null);
session.WhenX11 = [&x11shell](SshX11Handle xhandle)
{
x11shell.AcceptX11(xhandle);
};
if(!x11shell.ForwardX11().Console("ansi"))
LOG(x11shell.GetErrorDesc());
}
Key Points:
Requires an X server running locally (Xorg, XQuartz, Xming, etc.)
WhenX11 callback accepts incoming X11 connection requests
ForwardX11() enables X11 forwarding before opening the console
The shell must remain open while graphical applications run
Launch GUI apps in the remote shell; they'll display locally
6. Port Forwarding
6.1 TCP/IP Tunneling
SSH tunneling creates secure channels for network traffic. Set up a listener that accepts connections and forwards them through the SSH session.
#include "SshBasics.h"
// OpenTcpTunnel:
// Demonstrates tcp-ip and port forwarding feature of the ssh protocol.
// This example requires upp/reference/SocketServer and upp/reference/SocketClient examples.
// SocketClient: Set the port number to 3215.
bool ServerSendRecv(SshSession& session, String& data)
{
// SshTunnel <-> SocketServer
SshTunnel tunnel(session);
if(!tunnel.Connect("127.0.0.1", 3214)) {
LOG("ServerSendRecv(): " << tunnel.GetErrorDesc());
return false;
}
tunnel.Put(data + '\n');
data = tunnel.GetLine();
return !data.IsEmpty();
}
void ForwardTcpIp(SshSession& session)
{
SshTunnel listener(session);
if(!listener.Listen(3215, 5)) {
LOG("ForwardTcpIp(): " << listener.GetErrorDesc());
return;
}
LOG("SSH tunnel (server mode): Waiting for the requests to be tunneled...");
for(;;) {
SshTunnel tunnel(session);
if(!tunnel.Accept(listener)) {
LOG("ForwardTcpIp(): " << tunnel.GetErrorDesc());
return;
}
// SocketClient <-> SshTunnel
String data = tunnel.GetLine();
LOG("Tunneled Request: " << data);
if(!data.IsEmpty() && ServerSendRecv(session, data)) {
LOG("Tunneled Response: " << data);
tunnel.Put(data + '\n');
}
}
}
Key Points:
Listen(port, backlog) sets up a remote listener
Accept() waits for and accepts incoming connections
Each accepted connection gets its own SshTunnel object
Use Put() and GetLine() for bidirectional communication
The helper function ServerSendRecv() forwards to another local service
This creates a chain: RemoteClient → SSH → LocalService
7. Advanced Features
7.1 SCP File Transfer
The Scp class provides SCP protocol file transfer, which is simpler but less flexible than SFTP.
#include "SshBasics.h"
// ScpGet:
// Demonstrates a file download using scp.
void ScpGet(SshSession& session)
{
const char *path = "the/full/path/of/the/file/to/downlad";
Scp scp(session);
String file = scp.LoadFile(path);
LOG((!scp.IsError() ? file : scp.GetErrorDesc()));
}
Key Points:
SCP is good for simple file uploads and downloads
No directory listing or file management operations
API mirrors SFtp: LoadFile() for downloads
Requires full path to the file
Generally simpler but less capable than SFTP
7.2 Pick Semantics
U++ objects support pick (move) semantics for efficient transfer of ownership without copying internal state.
#include "SshBasics.h"
// SshPick:
// Demonstrates the pick (move) semantics for ssh objects.
void SshPick(SshSession& session)
{
SshSession psession = pick(session); // All Ssh-based objects are pickable.
if(!session)
LOG("SshSession object is picked.");
SFtpGet(psession);
}
Key Points:
pick() transfers ownership from one object to another
The original object becomes invalid (null state)
Check validity with if(!object) after picking
Useful for returning SSH objects from functions
Avoids expensive copying of sockets and internal state
7.3 Polymorphism and RTTI
All SSH classes inherit from the Ssh base class, enabling polymorphic storage and safe runtime type checking.
#include "SshBasics.h"
// SshPolymorphism:
// Demonstrates polymorphism and RTTI for Ssh objects.
void SshPolymorphism(SshSession& session)
{
constexpr const char *path = "/readme.txt";
Array<Ssh> channels;
channels.Create<Scp>(session);
channels.Create<SFtp>(session);
channels.Create<SshExec>(session);
for(Ssh& channel : channels){
if(channel.Is<Scp>()) {
LOG("\nFound: Scp object");
LOG("-----------------\n");
LOG(channel.To<Scp>().LoadFile(path));
}
else
if(channel.Is<SFtp>()) {
LOG("\nFound: Sftp object");
LOG("------------------\n");
LOG(channel.To<SFtp>().GetInfo(path).GetName());
}
else
if(channel.Is<SshExec>()) {
LOG("\nFound: Exec object");
LOG("------------------\n");
String out, err;
channel.To<SshExec>().Execute("ls -l", out, err);
LOG(out);
LOG(err);
}
if(channel.IsError()) {
LOG("Operation failed. Reason: " << channel.GetErrorDesc());
}
}
}
Key Points:
Store different SSH types in the same container
Create<T>() constructs objects of specific types
Is<T>() checks runtime type without casting
To<T>() performs a safe cast to the specific type
Each object maintains its error state independently
Useful for managing multiple connections or operations
7.4 Verbose Logging
For deep debugging of SSH protocol issues, enable libssh2's internal tracing with fine-grained control over what to log.
#include "SshBasics.h"
// TraceVerbose:
// To activate verbose logging, you need to set the LIBSSH2TRACE flag via
// TheIDE->Main Configuration settings.
void TraceVerbose()
{
Ssh::TraceVerbose(
// LIBSSH2_TRACE_SOCKET |
// LIBSSH2_TRACE_KEX |
LIBSSH2_TRACE_AUTH |
LIBSSH2_TRACE_CONN |
// LIBSSH2_TRACE_SCP |
// LIBSSH2_TRACE_SFTP |
// LIBSSH2_TRACE_PUBLICKEY |
LIBSSH2_TRACE_ERROR
);
}
Key Points:
Must compile with LIBSSH2TRACE flag enabled
Combine flags with | to trace multiple subsystems
LIBSSH2_TRACE_AUTH shows authentication details
LIBSSH2_TRACE_CONN shows connection events
LIBSSH2_TRACE_ERROR logs all errors
Comment/uncomment lines to control verbosity
Produces very detailed output (use for troubleshooting only)
8. Best Practices
8.1 Error Handling
Always check for errors after SSH operations:
SFtp sftp(session);
String file = sftp.LoadFile("/path");
if(sftp.IsError()) {
LOG("Download failed: " << sftp.GetErrorDesc());
return;
}
8.2 Resource Management
The package uses RAII, so resources are automatically cleaned up:
{
SshSession session;
session.Connect("host:22");
{
SFtp sftp(session);
sftp.LoadFile("/file");
// SFtp automatically closed here
}
// Session automatically closed here
}
8.3 Timeout Configuration
Set appropriate timeouts for different operations:
session.Timeout(5000).Connect("host:22"); // 5s for connection
SFtp sftp(session);
sftp.Timeout(300000); // 5 minutes for large transfers
SshShell shell(session);
shell.Timeout(Null); // No timeout for interactive use
8.4 Thread Safety
Each session is independent and thread-safe. Multiple threads can use separate sessions simultaneously:
void ParallelSessions()
{
Array<Thread> threads;
for(int i = 0; i < 4; i++) {
threads.Add().Run([=] {
SshSession session;
session.Connect("host:22");
// Each thread has its own session
});
}
for(Thread& t : threads)
t.Wait();
}
8.5 Memory Efficiency
For large files, always use streaming:
// Bad: loads entire file into memory, fails if larger than 2GB
String huge = sftp.LoadFile("/Min2GB_file.iso");
// Good: stream in chunks
SFtpFileIn fi(sftp, "/Min2GB_file.iso");
while(!fi.IsEof()) {
String chunk = fi.Get(65536); // 64KB chunks
// Process chunk
}
9. Summary
The U++/SSH package provides a robust, safe, and efficient way to handle SSH operations in C++. Key takeaways:
RAII design ensures automatic resource cleanup
Type-safe API prevents common errors
Thread-safe for parallel operations
Comprehensive error handling with exceptions
Flexible timeout management
Support for all major SSH features: exec, shell, SFTP, SCP, tunneling
Start with simple examples and gradually explore more advanced features as your application needs grow.
10. Additional Resources
You may also find these resources helpful:
libssh2 Reference: https://www.libssh2.org/
11. Note on Test Server
The examples use the public test server test.rebex.net with credentials demo:password. Note that SCP, X11 forwarding, and TCP-IP/port forwarding examples will not work with this public server. To test these features, set up a local SSH server (e.g., OpenSSH) for testing purposes.
|