SSH připojení pomocí ESP32

V dnešním článku se podíváme na to, jak se z ESP32 development boardu připojit pomocí SSH ke vzdálenému počítači.

Pro vývoj je použito PlatformIO.

platformio.ini

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
lib_deps = ewpa/LibSSH-ESP32@^4.2.0

main.cpp

Jediná externí závislost je libssh-esp32.

#include <Arduino.h>
#include <WiFi.h>
#include <libssh_esp32.h>
#include "libssh_esp32_config.h"
#include <libssh/libssh.h>
#include <vector>
#include <string>

Budeme potřebovat SSID WiFi sítě a heslo.

const char *ssid = "ssid-vasi-wifi";
const char *password = "vase-nejtajnejsi-heslo";

Potřebujeme nastavit přihlašovací údaje pro SSH a příkaz který po přihlášení provedeme.

const int ssh_port = 22;
const char *ssh_username = "pi";
const char *ssh_password = "super-tajne-heslo";
const char *ssh_command = "ls -l";

Dále seznam IP adres serverů na které se chceme připojit.

std::vector<std::string> server_ips = {
    "192.168.1.254",
};

Vytvoříme funkci pro připojení k WiFi. Na konci vypíšeme do konzole MAC adresu přístupového bodu, ke kterému jsme se připojili.

void connectToWiFi()
{
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED)
    {
        delay(1000);
        Serial.println("Connecting to WiFi..");
    }
    Serial.print("Připojeno k AP s MAC: ");
    Serial.println(WiFi.BSSIDstr());
}

Ted ta nejdelší část. Funkce co vytvoří SSH spojení, provede příkaz a výsledek vypíše na standardní výstup. Funkce si po sobě uklízí.

/**
 * Execute SSH Command
 *
 * @param ip Server IP
 * @param username SSH Username
 * @param password SSH Password
 * @param command Command to execute
 */
void executeSSHCommand(const char *ip, const char *username, const char *password, const char *command)
{
    // Create a new SSH session
    ssh_session session = ssh_new();
    if (session == NULL)
    {
        Serial.println("Failed to create SSH session");
        return;
    }

    // Set SSH options for the session
    ssh_options_set(session, SSH_OPTIONS_HOST, ip);
    ssh_options_set(session, SSH_OPTIONS_USER, username);

    // Connect to SSH server
    int rc = ssh_connect(session);
    if (rc != SSH_OK)
    {
        Serial.println("Failed to connect to SSH server");
        return;
    }

    // Verify the server's identity
    rc = ssh_userauth_password(session, NULL, password);
    if (rc != SSH_AUTH_SUCCESS)
    {
        Serial.println("Failed to authenticate");
        return;
    }

    // Create a new SSH channel
    ssh_channel channel = ssh_channel_new(session);
    if (channel == NULL)
    {
        Serial.println("Failed to create SSH channel");
        return;
    }

    // Open a new SSH session
    rc = ssh_channel_open_session(channel);
    if (rc != SSH_OK)
    {
        Serial.println("Failed to open SSH session");
        return;
    }

    // Execute the command
    rc = ssh_channel_request_exec(channel, command);
    if (rc != SSH_OK)
    {
        Serial.println("Failed to execute command");
        return;
    }

    // Read the output of the command
    char buffer[256];
    int nbytes; // Number of bytes read
    nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
    while (nbytes > 0)
    {
        if (write(1, buffer, nbytes) != nbytes) // Write to stdout
        {
            Serial.println("Failed to write to stdout");
            return;
        }
        nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
    }

    // Check if there was an error reading from the channel
    if (nbytes < 0)
    {
        Serial.println("Failed to read from SSH channel");
        return;
    }

    // Send EOF and close the channel
    ssh_channel_send_eof(channel);

    // Close the channel
    ssh_channel_close(channel);

    // Free the channel
    ssh_channel_free(channel);

    // Disconnect the session
    ssh_disconnect(session);

    // Free the session
    ssh_free(session);
}

Teď potřebujeme funkci která provede SSH příkaz pro všechny naše servery, které jsou definované v server_ips.

/**
 * Execute SSH Command on all servers
 * 
 * @param username SSH Username
 * @param password SSH Password
 * @param command Command to execute
 */
void executeSSHCommandOnAllServers(const char *username, const char *password, const char *command)
{
    for (std::string ip : server_ips)
    {
        executeSSHCommand(ip.c_str(), username, password, command);
    }
}

Teď už jen zbývá provést vše při startu ESPčka.

