Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
437136e
[ssh] Adopt ssh_callbacks_init macro in MP_LIBSSH
tobe2098 Jul 22, 2026
fbdda98
[libssh] Rename function to verb
tobe2098 Jul 24, 2026
ee0643e
[ssh] Add ssh_callbacks_init to MP_LIBSSH (#5098)
ricab Jul 24, 2026
29b48ee
[tests] Fix mock-method name skew
ricab Jul 24, 2026
14b8204
[sftp] Add an empty SftpServerSession
ricab Jun 13, 2026
e0969bf
[sftp] Add SftpServerSession factory to session
ricab Jul 6, 2026
359ed3a
[ssh] Add borrow_channel to PlainSSHProcess
ricab Jun 19, 2026
4077ddd
[ssh] Reorder methods slightly
ricab Jun 22, 2026
cc12b44
[sftp] Add PlainSftpServerSession skeleton
ricab Jun 23, 2026
f62368f
[ssh] Make concrete sftp session
ricab Jun 23, 2026
aa1b4ac
[sftp] Adjust interface constructor visibility
ricab Jun 23, 2026
01b5d24
[ssh] Adjust interface ctor visibility
ricab Jun 23, 2026
da9c5e5
[ssh] Consume an SSHSession to make an SFTP one
ricab Jul 6, 2026
2e7f200
[cosmetic] Tweak namespace qualifications
ricab Jun 23, 2026
48757e6
[ssh] Add a method to borrow the libssh session
ricab Jun 24, 2026
d212d92
[ssh] Prevent PlainSSHSession chopping with final
ricab Jun 24, 2026
5fa0e67
[sftp] Prevent copy/move of the sftp session
ricab Jun 24, 2026
dc24cbb
[sftp] Borrow ssh session to create sftp session
ricab Jul 6, 2026
bbbdd99
[sftp] Copy and use sftp server init code
ricab Jun 24, 2026
25b5d96
[sftp] Convert helper to private static
ricab Jun 24, 2026
c810319
[sftp] Rename field to avoid shadowing
ricab Jun 24, 2026
a5aeb03
[sftp] Create sshfs process and borrow channel
ricab Jun 25, 2026
982d0af
[ssh] Add an exec_plain version to PlainSSHSession
ricab Jun 25, 2026
b7bc460
[sftp] Remove "Server" from "SftpSession" types
ricab Jun 25, 2026
88a4525
[ssh] Avoid shadowing libssh types
ricab Jun 25, 2026
76961e4
[sftp] Parameterize sftp session with sshfs cmd
ricab Jun 25, 2026
9d2954d
[sftp] Handle failure to create raw sftp_server
ricab Jul 7, 2026
39e77db
[sftp] Add TODO to use SftpMessage and avoid leak
ricab Jul 7, 2026
b1c78dc
[cmake] Avoid leaking libssh-specific definition
ricab Jul 21, 2026
f1a57c4
[ssh] Clarify not-moved pre-condition to borrow
ricab Jul 8, 2026
e598cc4
[sftp] Fix format post rebase
ricab Jul 21, 2026
f4a4471
[sftp] Add some TODOs to go through MP_LIBSSH
ricab Jul 21, 2026
55bc87e
[tests] Test PlainSSHSession::exec_plain
ricab Jul 24, 2026
4641aa5
[tests] Add a couple more tests for exec_plain
ricab Jul 24, 2026
a0b9ba3
[tests] Verify PlainSSHSession moving
ricab Jul 24, 2026
1847386
[tests] Check SftpSessions' type traits
ricab Jul 24, 2026
162fa66
[tests] Check SSHSession consumed to make Sftp one
ricab Jul 24, 2026
6359c04
[tests] Rename mock libssh
ricab Jul 24, 2026
2e63a7a
[tests] Test SftpSession runs sshfs command
ricab Jul 24, 2026
45b2e85
[tests] Test Sftp session throws sshfs error
ricab Jul 24, 2026
3335dcc
[tests] Test consumed session released once
ricab Jul 24, 2026
1f6779b
[tests] Fix mock-method name skew (#5104)
tobe2098 Jul 28, 2026
264877c
[sftp] Add encapsulated SftpSession type (#5029)
ricab Jul 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/multipass/ssh/libssh_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class Libssh : public Singleton<Libssh>
int* core_dumped) const;

// --- channel callbacks ---------------------------------------------------
virtual void ssh_callbacks_initialize(ssh_channel_callbacks callbacks) const;
virtual int ssh_add_channel_callbacks(ssh_channel channel, ssh_channel_callbacks cb) const;
virtual int ssh_remove_channel_callbacks(ssh_channel channel, ssh_channel_callbacks cb) const;

Expand Down
55 changes: 55 additions & 0 deletions include/multipass/ssh/plain_sftp_session.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (C) Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

#pragma once

#include <multipass/private_pass_provider.h>
#include <multipass/ssh/plain_ssh_process.h>
#include <multipass/ssh/plain_ssh_session.h>
#include <multipass/sshfs_mount/sftp_session.h>

#include <libssh/sftp.h> // TODO@sftp avoid this include (need to go through MP_LIBSSH)

namespace multipass
{

/**
* A concrete SftpSession backed by an SSHFS mount: it serves the SFTP protocol over an SSH
* session to a remote sshfs client, which mounts it on the guest.
*/
class PlainSftpSession : public SftpSession, public PrivatePassProvider<PlainSftpSession>
{
public:
PlainSftpSession(PlainSSHSession&& ssh_session_obj, const std::string& sshfs_cmd);
PlainSftpSession(const PlainSftpSession&) = delete;
PlainSftpSession& operator=(const PlainSftpSession&) = delete;

// TODO@sftp Make class final before enabling these
PlainSftpSession(PlainSftpSession&&) = delete;
PlainSftpSession& operator=(PlainSftpSession&&) = delete;

private:
// TODO@sftp avoid mentioning sftp_server_free here (need to go through MP_LIBSSH)
using RawSftpSessionUptr = std::unique_ptr<sftp_session_struct, decltype(sftp_server_free)*>;

static RawSftpSessionUptr make_raw_sftp_session(ssh_session raw_session, ssh_channel channel);

PlainSSHSession plain_ssh_session;
std::unique_ptr<PlainSSHProcess> sshfs_process;
RawSftpSessionUptr raw_sftp_session;
};
} // namespace multipass
14 changes: 12 additions & 2 deletions include/multipass/ssh/plain_ssh_process.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

#include <multipass/ssh/ssh_process.h>

#include <multipass/private_pass_provider.h>

#include <libssh/libssh.h>

#include <exception>
Expand All @@ -28,12 +30,14 @@

namespace multipass
{
class PlainSftpSession;

class PlainSSHProcess : public SSHProcess
{
public:
using ChannelUPtr = std::unique_ptr<ssh_channel_struct, void (*)(ssh_channel)>;

PlainSSHProcess(ssh_session_struct& ssh_session,
PlainSSHProcess(ssh_session_struct& raw_session,
const std::string& cmd,
std::unique_lock<std::mutex> session_lock);

Expand All @@ -56,6 +60,12 @@ class PlainSSHProcess : public SSHProcess
std::string read_std_error() override;
const std::string& get_cmd() const override;

public: // but restricted
// Obtain a non-owning libssh channel handle.
// The caller adopts thread-safety responsibility for the channel with respect to this
// SSHProcess and the SSHSession it belongs to.
ssh_channel borrow_channel(const PrivatePassProvider<PlainSftpSession>::PrivatePass&);

private:
enum class StreamType
{
Expand All @@ -70,7 +80,7 @@ class PlainSSHProcess : public SSHProcess
// ensure thread safety

std::unique_lock<std::mutex> session_lock; // do not attempt to re-lock, as this is moved from
ssh_session session;
ssh_session raw_session;
std::string cmd;
ChannelUPtr channel;
std::variant<std::monostate, int, std::exception_ptr> exit_result;
Expand Down
33 changes: 29 additions & 4 deletions include/multipass/ssh/plain_ssh_session.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@

#pragma once

#include <multipass/ssh/plain_ssh_process.h>
#include <multipass/ssh/ssh_session.h>

#include <multipass/private_pass_provider.h>

#include <libssh/libssh.h>

#include <memory>
Expand All @@ -29,7 +30,10 @@
namespace multipass
{
class SSHKeyProvider;
class PlainSSHSession : public SSHSession
class PlainSftpSession;
class PlainSSHProcess;

class PlainSSHSession final : public SSHSession // final to prevent chopping on move
{
public:
PlainSSHSession(const std::string& host,
Expand All @@ -49,10 +53,21 @@ class PlainSSHSession : public SSHSession

/**
* @copydoc SSHSession::exec
*
* The dynamic type is always a PlainSSHProcess; see exec_plain to obtain it statically.
*/
[[nodiscard]] std::unique_ptr<SSHProcess> exec(const std::string& cmd,
bool whisper = false) override;

/**
* TODO@sftp can we copydoc? partially
* Like exec, but statically typed to the concrete PlainSSHProcess this session produces.
*/
[[nodiscard]] std::unique_ptr<PlainSSHProcess> exec_plain(const std::string& cmd,
bool whisper = false);

std::unique_ptr<SftpSession> make_sftp_session(const std::string& sshfs_cmd) && override;

/**
* @copydoc SSHSession::is_connected
*/
Expand All @@ -63,15 +78,25 @@ class PlainSSHSession : public SSHSession
*/
[[nodiscard]] bool is_moved() const override;

operator ssh_session() override;
operator ssh_session() override; // TODO@sftp remove
void force_shutdown() override; // TODO@sftp this should not be public

public: // but restricted
/**
* Obtain a non-owning libssh session handle.
* The caller adopts thread-safety responsibility for the underlying session with respect to
* this SSHSession
*
* @pre !this->is_moved()
*/
ssh_session borrow_session(const PrivatePassProvider<PlainSftpSession>::PrivatePass&) const;

private:
PlainSSHSession(PlainSSHSession&&, std::unique_lock<std::mutex> lock);

void set_option(ssh_options_e type, const void* value);

std::unique_ptr<ssh_session_struct, void (*)(ssh_session)> session;
std::unique_ptr<ssh_session_struct, void (*)(ssh_session)> raw_session;
mutable std::mutex mut;
};
} // namespace multipass
4 changes: 2 additions & 2 deletions include/multipass/ssh/sftp_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class SFTPClient
int port,
const std::string& username,
const std::string& priv_key_blob);
SFTPClient(SSHSessionUPtr ssh_session);
SFTPClient(SSHSessionUPtr ssh_session_obj);

virtual bool is_remote_dir(const fs::path& path);
virtual bool push(const fs::path& source_path, const fs::path& target_path, Flags flags = {});
Expand All @@ -69,7 +69,7 @@ class SFTPClient
void do_push_file(std::istream& source, const fs::path& target_path);
void do_pull_file(const fs::path& source_path, std::ostream& target);

SSHSessionUPtr ssh_session;
SSHSessionUPtr ssh_session_obj;
SFTPSessionUPtr sftp;
};

Expand Down
4 changes: 2 additions & 2 deletions include/multipass/ssh/ssh_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class SSHClient
const std::string& username,
const std::string& priv_key_blob,
ConsoleCreator console_creator);
SSHClient(SSHSessionUPtr ssh_session, ConsoleCreator console_creator);
SSHClient(SSHSessionUPtr ssh_session_obj, ConsoleCreator console_creator);

int exec(const std::vector<std::vector<std::string>>& args_list);
int connect();
Expand All @@ -52,7 +52,7 @@ class SSHClient
int exec_string(const std::string& cmd_line);
int get_ssh_exit_code();

SSHSessionUPtr ssh_session;
SSHSessionUPtr ssh_session_obj;
ChannelUPtr channel;
Console::UPtr console;
};
Expand Down
11 changes: 8 additions & 3 deletions include/multipass/ssh/ssh_session.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,17 @@

namespace multipass
{
class SftpSession;

class SSHSession
{
public:
virtual ~SSHSession() = default;

// Non-copyable (but movable by descendants, see below)
SSHSession(const SSHSession&) = delete;
SSHSession& operator=(const SSHSession&) = delete;

/**
* Execute a command in this SSH session.
*
Expand All @@ -49,6 +55,8 @@ class SSHSession
[[nodiscard]] virtual std::unique_ptr<SSHProcess> exec(const std::string& cmd,
bool whisper = false) = 0;

virtual std::unique_ptr<SftpSession> make_sftp_session(const std::string& sshfs_cmd) && = 0;

/**
* @return Whether this object represents a session that is currently connected
*/
Expand All @@ -65,9 +73,6 @@ class SSHSession

protected:
SSHSession() = default;

SSHSession(const SSHSession&) = delete;
SSHSession& operator=(const SSHSession&) = delete;
SSHSession(SSHSession&&) = default;
SSHSession& operator=(SSHSession&&) = default;
};
Expand Down
37 changes: 37 additions & 0 deletions include/multipass/sshfs_mount/sftp_session.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (C) Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

#pragma once

namespace multipass
{
/**
* A server-side SFTP session.
*/
class SftpSession
{
public:
virtual ~SftpSession() = default;

// No copies
SftpSession(const SftpSession&) = delete;
SftpSession& operator=(const SftpSession&) = delete;

protected:
SftpSession() = default;
};
} // namespace multipass
8 changes: 8 additions & 0 deletions src/ssh/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,14 @@ function(add_ssh_target TARGET_NAME LIBSSH_TARGET)
add_library(${TARGET_NAME} STATIC
libssh_scope_guard.cpp
openssh_key_provider.cpp
plain_sftp_session.cpp
plain_ssh_process.cpp
plain_ssh_session.cpp
ssh_client_key_provider.cpp)

# TODO@sftp move remaining bits to MP_LIBSSH, then this should not be necessary
target_compile_definitions(${TARGET_NAME} PRIVATE WITH_SERVER)

target_link_libraries(${TARGET_NAME}
${LIBSSH_TARGET}
fmt::fmt-header-only
Expand All @@ -60,6 +64,8 @@ function(add_sftp_client_target TARGET_NAME LIBSSH_TARGET)
sftp_utils.cpp
plain_ssh_session.cpp)

target_compile_definitions(${TARGET_NAME} PRIVATE WITH_SERVER)

target_link_libraries(${TARGET_NAME}
${LIBSSH_TARGET}
fmt::fmt-header-only
Expand All @@ -78,6 +84,8 @@ function(add_ssh_client_target TARGET_NAME LIBSSH_TARGET)
ssh_client.cpp
plain_ssh_session.cpp)

target_compile_definitions(${TARGET_NAME} PRIVATE WITH_SERVER)

target_link_libraries(${TARGET_NAME}
${LIBSSH_TARGET}
console
Expand Down
5 changes: 5 additions & 0 deletions src/ssh/libssh_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,11 @@ int mp::Libssh::ssh_channel_get_exit_state(ssh_channel channel,
}

// --- channel callbacks ------------------------------------------------------
void mp::Libssh::ssh_callbacks_initialize(ssh_channel_callbacks callbacks) const
{
ssh_callbacks_init(callbacks);
}

int mp::Libssh::ssh_add_channel_callbacks(ssh_channel channel, ssh_channel_callbacks cb) const
{
return ::ssh_add_channel_callbacks(channel, cb);
Expand Down
Loading
Loading