mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-20 02:00:55 +07:00
feat: introduce Tunnel wrapping VpnProtocol with two-phase lifecycle
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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?
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user