Skip to content

Commit

Permalink
rewrite network_access_manager and lxd backend to use POCO
Browse files Browse the repository at this point in the history
  • Loading branch information
levkropp committed Oct 3, 2024
1 parent 96cbc2a commit af32cbe
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 170 deletions.
15 changes: 10 additions & 5 deletions include/multipass/cli/command.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
#include <QString>

#include <grpc++/grpc++.h>
#include <Poco/Net/SocketAddress.h>
#include <Poco/Net/StreamSocket.h>

namespace multipass
{
Expand Down Expand Up @@ -102,14 +104,17 @@ class Command : private DisabledCopyMove
if (tokens[0] == "unix")
{
socket_address = tokens[1];
QLocalSocket multipassd_socket;
multipassd_socket.connectToServer(QString::fromStdString(socket_address));
if (!multipassd_socket.waitForConnected() &&
multipassd_socket.error() == QLocalSocket::SocketAccessError)
try
{
Poco::Net::SocketAddress local_address(socket_address);
Poco::Net::StreamSocket socket;
socket.connect(local_address, Poco::Timespan(5, 0)); // 5 seconds timeout
}
catch (const Poco::Exception& e)
{
grpc::Status denied_status{
grpc::StatusCode::PERMISSION_DENIED, "multipass socket access denied",
fmt::format("Please check that you have read/write permissions to '{}'", socket_address)};
fmt::format("Please check that you have read/write permissions to '{}': {}", socket_address, e.displayText())};
return handle_failure(denied_status);
}
}
Expand Down
37 changes: 29 additions & 8 deletions include/multipass/network_access_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,47 @@
#ifndef MULTIPASS_NETWORK_ACCESS_MANAGER_H
#define MULTIPASS_NETWORK_ACCESS_MANAGER_H

#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <Poco/Net/HTTPClientSession.h>
#include <Poco/Net/HTTPRequest.h>
#include <Poco/Net/HTTPResponse.h>
#include <Poco/Net/StreamSocket.h>
#include <Poco/Net/HTMLForm.h>
#include <Poco/Net/PartSource.h>

#include <QString>
#include <QUrl>
#include <QByteArray>
#include <map>
#include <vector>
#include <memory>

namespace multipass
{

class NetworkAccessManager : public QNetworkAccessManager
class NetworkAccessManager
{
Q_OBJECT
public:
using UPtr = std::unique_ptr<NetworkAccessManager>;
NetworkAccessManager();
~NetworkAccessManager();

NetworkAccessManager(QObject* parent = nullptr);
QByteArray sendRequest(const QUrl& url, const std::string& method, const QByteArray& data = QByteArray(),
const std::map<std::string, std::string>& headers = {});

protected:
QNetworkReply* createRequest(Operation op, const QNetworkRequest& orig_request,
QIODevice* outgoingData = nullptr) override;
// New method for multipart requests
QByteArray sendMultipartRequest(const QUrl& url, const std::string& method,
const std::vector<std::pair<std::string, Poco::Net::PartSource*>>& parts,
const std::map<std::string, std::string>& headers = {});

private:
QByteArray sendUnixRequest(const QUrl& url, const std::string& method, const QByteArray& data,
const std::map<std::string, std::string>& headers);

QByteArray sendUnixMultipartRequest(const QUrl& url, const std::string& method,
const std::vector<std::pair<std::string, Poco::Net::PartSource*>>& parts,
const std::map<std::string, std::string>& headers);
};

} // namespace multipass

#endif // MULTIPASS_NETWORK_ACCESS_MANAGER_H
191 changes: 160 additions & 31 deletions src/network/network_access_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,60 +15,189 @@
*
*/

#include "local_socket_reply.h"

#include <multipass/exceptions/local_socket_connection_exception.h>
#include <multipass/format.h>
#include <multipass/network_access_manager.h>
#include <Poco/Net/SocketAddress.h>
#include <Poco/Exception.h>
#include <QUrl>
#include <QByteArray>

namespace mp = multipass;

mp::NetworkAccessManager::NetworkAccessManager(QObject* parent) : QNetworkAccessManager(parent)
mp::NetworkAccessManager::NetworkAccessManager()
{
}

mp::NetworkAccessManager::~NetworkAccessManager()
{
}

QNetworkReply* mp::NetworkAccessManager::createRequest(QNetworkAccessManager::Operation operation,
const QNetworkRequest& orig_request, QIODevice* device)
QByteArray mp::NetworkAccessManager::sendRequest(const QUrl& url, const std::string& method, const QByteArray& data,
const std::map<std::string, std::string>& headers)
{
auto scheme = orig_request.url().scheme();
auto scheme = url.scheme();

// To support http requests over Unix sockets, the initial URL needs to be in the form of:
// unix:///path/to/unix_socket@path/in/server (or 'local' instead of 'unix')
//
// For example, to get the general LXD configuration when LXD is installed as a snap:
// unix:////var/snap/lxd/common/lxd/[email protected]
if (scheme == "unix" || scheme == "local")
{
const auto url_parts = orig_request.url().toString().split('@');
if (url_parts.count() != 2)
return sendUnixRequest(url, method, data, headers);
}
else
{
throw std::runtime_error("Only UNIX socket requests are supported");
}
}