void setup()
{
    Serial.begin(115200);
    while (!Serial)
    {
        ; // Wait for serial to be ready
    }

    connectToWiFi();
    ssh_init();
    executeSSHCommandOnAllServers(ssh_username, ssh_password, ssh_command);
}

Hlavní smyčku programu pro tento příklad necháme odpočívat.

void loop()
{
    delay(1000); // Delay for 1 second
}

To je vše. Po kompilaci, nahrání kódu a spuštění ESPčka se připojí k WiFi a poté provede SSH připojení na zadané IP adresy a provede ls -l příkaz. Ten vypíše do konzole.

main.cpp full

#include <Arduino.h>
#include <WiFi.h>
#include <libssh_esp32.h>
#include "libssh_esp32_config.h"
#include <libssh/libssh.h>
#include <vector>
#include <string>

// WiFi Credentials
const char *ssid = "ssid-vasi-wifi";
const char *password = "vase-nejtajnejsi-heslo";

// SSH Credentials
const int ssh_port = 22;
const char *ssh_username = "pi";
const char *ssh_password = "super-tajne-helso";
const char *ssh_command = "ls -l";

// Server IPs List
std::vector<std::string> server_ips = {
    "192.168.1.254",
};

/**
 * Connect to WiFi
 */
void connectToWiFi()
{
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED)
    {
        delay(1000);
        Serial.println("Connecting to WiFi..");
    }
    Serial.print("Připojeno k AP s MAC: ");
    Serial.println(WiFi.BSSIDstr());
}

/**
 * Execute SSH Command
 *
 * @param ip Server IP
 * @param username SSH Username
 * @param password SSH Password
 * @param command Command to execute
 */
void executeSSHCommand(const char *ip, const char *username, const char *password, const char *command)
{
    // Create a new SSH session
    ssh_session session = ssh_new();
    if (session == NULL)
    {
        Serial.println("Failed to create SSH session");
        return;
    }

    // Set SSH options for the session
    ssh_options_set(session, SSH_OPTIONS_HOST, ip);
    ssh_options_set(session, SSH_OPTIONS_USER, username);

    // Connect to SSH server
    int rc = ssh_connect(session);
    if (rc != SSH_OK)
    {
        Serial.println("Failed to connect to SSH server");
        return;
    }

    // Verify the server's identity
    rc = ssh_userauth_password(session, NULL, password);
    if (rc != SSH_AUTH_SUCCESS)
    {
        Serial.println("Failed to authenticate");
        return;
    }

    // Create a new SSH channel
    ssh_channel channel = ssh_channel_new(session);
    if (channel == NULL)
    {
        Serial.println("Failed to create SSH channel");
        return;
    }

    // Open a new SSH session
    rc = ssh_channel_open_session(channel);
    if (rc != SSH_OK)
    {
        Serial.println("Failed to open SSH session");
        return;
    }

    // Execute the command
    rc = ssh_channel_request_exec(channel, command);
    if (rc != SSH_OK)
    {
        Serial.println("Failed to execute command");
        return;
    }

    // Read the output of the command
    char buffer[256];
    int nbytes; // Number of bytes read
    nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
    while (nbytes > 0)
    {
        if (write(1, buffer, nbytes) != nbytes) // Write to stdout
        {
            Serial.println("Failed to write to stdout");
            return;
        }
        nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
    }

    // Check if there was an error reading from the channel
    if (nbytes < 0)
    {
        Serial.println("Failed to read from SSH channel");
        return;
    }

    // Send EOF and close the channel
    ssh_channel_send_eof(channel);

    // Close the channel
    ssh_channel_close(channel);

    // Free the channel
    ssh_channel_free(channel);

    // Disconnect the session
    ssh_disconnect(session);

    // Free the session
    ssh_free(session);
}

/**
 * Execute SSH Command on all servers
 *
 * @param username SSH Username
 * @param password SSH Password
 * @param command Command to execute
 */
void executeSSHCommandOnAllServers(const char *username, const char *password, const char *command)
{
    for (std::string ip : server_ips)
    {
        executeSSHCommand(ip.c_str(), username, password, command);
    }
}

void setup()
{
    Serial.begin(115200);
    while (!Serial)
    {
        ; // Wait for serial to be ready
    }

    connectToWiFi();
    ssh_init();
    executeSSHCommandOnAllServers(ssh_username, ssh_password, ssh_command);
}

void loop()
{
    delay(1000); // Delay for 1 second
}

Loading