Overview
Examples
Screenshots
Comparisons
Applications
Download
Documentation
Tutorials
UppHub
Status & Roadmap
FAQ
Authors & License
Forums
Funding U++
Search on this site









SourceForge.net Logo

SourceForge.net Logo

GitHub Logo

Discord Logo

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.

 

Do you want to contribute?