QByteArray mp::NetworkAccessManager::sendMultipartRequest(const QUrl& url, const std::string& method,
const std::vector<std::pair<std::string, Poco::Net::PartSource*>>& parts,
const std::map<std::string, std::string>& headers)
{
auto scheme = url.scheme();

if (scheme == "unix" || scheme == "local")
{
return sendUnixMultipartRequest(url, method, parts, headers);
}
else
{
throw std::runtime_error("Only UNIX socket requests are supported");
}
}

QByteArray mp::NetworkAccessManager::sendUnixRequest(const QUrl& url, const std::string& method, const QByteArray& data,
const std::map<std::string, std::string>& headers)
{
// Parse the URL to get the socket path and the request path
auto url_str = url.toString();
auto url_parts = url_str.split('@');
if (url_parts.count() != 2)
{
throw std::runtime_error("The local socket scheme is malformed.");
}

auto socket_path = QUrl(url_parts[0]).path().toStdString();
auto request_path = url_parts[1].toStdString();

try
{
// Create a local stream socket and connect to the UNIX socket
Poco::Net::SocketAddress local_address(socket_path);
Poco::Net::StreamSocket local_socket;
local_socket.connect(local_address);

// Create an HTTP client session with the local socket
Poco::Net::HTTPClientSession session(local_socket);

// Create the request
Poco::Net::HTTPRequest request(method, "/" + request_path, Poco::Net::HTTPMessage::HTTP_1_1);
if (!data.isEmpty())
request.setContentLength(data.size());

// Set headers
for (const auto& header : headers)
{
throw LocalSocketConnectionException("The local socket scheme is malformed.");
request.set(header.first, header.second);
}

const auto socket_path = QUrl(url_parts[0]).path();
// Send the request
std::ostream& os = session.sendRequest(request);
if (!data.isEmpty())
{
os.write(data.constData(), data.size());
}

LocalSocketUPtr local_socket = std::make_unique<QLocalSocket>();
// Receive the response
Poco::Net::HTTPResponse response;
std::istream& rs = session.receiveResponse(response);

local_socket->connectToServer(socket_path);
if (!local_socket->waitForConnected(5000))
// Read the response data
QByteArray response_data;
char buffer[1024];
while (rs.read(buffer, sizeof(buffer)))
{
response_data.append(buffer, rs.gcount());
}
// Read any remaining bytes
if (rs.gcount() > 0)
{
throw LocalSocketConnectionException(
fmt::format("Cannot connect to {}: {}", socket_path, local_socket->errorString()));
response_data.append(buffer, rs.gcount());
}

const auto server_path = url_parts[1];
QNetworkRequest request{orig_request};
return response_data;
}
catch (Poco::Exception& ex)
{
throw std::runtime_error("Failed to communicate over UNIX socket: " + ex.displayText());
}
}

QUrl url(QString("/%1").arg(server_path));
url.setHost(orig_request.url().host());
QByteArray mp::NetworkAccessManager::sendUnixMultipartRequest(const QUrl& url, const std::string& method,
const std::vector<std::pair<std::string, Poco::Net::PartSource*>>& parts,
const std::map<std::string, std::string>& headers)
{
// Parse the URL to get the socket path and the request path
auto url_str = url.toString();
auto url_parts = url_str.split('@');
if (url_parts.count() != 2)
{
throw std::runtime_error("The local socket scheme is malformed.");
}

request.setUrl(url);
auto socket_path = QUrl(url_parts[0]).path().toStdString();
auto request_path = url_parts[1].toStdString();

try
{
// Create a local stream socket and connect to the UNIX socket
Poco::Net::SocketAddress local_address(socket_path);
Poco::Net::StreamSocket local_socket;
local_socket.connect(local_address);

// The caller needs to be responsible for freeing the allocated memory
return new LocalSocketReply(std::move(local_socket), request, device);
// Create an HTTP client session with the local socket
Poco::Net::HTTPClientSession session(local_socket);

// Create the request
Poco::Net::HTTPRequest request(method, "/" + request_path, Poco::Net::HTTPMessage::HTTP_1_1);

// Set headers
for (const auto& header : headers)
{
request.set(header.first, header.second);
}

// Create the HTMLForm and add parts
Poco::Net::HTMLForm form(Poco::Net::HTMLForm::ENCODING_MULTIPART);
for (const auto& part : parts)
{
form.addPart(part.first, part.second); // HTMLForm takes ownership of PartSource*
}

// Prepare the request with the form
form.prepareSubmit(request);

// Send the request
std::ostream& os = session.sendRequest(request);
form.write(os);

// Receive the response
Poco::Net::HTTPResponse response;
std::istream& rs = session.receiveResponse(response);

// Read the response data
QByteArray response_data;
char buffer[1024];
while (rs.read(buffer, sizeof(buffer)))
{
response_data.append(buffer, rs.gcount());
}
// Read any remaining bytes
if (rs.gcount() > 0)
{
response_data.append(buffer, rs.gcount());
}

return response_data;
}
else
catch (Poco::Exception& ex)
{
return QNetworkAccessManager::createRequest(operation, orig_request, device);
throw std::runtime_error("Failed to communicate over UNIX socket: " + ex.displayText());
}
}
Loading

0 comments on commit af32cbe

Please sign in to comment.