feat: introduce Tunnel wrapping VpnProtocol with two-phase lifecycle

This commit is contained in:
cd-amn
2026-05-18 16:47:54 +00:00
parent f2ff8a7b3b
commit 9d69ab89d5
7 changed files with 257 additions and 16 deletions
+2
View File
@@ -66,6 +66,7 @@ set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/core/utils/managementServer.h ${CLIENT_ROOT_DIR}/core/utils/managementServer.h
${CLIENT_ROOT_DIR}/core/utils/constants.h ${CLIENT_ROOT_DIR}/core/utils/constants.h
${CLIENT_ROOT_DIR}/core/vpnTrafficGuard.h ${CLIENT_ROOT_DIR}/core/vpnTrafficGuard.h
${CLIENT_ROOT_DIR}/core/tunnel.h
) )
# Mozilla headres # Mozilla headres
@@ -147,6 +148,7 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/core/utils/utilities.cpp ${CLIENT_ROOT_DIR}/core/utils/utilities.cpp
${CLIENT_ROOT_DIR}/core/utils/managementServer.cpp ${CLIENT_ROOT_DIR}/core/utils/managementServer.cpp
${CLIENT_ROOT_DIR}/core/vpnTrafficGuard.cpp ${CLIENT_ROOT_DIR}/core/vpnTrafficGuard.cpp
${CLIENT_ROOT_DIR}/core/tunnel.cpp
) )
# Mozilla sources # Mozilla sources
+8
View File
@@ -106,6 +106,13 @@ QString VpnProtocol::vpnLocalAddress() const
return m_vpnLocalAddress; return m_vpnLocalAddress;
} }
bool VpnProtocol::isWireGuardBased(amnezia::DockerContainer container)
{
return container == amnezia::DockerContainer::Awg
|| container == amnezia::DockerContainer::Awg2
|| container == amnezia::DockerContainer::WireGuard;
}
VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &configuration) VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &configuration)
{ {
switch (container) { switch (container) {
@@ -137,6 +144,7 @@ QString VpnProtocol::textConnectionState(Vpn::ConnectionState connectionState)
case Vpn::ConnectionState::Preparing: return tr("Preparing"); case Vpn::ConnectionState::Preparing: return tr("Preparing");
case Vpn::ConnectionState::Connecting: return tr("Connecting..."); case Vpn::ConnectionState::Connecting: return tr("Connecting...");
case Vpn::ConnectionState::Connected: return tr("Connected"); case Vpn::ConnectionState::Connected: return tr("Connected");
case Vpn::ConnectionState::Switching: return tr("Switching...");
case Vpn::ConnectionState::Disconnecting: return tr("Disconnecting..."); case Vpn::ConnectionState::Disconnecting: return tr("Disconnecting...");
case Vpn::ConnectionState::Reconnecting: return tr("Reconnecting..."); case Vpn::ConnectionState::Reconnecting: return tr("Reconnecting...");
case Vpn::ConnectionState::Error: return tr("Error"); case Vpn::ConnectionState::Error: return tr("Error");
+5
View File
@@ -27,6 +27,7 @@ namespace Vpn
Preparing, Preparing,
Connecting, Connecting,
Connected, Connected,
Switching,
Disconnecting, Disconnecting,
Reconnecting, Reconnecting,
Error Error
@@ -60,6 +61,7 @@ public:
virtual bool isDisconnected() const; virtual bool isDisconnected() const;
virtual ErrorCode start() = 0; virtual ErrorCode start() = 0;
virtual void stop() = 0; virtual void stop() = 0;
virtual void setPrimary(const QJsonObject& config) { Q_UNUSED(config) }
Vpn::ConnectionState connectionState() const; Vpn::ConnectionState connectionState() const;
ErrorCode lastError() const; ErrorCode lastError() const;
@@ -71,6 +73,7 @@ public:
QString vpnLocalAddress() const; QString vpnLocalAddress() const;
static VpnProtocol* factory(amnezia::DockerContainer container, const QJsonObject &configuration); static VpnProtocol* factory(amnezia::DockerContainer container, const QJsonObject &configuration);
static bool isWireGuardBased(amnezia::DockerContainer container);
signals: signals:
void bytesChanged(quint64 receivedBytes, quint64 sentBytes); void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
@@ -78,6 +81,8 @@ signals:
void timeoutTimerEvent(); void timeoutTimerEvent();
void protocolError(amnezia::ErrorCode e); void protocolError(amnezia::ErrorCode e);
void tunnelAddressesUpdated(const QString& gateway, const QString& localAddress); void tunnelAddressesUpdated(const QString& gateway, const QString& localAddress);
void primaryReady();
void primaryFailed();
public slots: public slots:
virtual void onTimeout(); // todo: remove? virtual void onTimeout(); // todo: remove?
+13 -13
View File
@@ -15,7 +15,8 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
const QString ifname = configuration.value("ifname").toString(); const QString ifname = configuration.value("ifname").toString();
m_impl.reset(new LocalSocketController(ifname)); m_impl.reset(new LocalSocketController(ifname));
connect(m_impl.get(), &ControllerImpl::connected, this, connect(m_impl.get(), &ControllerImpl::connected, this,
[this](const QString &pubkey, const QDateTime &connectionTimestamp) { [this](const QString& pubkey, const QDateTime&) {
Q_UNUSED(pubkey)
setConnectionState(Vpn::ConnectionState::Connected); setConnectionState(Vpn::ConnectionState::Connected);
}); });
connect(m_impl.get(), &ControllerImpl::statusUpdated, this, connect(m_impl.get(), &ControllerImpl::statusUpdated, this,
@@ -42,6 +43,10 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
connect(m_impl.get(), &ControllerImpl::disconnected, this, connect(m_impl.get(), &ControllerImpl::disconnected, this,
[this]() { setConnectionState(Vpn::ConnectionState::Disconnected); }); [this]() { setConnectionState(Vpn::ConnectionState::Disconnected); });
connect(m_impl.get(), &ControllerImpl::primaryReady,
this, &WireguardProtocol::primaryReady);
connect(m_impl.get(), &ControllerImpl::primaryFailed,
this, &WireguardProtocol::primaryFailed);
m_impl->initialize(nullptr, nullptr); m_impl->initialize(nullptr, nullptr);
} }
@@ -51,13 +56,7 @@ WireguardProtocol::~WireguardProtocol()
QThread::msleep(200); QThread::msleep(200);
} }
void WireguardProtocol::stop() ErrorCode WireguardProtocol::start()
{
stopMzImpl();
return;
}
ErrorCode WireguardProtocol::startMzImpl()
{ {
QString protocolName = m_rawConfig.value("protocol").toString(); QString protocolName = m_rawConfig.value("protocol").toString();
QJsonObject vpnConfigData = m_rawConfig.value(protocolName + "_config_data").toObject(); QJsonObject vpnConfigData = m_rawConfig.value(protocolName + "_config_data").toObject();
@@ -65,18 +64,19 @@ ErrorCode WireguardProtocol::startMzImpl()
m_rawConfig.insert(protocolName + "_config_data", vpnConfigData); m_rawConfig.insert(protocolName + "_config_data", vpnConfigData);
m_rawConfig[configKey::hostName] = NetworkUtilities::getIPAddress(m_rawConfig[configKey::hostName].toString()); m_rawConfig[configKey::hostName] = NetworkUtilities::getIPAddress(m_rawConfig[configKey::hostName].toString());
m_stopped = false;
m_impl->activate(m_rawConfig); m_impl->activate(m_rawConfig);
return ErrorCode::NoError; return ErrorCode::NoError;
} }
ErrorCode WireguardProtocol::stopMzImpl() void WireguardProtocol::stop()
{ {
if (m_stopped) return;
m_stopped = true;
m_impl->deactivate(); m_impl->deactivate();
return ErrorCode::NoError;
} }
void WireguardProtocol::setPrimary(const QJsonObject& config)
ErrorCode WireguardProtocol::start()
{ {
return startMzImpl(); m_impl->setPrimary(config);
} }
+2 -3
View File
@@ -21,11 +21,10 @@ public:
ErrorCode start() override; ErrorCode start() override;
void stop() override; void stop() override;
void setPrimary(const QJsonObject& config) override;
ErrorCode startMzImpl();
ErrorCode stopMzImpl();
private: private:
bool m_stopped = false;
QScopedPointer<ControllerImpl> m_impl; QScopedPointer<ControllerImpl> m_impl;
}; };
+150
View File
@@ -0,0 +1,150 @@
#include "tunnel.h"
#include <QTimer>
#include "daemon/interfaceconfig.h"
Tunnel::Tunnel(QString ifname,
amnezia::DockerContainer container,
QJsonObject config,
QString remoteAddress,
QObject* parent)
: QObject(parent),
m_ifname(std::move(ifname)),
m_remoteAddress(std::move(remoteAddress)),
m_container(container),
m_config(std::move(config)) {}
Tunnel::~Tunnel() = default;
void Tunnel::prepare() {
if (m_state != State::Idle) {
return;
}
setState(State::Preparing);
m_config.insert("ifname", m_ifname);
m_protocol.reset(VpnProtocol::factory(m_container, m_config));
if (!m_protocol) {
setState(State::Failed);
emit failed(amnezia::ErrorCode::InternalError);
return;
}
connect(m_protocol.data(), &VpnProtocol::connectionStateChanged,
this, &Tunnel::onProtocolStateChanged);
connect(m_protocol.data(), &VpnProtocol::bytesChanged,
this, &Tunnel::bytesChanged);
connect(m_protocol.data(), &VpnProtocol::tunnelAddressesUpdated,
this, &Tunnel::addressesUpdated);
connect(m_protocol.data(), &VpnProtocol::primaryReady,
this, &Tunnel::onPrimaryReady);
connect(m_protocol.data(), &VpnProtocol::primaryFailed,
this, &Tunnel::onPrimaryFailed);
startActivationDeadline(ACTIVATION_TIMEOUT_MSEC);
const amnezia::ErrorCode err = m_protocol->start();
if (err != amnezia::ErrorCode::NoError) {
cancelActivationDeadline();
setState(State::Failed);
emit failed(err);
}
}
void Tunnel::commit() {
if (m_state != State::Prepared) {
return;
}
setState(State::Committing);
startActivationDeadline(ACTIVATION_TIMEOUT_MSEC);
if (m_protocol) {
m_protocol->setPrimary(m_config);
}
}
void Tunnel::onPrimaryReady() {
if (m_state != State::Committing) {
return;
}
cancelActivationDeadline();
setState(State::Active);
emit activated();
}
void Tunnel::onPrimaryFailed() {
if (m_state != State::Committing) {
return;
}
cancelActivationDeadline();
setState(State::Failed);
emit failed(m_protocol ? m_protocol->lastError() : amnezia::ErrorCode::InternalError);
}
void Tunnel::deactivate() {
if (m_state == State::Gone || m_state == State::Idle) {
return;
}
cancelActivationDeadline();
setState(State::Gone);
if (m_protocol) {
m_protocol->stop();
}
}
void Tunnel::restart() {
deactivate();
setState(State::Idle);
prepare();
}
void Tunnel::setState(State next) {
if (m_state == next) {
return;
}
m_state = next;
emit stateChanged(m_state);
}
void Tunnel::startActivationDeadline(int msec) {
if (!m_deadline) {
m_deadline = new QTimer(this);
m_deadline->setSingleShot(true);
connect(m_deadline, &QTimer::timeout, this, [this]() {
if (m_state != State::Preparing && m_state != State::Committing) {
return;
}
setState(State::Failed);
emit failed(amnezia::ErrorCode::InternalError);
});
}
m_deadline->start(msec);
}
void Tunnel::cancelActivationDeadline() {
if (m_deadline) {
m_deadline->stop();
}
}
void Tunnel::onProtocolStateChanged(Vpn::ConnectionState state) {
if (m_state == State::Preparing && state == Vpn::ConnectionState::Connected) {
cancelActivationDeadline();
setState(State::Prepared);
emit prepared();
return;
}
const bool inLiveState = m_state == State::Preparing
|| m_state == State::Prepared
|| m_state == State::Committing
|| m_state == State::Active;
const bool isFailureSignal = state == Vpn::ConnectionState::Disconnected
|| state == Vpn::ConnectionState::Error;
if (inLiveState && isFailureSignal) {
cancelActivationDeadline();
setState(State::Failed);
emit failed(m_protocol ? m_protocol->lastError() : amnezia::ErrorCode::InternalError);
}
}
+77
View File
@@ -0,0 +1,77 @@
#ifndef TUNNEL_H
#define TUNNEL_H
#include <QJsonObject>
#include <QObject>
#include <QSharedPointer>
#include <QString>
#include "core/protocols/vpnProtocol.h"
#include "core/utils/containerEnum.h"
#include "core/utils/errorCodes.h"
class QTimer;
class Tunnel : public QObject {
Q_OBJECT
public:
enum class State {
Idle,
Preparing,
Prepared,
Committing,
Active,
Gone,
Failed,
};
Q_ENUM(State)
Tunnel(QString ifname,
amnezia::DockerContainer container,
QJsonObject config,
QString remoteAddress,
QObject* parent = nullptr);
~Tunnel() override;
const QString& ifname() const { return m_ifname; }
const QString& remoteAddress() const { return m_remoteAddress; }
amnezia::DockerContainer container() const { return m_container; }
const QJsonObject& config() const { return m_config; }
State state() const { return m_state; }
QSharedPointer<VpnProtocol> protocol() const { return m_protocol; }
virtual void prepare();
virtual void commit();
virtual void deactivate();
virtual void restart();
signals:
void prepared();
void activated();
void failed(amnezia::ErrorCode);
void stateChanged(Tunnel::State);
void bytesChanged(quint64 rxBytes, quint64 txBytes);
void addressesUpdated(const QString& gateway, const QString& localAddress);
protected:
void setState(State);
void startActivationDeadline(int msec);
void cancelActivationDeadline();
QString m_ifname;
QString m_remoteAddress;
amnezia::DockerContainer m_container;
QJsonObject m_config;
QSharedPointer<VpnProtocol> m_protocol;
private:
void onProtocolStateChanged(Vpn::ConnectionState state);
void onPrimaryReady();
void onPrimaryFailed();
State m_state = State::Idle;
QTimer* m_deadline = nullptr;
};
#endif // TUNNEL_H