refactor: refactor the application to the mvvm architecture (#2009)

* refactor: move business logic from servers model

* refactor: move containersModel initialization

* refactor: added protocol ui controller and removed settings class from protocols model

* refactor: moved cli management to separate controller

* refactor: moved app split to separate controller

* refactor: moved site split to separate controller

* refactor: moved allowed dns to separate controller

* refactor: moved language logic to separate ui controller

* refactor: removed Settings from devices model

* refactor: moved configs and services api logit to separate core controller

* refactor: added a layer with a repository between the storage and controllers

* refactor: use child parent system instead of smart pointers for controllers and models initialization

* refactor: moved install functions from server controller to install controller

* refactor: install controller refactoring

* chore: renamed exportController to exportUiController

* refactor: separate export controller

* refactor: removed VpnConfigurationsController

* chore: renamed ServerController to SshSession

* refactor: replaced ServerController to SshSession

* chore: moved qml controllers to separate folder

* chore: include fixes

* chore: moved utils from core root to core/utils

* chore: include fixes

* chore: rename core/utils files to camelCase foramt

* chore: include fixes

* chore: moved some utils to api and selfhosted folders

* chore: include fixes

* chore: remove unused file

* chore: moved serialization folder to core/utils

* chore: include fixes

* chore: moved some files from client root to core/utils

* chore: include fixes

* chore: moved ui utils to ui/utils folder

* chore: include fixes

* chore: move utils from root to ui/utils

* chore: include fixes

* chore: moved configurators to core/configurators

* chore: include fixes

* refactor: moved iap logic from ui controller to core

* refactor: moved remaining core logic from ApiConfigsController to SubscriptionController

* chore: rename apiNewsController to apiNewsUiController

* refactor: moved core logic from news ui controller to core

* chore: renamed apiConfigsController to subscriptionUiController

* chore: include fixes

* refactor: merge ApiSettingsController with SubscriptionUiController

* chore: moved ui selfhosted controllers to separate folder

* chore: include fixes

* chore: rename connectionController to connectiomUiController

* refactor: moved core logic from connectionUiController

* chore: rename settingsController to settingsUiController

* refactor: move core logic from settingsUiController

* refactor: moved core controller signal/slot connections to separate class

* fix: newsController fixes after refactoring

* chore: rename model to camelCase

* chore: include fixes

* chore: remove unused code

* chore: move selfhosted core to separate folder

* chore: include fixes

* chore: rename importController to importUiController

* refactor: move core logic from importUiController

* chore: minor fixes

* chore: remove prem v1 migration

* refactor: remove openvpn over cloak and openvpn over shadowsocks

* refactor: removed protocolsForContainer function

* refactor: add core models

* refactor: replace json with c++ structs for server config

* refactor: move getDnsPair to ServerConfigUtils

* feat: add admin selfhosted config export test

* feat: add multi import test

* refactor: use coreController for tests

* feat: add few simple tests

* chore: qrepos in all core controllers

* feat: add test for settings

* refactor: remove repo dependency from configurators

* chore: moved protocols to core folder

* chore: include fixes

* refactor: moved containersDefs, defs, apiDefs, protocolsDefs to different places

* chore: include fixes

* chore: build fixes

* chore: build fixes

* refactor: remove q repo and interface repo

* feat: add test for ui servers model and controller

* chore: renamed to camelCase

* chore: include fixes

* refactor: moved core logic from sites ui controller

* fix: fixed api config processing

* fix: fixed processed server index processing

* refactor: protocol models now use c++ structs instead of json configs

* refactor: servers model now use c++ struct instead of json config

* fix: fixed default server index processing

* fix: fix logs init

* fix: fix secure settings load keys

* chore: build fixes

* fix: fixed clear settings

* fix: fixed restore backup

* fix: sshSession usage

* fix: fixed export functions signatures

* fix: return missing part from buildContainerWorker

* fix: fixed server description on page home

* refactor: add container config helpers functions

* refactor: c++ structs instead of json

* chore: add dns protocol config struct

* refactor: move config utils functions to config structs

* feat: add test for selfhosted server setup

* refactor: separate resources.qrc

* fix: fixed server rename

* chore: return nameOverriddenByUser

* fix: build fixes

* fix: fixed models init

* refactor: cleanup models usage

* fix: fixed models init

* chore: cleanup connections and functions signatures

* chore: cleanup updateModel calls

* feat: added cache to servers repo

* chore: cleanup unused functions

* chore: ssxray processing

* chore: remove transportProtoWithDefault and portWithDefault functions

* chore: removed proto types any and l2tp

* refactor: moved some constants

* fix: fixed native configs export

* refactor: remove json from processConfigWith functions

* fix: fixed processed server index usage

* fix: qml warning fixes

* chore: merge fixes

* chore: update tests

* fix: fixed xray config processing

* fix: fixed split tunneling processing

* chore: rename sites controllers and model

* chore: rename fixes

* chore: minor fixes

* chore: remove ability to load backup from "file with connection settings" button

* fix: fixed api device revoke

* fix: remove full model update when renaming a user

* fix: fixed premium/free server rename

* fix: fixed selfhosted new server install

* fix: fixed updateContainer function

* fix: fixed revoke for external premium configs

* feat: add native configs qr processing

* chore: codestyle fixes

* fix: fixed admin config create

* chore: again remove ability to load backup from "file with connection settings" button

* chore: minor fixes

* fix: fixed variables initialization

* fix: fixed qml imports

* fix: minor fixes

* fix: fix vpnConnection function calls

* feat: add buckup error handling

* fix: fixed admin config revok

* fix: fixed selfhosted awg installation

* fix: ad visability

* feat: add empty check for primary dns

* chore: minor fixes
This commit is contained in:
vkamn
2026-04-30 14:53:03 +08:00
committed by GitHub
parent 2edd7de413
commit 847bb6923b
469 changed files with 25992 additions and 17154 deletions
@@ -0,0 +1,21 @@
#include "androidVpnProtocol.h"
#include "platforms/android/android_controller.h"
AndroidVpnProtocol::AndroidVpnProtocol(const QJsonObject &configuration, QObject* parent)
: VpnProtocol(configuration, parent)
{ }
ErrorCode AndroidVpnProtocol::start()
{
qDebug() << "AndroidVpnProtocol::start()";
return AndroidController::instance()->start(m_rawConfig);
}
void AndroidVpnProtocol::stop()
{
qDebug() << "AndroidVpnProtocol::stop()";
setConnectionState(Vpn::ConnectionState::Disconnecting);
AndroidController::instance()->stop();
}
@@ -0,0 +1,20 @@
#ifndef ANDROID_VPNPROTOCOL_H
#define ANDROID_VPNPROTOCOL_H
#include "vpnProtocol.h"
using namespace amnezia;
class AndroidVpnProtocol : public VpnProtocol
{
Q_OBJECT
public:
explicit AndroidVpnProtocol(const QJsonObject& configuration, QObject* parent = nullptr);
~AndroidVpnProtocol() override = default;
ErrorCode start() override;
void stop() override;
};
#endif // ANDROID_VPNPROTOCOL_H
+10
View File
@@ -0,0 +1,10 @@
#include "awgProtocol.h"
Awg::Awg(const QJsonObject &configuration, QObject *parent)
: WireguardProtocol(configuration, parent)
{
}
Awg::~Awg()
{
}
+17
View File
@@ -0,0 +1,17 @@
#ifndef AWGPROTOCOL_H
#define AWGPROTOCOL_H
#include <QObject>
#include "wireGuardProtocol.h"
class Awg : public WireguardProtocol
{
Q_OBJECT
public:
explicit Awg(const QJsonObject &configuration, QObject *parent = nullptr);
virtual ~Awg() override;
};
#endif // AWGPROTOCOL_H
@@ -0,0 +1,332 @@
#include <QCoreApplication>
#include <QFileInfo>
#include <QProcess>
#include <QThread>
#include <chrono>
#include "ipc.h"
#include "logger.h"
#include "ikev2VpnProtocolWindows.h"
#include "core/utils/utilities.h"
#include "core/protocols/protocolUtils.h"
static Ikev2Protocol* self = nullptr;
static std::mutex rasDialFuncMutex;
extern "C" {
static void WINAPI RasDialFuncCallback(UINT unMsg,
RASCONNSTATE rasconnstate,
DWORD dwError );
}
Ikev2Protocol::Ikev2Protocol(const QJsonObject &configuration, QObject* parent) :
VpnProtocol(configuration, parent)
{
self = this;
readIkev2Configuration(configuration);
}
Ikev2Protocol::~Ikev2Protocol()
{
qDebug() << "IpsecProtocol::~IpsecProtocol()";
Ikev2Protocol::stop();
}
void Ikev2Protocol::stop()
{
setConnectionState(Vpn::ConnectionState::Disconnecting);
{
if (!disconnect_vpn()){
qDebug()<<"We don't disconnect";
setConnectionState(Vpn::ConnectionState::Error);
}
else {
setConnectionState(Vpn::ConnectionState::Disconnected);
}
}
}
void Ikev2Protocol::newConnectionStateEventReceived(UINT unMsg, tagRASCONNSTATE rasconnstate, DWORD dwError)
{
Q_UNUSED(unMsg);
qDebug()<<"Receive the new event "<<static_cast<int>(rasconnstate);
switch (rasconnstate)
{
case RASCS_OpenPort:
//qDebug()<<__FUNCTION__ << __LINE__;
setConnectionState(Vpn::ConnectionState::Preparing);
break;
case RASCS_PortOpened:
//qDebug()<<__FUNCTION__ << __LINE__;
setConnectionState(Vpn::ConnectionState::Preparing);
break;
case RASCS_ConnectDevice:
//qDebug()<<__FUNCTION__ << __LINE__;
setConnectionState(Vpn::ConnectionState::Preparing);
break;
case RASCS_DeviceConnected:
//qDebug()<<__FUNCTION__ << __LINE__;
setConnectionState(Vpn::ConnectionState::Preparing);
break;
case RASCS_AllDevicesConnected:
//qDebug()<<__FUNCTION__ << __LINE__;
setConnectionState(Vpn::ConnectionState::Preparing);
break;
case RASCS_Authenticate:
//qDebug()<<__FUNCTION__ << __LINE__;
setConnectionState(Vpn::ConnectionState::Preparing);
break;
case RASCS_AuthNotify:
//qDebug()<<__FUNCTION__ << __LINE__;
if (dwError != 0) {
qDebug() << "have error" << dwError;
setConnectionState(Vpn::ConnectionState::Disconnected);
} else {
qDebug() << "RASCS_AuthNotify but no error" << dwError;
}
break;
case RASCS_AuthRetry:
//qDebug()<<__FUNCTION__ << __LINE__;
setConnectionState(Vpn::ConnectionState::Preparing);
break;
case RASCS_AuthCallback:
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_AuthChangePassword:
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_AuthProject:
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_AuthLinkSpeed:
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_AuthAck:
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_ReAuthenticate:
//qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_Authenticated:
//qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_PrepareForCallback:
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_WaitForModemReset:
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_WaitForCallback:
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_Projected:
qDebug()<<__FUNCTION__ << __LINE__;
break;
#if (WINVER >= 0x400)
case RASCS_StartAuthentication: // Windows 95 only
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_CallbackComplete: // Windows 95 only
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_LogonNetwork: // Windows 95 only
qDebug()<<__FUNCTION__ << __LINE__;
break;
#endif
case RASCS_SubEntryConnected:
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_SubEntryDisconnected:
qDebug()<<__FUNCTION__ << __LINE__;
break;
//PAUSED STATES:
case RASCS_Interactive:
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_RetryAuthentication:
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_CallbackSetByCaller:
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_PasswordExpired:
setConnectionState(Vpn::ConnectionState::Error);
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_Connected: // = RASCS_DONE:
setConnectionState(Vpn::ConnectionState::Connected);
//qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_Disconnected:
setConnectionState(Vpn::ConnectionState::Disconnected);
//qDebug()<<__FUNCTION__ << __LINE__;
break;
default:
//qDebug()<<__FUNCTION__ << __LINE__;
break;
}
}
void Ikev2Protocol::readIkev2Configuration(const QJsonObject &configuration)
{
m_config = configuration.value(ProtocolUtils::key_proto_config_data(Proto::Ikev2)).toObject();
}
ErrorCode Ikev2Protocol::start()
{
QByteArray cert = QByteArray::fromBase64(m_config[configKey::cert].toString().toUtf8());
setConnectionState(Vpn::ConnectionState::Connecting);
QTemporaryFile * certFile = new QTemporaryFile;
certFile->setAutoRemove(false);
certFile->open();
QString m_filename = certFile->fileName();
certFile->write(cert);
certFile->close();
delete certFile;
{
auto certInstallProcess = IpcClient::CreatePrivilegedProcess();
if (!certInstallProcess) {
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
return ErrorCode::AmneziaServiceConnectionFailed;
}
certInstallProcess->waitForSource();
if (!certInstallProcess->isInitialized()) {
qWarning() << "IpcProcess replica is not connected!";
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
return ErrorCode::AmneziaServiceConnectionFailed;
}
certInstallProcess->setProgram(PermittedProcess::CertUtil);
QStringList arguments({"-f", "-importpfx", "-p", m_config[configKey::password].toString(),
QDir::toNativeSeparators(m_filename), "NoExport"
});
certInstallProcess->setArguments(arguments);
certInstallProcess->start();
}
// /*
{
if ( disconnect_vpn()){
qDebug()<<"VPN was disconnected";
}
if ( delete_vpn_connection (tunnelName())){
qDebug()<<"VPN was deleted";
}
}
{
{
if ( !create_new_vpn(tunnelName(), m_config[configKey::hostName].toString())){
qDebug() <<"Can't create the VPN connect";
}
}
}
{
QProcess adapterConfigProcess;
adapterConfigProcess.setProgram("powershell");
QString arguments = QString("-command \"Set-VpnConnectionIPsecConfiguration\" "
"-ConnectionName '%1' "
"-AuthenticationTransformConstants GCMAES128 "
"-CipherTransformConstants GCMAES128 "
"-EncryptionMethod AES256 "
"-IntegrityCheckMethod SHA256 "
"-PfsGroup PFS2048 "
"-DHGroup Group14 "
"-PassThru -Force\"")
.arg(tunnelName());
adapterConfigProcess.setNativeArguments(arguments);
adapterConfigProcess.start();
adapterConfigProcess.waitForFinished(5000);
}
//*/
{
if (!connect_to_vpn(tunnelName())) {
qDebug()<<"We can't connect to VPN";
}
}
//setConnectionState(Connecting);
return ErrorCode::NoError;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool Ikev2Protocol::create_new_vpn(const QString & vpn_name,
const QString & serv_addr){
if ( RasValidateEntryName(nullptr, vpn_name.toStdWString().c_str()) != ERROR_SUCCESS)
return false;
DWORD size = 0;
::RasGetEntryProperties(nullptr, L"", nullptr, &size, nullptr, nullptr);
LPRASENTRY pras = static_cast<LPRASENTRY>(malloc(size));
memset(pras, 0, size);
pras->dwSize = size;
pras->dwType = RASET_Vpn;
pras->dwRedialCount = 1;
pras->dwRedialPause = 60;
pras->dwfNetProtocols = RASNP_Ip|RASNP_Ipv6;
pras->dwEncryptionType = ET_RequireMax;
wcscpy_s(pras->szLocalPhoneNumber, serv_addr.toStdWString().c_str());
wcscpy_s(pras->szDeviceType, RASDT_Vpn);
pras->dwfOptions = RASEO_RemoteDefaultGateway;
pras->dwfOptions |= RASEO_RequireDataEncryption;
pras->dwfOptions2 |= RASEO2_RequireMachineCertificates;
pras->dwVpnStrategy = VS_Ikev2Only;
const auto nRet = ::RasSetEntryProperties(nullptr, vpn_name.toStdWString().c_str(), pras, pras->dwSize, NULL, 0);
free(pras);
if (nRet == ERROR_SUCCESS)
return true;
return false;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool Ikev2Protocol::delete_vpn_connection(const QString &vpn_name){
if ( RasDeleteEntry(nullptr, vpn_name.toStdWString().c_str()) == ERROR_SUCCESS){
return true;
}
return false;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool Ikev2Protocol::connect_to_vpn(const QString & vpn_name){
RASDIALPARAMS RasDialParams;
memset(&RasDialParams, 0x0, sizeof(RASDIALPARAMS));
RasDialParams.dwSize = sizeof(RASDIALPARAMS);
wcscpy_s(RasDialParams.szEntryName, vpn_name.toStdWString().c_str());
auto ret = RasDial(NULL, NULL, &RasDialParams, 0,
&RasDialFuncCallback,
&hRasConn);
if (ret == ERROR_SUCCESS){
return true;
}
return false;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool Ikev2Protocol::disconnect_vpn(){
if ( hRasConn != nullptr ){
auto ret = RasHangUp(hRasConn);
qDebug() << "RasHangUp " << ret;
if (ret != ERROR_SUCCESS)
return false;
}
QThread::msleep(3000);
return true;
}
void WINAPI RasDialFuncCallback(UINT unMsg,
RASCONNSTATE rasconnstate,
DWORD dwError ){
std::lock_guard<std::mutex> guard(rasDialFuncMutex);
if (self) {
self->newConnectionStateEventReceived(unMsg, rasconnstate, dwError);
}
}
@@ -0,0 +1,69 @@
#ifndef IKEV2_VPN_PROTOCOL_WINDOWS_H
#define IKEV2_VPN_PROTOCOL_WINDOWS_H
#include <QObject>
#include <QProcess>
#include <QString>
#include <QTemporaryFile>
#include <QTimer>
#include "vpnProtocol.h"
#include "core/utils/ipcClient.h"
#include <string>
#include <memory>
#include <atomic>
#include <thread>
#include <condition_variable>
#include <mutex>
#include <stdio.h>
#include <windows.h>
#include <Ras.h>
#include <raserror.h>
#include <shlwapi.h>
#include <wincrypt.h>
#pragma comment(lib, "shlwapi.lib")
#pragma comment(lib, "rasapi32.lib")
#pragma comment(lib, "Crypt32.lib")
class Ikev2Protocol : public VpnProtocol
{
Q_OBJECT
public:
explicit Ikev2Protocol(const QJsonObject& configuration, QObject* parent = nullptr);
virtual ~Ikev2Protocol() override;
ErrorCode start() override;
void stop() override;
static QString tunnelName() { return "AmneziaVPN IKEv2"; }
public:
void newConnectionStateEventReceived(UINT unMsg,
RASCONNSTATE rasconnstate,
DWORD dwError);
private:
void readIkev2Configuration(const QJsonObject &configuration);
private:
QJsonObject m_config;
//RAS functions and parameters
HRASCONN hRasConn{nullptr};
bool create_new_vpn(const QString & vpn_name,
const QString & serv_addr);
bool delete_vpn_connection(const QString &vpn_name);
bool connect_to_vpn(const QString & vpn_name);
bool disconnect_vpn();
};
DWORD CALLBACK rasCallback(UINT msg, RASCONNSTATE rascs, DWORD err);
#endif // IKEV2_VPN_PROTOCOL_WINDOWS_H
+388
View File
@@ -0,0 +1,388 @@
#include <QCoreApplication>
#include <QFileInfo>
#include <QProcess>
#include <QRandomGenerator>
#include <QTcpServer>
#include <QTcpSocket>
#include <QNetworkInterface>
#include "core/utils/networkUtilities.h"
#include "ipc.h"
#include "openVpnProtocol.h"
#include "core/utils/utilities.h"
#include "core/protocols/protocolUtils.h"
#include "version.h"
OpenVpnProtocol::OpenVpnProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent)
{
readOpenVpnConfiguration(configuration);
connect(&m_managementServer, &ManagementServer::readyRead, this,
&OpenVpnProtocol::onReadyReadDataFromManagementServer);
}
OpenVpnProtocol::~OpenVpnProtocol()
{
OpenVpnProtocol::stop();
QThread::msleep(200);
}
QString OpenVpnProtocol::defaultConfigFileName()
{
return defaultConfigPath() + QString("/%1.ovpn").arg(APPLICATION_NAME);
}
QString OpenVpnProtocol::defaultConfigPath()
{
QString p = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/config";
Utils::initializePath(p);
return p;
}
void OpenVpnProtocol::stop()
{
qDebug() << "OpenVpnProtocol::stop()";
setConnectionState(Vpn::ConnectionState::Disconnecting);
// TODO: need refactoring
// sendTermSignal() will even return true while server connected ???
if ((m_connectionState == Vpn::ConnectionState::Preparing) || (m_connectionState == Vpn::ConnectionState::Connecting)
|| (m_connectionState == Vpn::ConnectionState::Connected)
|| (m_connectionState == Vpn::ConnectionState::Reconnecting)) {
if (!sendTermSignal()) {
killOpenVpnProcess();
}
QThread::msleep(10);
m_managementServer.stop();
}
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> reply = iface->disableKillSwitch();
if (!reply.waitForFinished(1000) && !reply.returnValue()) {
qWarning() << "OpenVpnProtocol::stop(): Failed to disable killswitch";
}
});
#endif
setConnectionState(Vpn::ConnectionState::Disconnected);
}
ErrorCode OpenVpnProtocol::prepare()
{
return IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<QStringList> listReply = iface->getTapList();
if (!listReply.waitForFinished(1000)) {
return ErrorCode::InternalError;
}
QStringList list = listReply.returnValue();
if (list.empty()) {
QRemoteObjectPendingReply<bool> installReply = iface->checkAndInstallDriver();
if (!installReply.waitForFinished() || !installReply.returnValue()) {
return ErrorCode::OpenVpnTapAdapterError;
}
}
return ErrorCode::NoError;
}, [] () {
return ErrorCode::AmneziaServiceConnectionFailed;
});
}
void OpenVpnProtocol::killOpenVpnProcess()
{
if (m_openVpnProcess) {
m_openVpnProcess->close();
}
}
void OpenVpnProtocol::readOpenVpnConfiguration(const QJsonObject &configuration)
{
if (configuration.contains(ProtocolUtils::key_proto_config_data(Proto::OpenVpn))) {
m_configData = configuration;
QJsonObject jConfig = configuration.value(ProtocolUtils::key_proto_config_data(Proto::OpenVpn)).toObject();
m_configFile.open();
m_configFile.write(jConfig.value(configKey::config).toString().toUtf8());
m_configFile.close();
m_configFileName = m_configFile.fileName();
qDebug().noquote() << QString("Set config data") << m_configFileName;
}
}
bool OpenVpnProtocol::openVpnProcessIsRunning() const
{
return Utils::processIsRunning("openvpn");
}
void OpenVpnProtocol::disconnectFromManagementServer()
{
m_managementServer.stop();
}
QString OpenVpnProtocol::configPath() const
{
return m_configFileName;
}
void OpenVpnProtocol::sendManagementCommand(const QString &command)
{
QIODevice *device = dynamic_cast<QIODevice *>(m_managementServer.socket().data());
if (device) {
QTextStream stream(device);
stream << command << Qt::endl;
}
}
uint OpenVpnProtocol::selectMgmtPort()
{
for (int i = 0; i < 100; ++i) {
quint32 port = QRandomGenerator::global()->generate();
port = (double)(65000 - 15001) * port / UINT32_MAX + 15001;
QTcpServer s;
bool ok = s.listen(QHostAddress::LocalHost, port);
if (ok)
return port;
}
return m_managementPort;
}
void OpenVpnProtocol::updateRouteGateway(QString line)
{
if (line.contains("net_route_v4_best_gw")) {
QStringList params = line.split(" ");
if (params.size() == 6) {
m_routeGateway = params.at(3);
}
} else {
line = line.split("ROUTE_GATEWAY", Qt::SkipEmptyParts).at(1);
if (!line.contains("/"))
return;
m_routeGateway = line.split("/", Qt::SkipEmptyParts).first();
m_routeGateway.replace(" ", "");
}
qDebug() << "Set VPN route gateway" << m_routeGateway;
}
ErrorCode OpenVpnProtocol::start()
{
OpenVpnProtocol::stop();
if (!QFileInfo::exists(Utils::openVpnExecPath())) {
setLastError(ErrorCode::OpenVpnExecutableMissing);
return lastError();
}
if (!QFileInfo::exists(configPath())) {
setLastError(ErrorCode::OpenVpnConfigMissing);
return lastError();
}
#ifdef AMNEZIA_DESKTOP
const ErrorCode res = IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
QString ip = NetworkUtilities::getIPAddress(m_configData.value(amnezia::configKey::hostName).toString());
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QStringList(ip));
if (!reply.waitForFinished(1000) || !reply.returnValue()) {
return ErrorCode::AmneziaServiceConnectionFailed;
}
return ErrorCode::NoError;
});
if (res != ErrorCode::NoError) {
return res;
}
#endif
// Detect default gateway
#ifdef Q_OS_MAC
QProcess p;
p.setProcessChannelMode(QProcess::MergedChannels);
p.start("route",
QStringList() << "-n"
<< "get"
<< "default");
p.waitForFinished();
QString s = p.readAll();
QRegularExpression rx(R"(gateway:\s*(\d+\.\d+\.\d+\.\d+))");
QRegularExpressionMatch match = rx.match(s);
if (match.hasMatch()) {
m_routeGateway = match.captured(1);
qDebug() << "Set VPN route gateway" << m_routeGateway;
} else {
qWarning() << "Unable to set VPN route gateway, output:\n" << s;
}
#endif
uint mgmtPort = selectMgmtPort();
qDebug() << "OpenVpnProtocol::start mgmt port selected:" << mgmtPort;
if (!m_managementServer.start(m_managementHost, mgmtPort)) {
setLastError(ErrorCode::OpenVpnManagementServerError);
return lastError();
}
setConnectionState(Vpn::ConnectionState::Connecting);
m_openVpnProcess = IpcClient::CreatePrivilegedProcess();
if (!m_openVpnProcess) {
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
return ErrorCode::AmneziaServiceConnectionFailed;
}
m_openVpnProcess->setProgram(PermittedProcess::OpenVPN);
QStringList arguments({
"--config", configPath(), "--management", m_managementHost, QString::number(mgmtPort),
"--management-client" /*, "--log", vpnLogFileNamePath */
});
m_openVpnProcess->setArguments(arguments);
qDebug() << arguments.join(" ");
connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::errorOccurred,
[&](QProcess::ProcessError error) { qDebug() << "PrivilegedProcess errorOccurred" << error; });
connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::stateChanged,
[&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; });
connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::finished, this,
[&]() { setConnectionState(Vpn::ConnectionState::Disconnected); });
m_openVpnProcess->start();
return ErrorCode::NoError;
}
bool OpenVpnProtocol::sendTermSignal()
{
return m_managementServer.writeCommand("signal SIGTERM");
}
void OpenVpnProtocol::sendByteCount()
{
m_managementServer.writeCommand("bytecount 1");
}
void OpenVpnProtocol::sendInitialData()
{
m_managementServer.writeCommand("state on");
m_managementServer.writeCommand("log on");
}
void OpenVpnProtocol::onReadyReadDataFromManagementServer()
{
for (;;) {
QString line = m_managementServer.readLine().simplified();
if (line.isEmpty()) {
return;
}
if (!line.contains(">BYTECOUNT")) {
qDebug().noquote() << line;
}
if (line.contains(">INFO:OpenVPN Management Interface")) {
sendInitialData();
} else if (line.startsWith(">STATE")) {
if (line.contains("CONNECTED,SUCCESS")) {
sendByteCount();
stopTimeoutTimer();
setConnectionState(Vpn::ConnectionState::Connected);
continue;
} else if (line.contains("EXITING,SIGTER")) {
// openVpnStateSigTermHandler();
setConnectionState(Vpn::ConnectionState::Disconnecting);
continue;
} else if (line.contains("RECONNECTING")) {
setConnectionState(Vpn::ConnectionState::Reconnecting);
continue;
}
}
if (line.contains("ROUTE_GATEWAY") || line.contains("net_route_v4_best_gw")) {
updateRouteGateway(line);
}
if (line.contains("PUSH: Received control message")) {
updateVpnGateway(line);
}
if (line.contains("FATAL")) {
if (line.contains("tap-windows6 adapters on this system are currently in use or disabled")) {
emit protocolError(ErrorCode::OpenVpnAdaptersInUseError);
} else {
emit protocolError(ErrorCode::OpenVpnUnknownError);
}
return;
}
QByteArray data(line.toStdString().c_str());
if (data.contains(">BYTECOUNT:")) {
int beg = data.lastIndexOf(">BYTECOUNT:");
int end = data.indexOf("\n", beg);
beg += sizeof(">BYTECOUNT:") - 1;
QList<QByteArray> count = data.mid(beg, end - beg + 1).split(',');
quint64 r = static_cast<quint64>(count.at(0).trimmed().toULongLong());
quint64 s = static_cast<quint64>(count.at(1).trimmed().toULongLong());
setBytesChanged(r, s);
}
}
}
void OpenVpnProtocol::updateVpnGateway(const QString &line)
{
// line looks like
// PUSH: Received control message: 'PUSH_REPLY,route 10.8.0.1,topology net30,ping 10,ping-restart
// 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-GCM'
QStringList params = line.split(",");
for (const QString &l : params) {
if (l.contains("ifconfig")) {
if (l.split(" ").size() == 3) {
m_vpnLocalAddress = l.split(" ").at(1);
m_vpnGateway = l.split(" ").at(2);
#ifdef Q_OS_WIN
QThread::msleep(300);
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
for (int i = 0; i < netInterfaces.size(); i++) {
for (int j=0; j < netInterfaces.at(i).addressEntries().size(); j++)
{
// killSwitch toggle
if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) {
if (QVariant(m_configData.value(configKey::killSwitchOption).toString()).toBool()) {
iface->enableKillSwitch(m_configData, netInterfaces.at(i).index());
}
m_configData.insert("vpnAdapterIndex", netInterfaces.at(i).index());
m_configData.insert("vpnGateway", m_vpnGateway);
m_configData.insert("vpnServer",
NetworkUtilities::getIPAddress(m_configData.value(amnezia::configKey::hostName).toString()));
iface->enablePeerTraffic(m_configData);
}
}
}
});
#endif
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
// killSwitch toggle
if (QVariant(m_configData.value(configKey::killSwitchOption).toString()).toBool()) {
m_configData.insert("vpnServer",
NetworkUtilities::getIPAddress(m_configData.value(amnezia::configKey::hostName).toString()));
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> reply = iface->enableKillSwitch(m_configData, 0);
if (!reply.waitForFinished(1000) || !reply.returnValue()) {
qWarning() << "OpenVpnProtocol::updateVpnGateway(): Failed to enable killswitch";
}
});
}
#endif
qDebug() << QString("Set vpn local address %1, gw %2").arg(m_vpnLocalAddress).arg(vpnGateway());
}
}
}
}
+59
View File
@@ -0,0 +1,59 @@
#ifndef OPENVPNPROTOCOL_H
#define OPENVPNPROTOCOL_H
#include <QObject>
#include <QString>
#include <QTimer>
#include "core/utils/managementServer.h"
#include "vpnProtocol.h"
#include "core/utils/ipcClient.h"
class OpenVpnProtocol : public VpnProtocol
{
Q_OBJECT
public:
explicit OpenVpnProtocol(const QJsonObject& configuration, QObject* parent = nullptr);
virtual ~OpenVpnProtocol() override;
ErrorCode start() override;
void stop() override;
ErrorCode prepare() override;
static QString defaultConfigFileName();
static QString defaultConfigPath();
protected slots:
void onReadyReadDataFromManagementServer();
private:
QString configPath() const;
bool openVpnProcessIsRunning() const;
bool sendTermSignal();
void readOpenVpnConfiguration(const QJsonObject &configuration);
void disconnectFromManagementServer();
void killOpenVpnProcess();
void sendByteCount();
void sendInitialData();
void sendManagementCommand(const QString& command);
const QString m_managementHost = "127.0.0.1";
const unsigned int m_managementPort = 57775;
ManagementServer m_managementServer;
QString m_configFileName;
QJsonObject m_configData;
QTemporaryFile m_configFile;
uint selectMgmtPort();
private:
void updateRouteGateway(QString line);
void updateVpnGateway(const QString &line);
QSharedPointer<IpcProcessInterfaceReplica> m_openVpnProcess;
};
#endif // OPENVPNPROTOCOL_H
+211
View File
@@ -0,0 +1,211 @@
#include "protocolUtils.h"
#include <QRandomGenerator>
#include <QJsonObject>
#include <QObject>
using namespace amnezia;
QList<Proto> ProtocolUtils::allProtocols()
{
QMetaEnum metaEnum = QMetaEnum::fromType<Proto>();
QList<Proto> all;
for (int i = 0; i < metaEnum.keyCount(); ++i) {
all.append(static_cast<Proto>(i));
}
return all;
}
TransportProto ProtocolUtils::transportProtoFromString(QString p)
{
QMetaEnum metaEnum = QMetaEnum::fromType<TransportProto>();
for (int i = 0; i < metaEnum.keyCount(); ++i) {
TransportProto tp = static_cast<TransportProto>(i);
if (p.toLower() == transportProtoToString(tp).toLower())
return tp;
}
return TransportProto::Udp;
}
QString ProtocolUtils::transportProtoToString(TransportProto proto, Proto p)
{
QMetaEnum metaEnum = QMetaEnum::fromType<TransportProto>();
QString protoKey = metaEnum.valueToKey(static_cast<int>(proto));
return protoKey.toLower();
}
Proto ProtocolUtils::protoFromString(QString proto)
{
QMetaEnum metaEnum = QMetaEnum::fromType<Proto>();
for (int i = 0; i < metaEnum.keyCount(); ++i) {
Proto p = static_cast<Proto>(i);
if (proto == protoToString(p))
return p;
}
return Proto::Unknown;
}
QString ProtocolUtils::protoToString(Proto p)
{
if (p == Proto::Unknown)
return "";
QMetaEnum metaEnum = QMetaEnum::fromType<Proto>();
QString protoKey = metaEnum.valueToKey(static_cast<int>(p));
return protoKey.toLower();
}
QMap<Proto, QString> ProtocolUtils::protocolHumanNames()
{
return { { Proto::OpenVpn, "OpenVPN" },
{ Proto::WireGuard, "WireGuard" },
{ Proto::Awg, "AmneziaWG" },
{ Proto::Ikev2, "IKEv2" },
{ Proto::Xray, "XRay" },
{ Proto::SSXray, "Shadowsocks"},
{ Proto::TorWebSite, "Website in Tor network" },
{ Proto::Dns, "DNS Service" },
{ Proto::Sftp, QObject::tr("SFTP service") },
{ Proto::Socks5Proxy, QObject::tr("SOCKS5 proxy server") } };
}
QMap<Proto, QString> ProtocolUtils::protocolDescriptions()
{
return {};
}
ServiceType ProtocolUtils::protocolService(Proto p)
{
switch (p) {
case Proto::Unknown: return ServiceType::None;
case Proto::SSXray: return ServiceType::None;
case Proto::OpenVpn: return ServiceType::Vpn;
case Proto::WireGuard: return ServiceType::Vpn;
case Proto::Awg: return ServiceType::Vpn;
case Proto::Ikev2: return ServiceType::Vpn;
case Proto::Xray: return ServiceType::Vpn;
case Proto::TorWebSite: return ServiceType::Other;
case Proto::Dns: return ServiceType::Other;
case Proto::Sftp: return ServiceType::Other;
case Proto::Socks5Proxy: return ServiceType::Other;
default: return ServiceType::Other;
}
}
int ProtocolUtils::getPortForInstall(Proto p)
{
switch (p) {
case Awg:
case WireGuard:
case OpenVpn:
case Socks5Proxy:
return QRandomGenerator::global()->bounded(30000, 50000);
default:
return defaultPort(p);
}
}
int ProtocolUtils::defaultPort(Proto p)
{
switch (p) {
case Proto::Unknown: return -1;
case Proto::OpenVpn: return QString(protocols::openvpn::defaultPort).toInt();
case Proto::WireGuard: return QString(protocols::wireguard::defaultPort).toInt();
case Proto::Awg: return QString(protocols::awg::defaultPort).toInt();
case Proto::Xray: return QString(protocols::xray::defaultPort).toInt();
case Proto::Ikev2: return -1;
case Proto::TorWebSite: return -1;
case Proto::Dns: return 53;
case Proto::Sftp: return 222;
case Proto::Socks5Proxy: return 38080;
default: return -1;
}
}
bool ProtocolUtils::defaultPortChangeable(Proto p)
{
switch (p) {
case Proto::Unknown: return false;
case Proto::OpenVpn: return true;
case Proto::WireGuard: return true;
case Proto::Awg: return true;
case Proto::Ikev2: return false;
case Proto::Xray: return true;
case Proto::TorWebSite: return false;
case Proto::Dns: return false;
case Proto::Sftp: return true;
case Proto::Socks5Proxy: return true;
default: return false;
}
}
TransportProto ProtocolUtils::defaultTransportProto(Proto p)
{
switch (p) {
case Proto::Unknown: return TransportProto::Udp;
case Proto::OpenVpn: return TransportProto::Udp;
case Proto::WireGuard: return TransportProto::Udp;
case Proto::Awg: return TransportProto::Udp;
case Proto::Ikev2: return TransportProto::Udp;
case Proto::Xray: return TransportProto::Tcp;
case Proto::SSXray: return TransportProto::Tcp;
// non-vpn
case Proto::TorWebSite: return TransportProto::Tcp;
case Proto::Dns: return TransportProto::Udp;
case Proto::Sftp: return TransportProto::Tcp;
case Proto::Socks5Proxy: return TransportProto::Tcp;
default: return TransportProto::Udp;
}
}
bool ProtocolUtils::defaultTransportProtoChangeable(Proto p)
{
switch (p) {
case Proto::Unknown: return false;
case Proto::OpenVpn: return true;
case Proto::WireGuard: return false;
case Proto::Awg: return false;
case Proto::Ikev2: return false;
case Proto::Xray: return false;
// non-vpn
case Proto::TorWebSite: return false;
case Proto::Dns: return false;
case Proto::Sftp: return false;
case Proto::Socks5Proxy: return false;
default: return false;
}
return false;
}
QString ProtocolUtils::key_proto_config_data(Proto p)
{
return protoToString(p) + "_config_data";
}
QString ProtocolUtils::key_proto_config_path(Proto p)
{
return protoToString(p) + "_config_path";
}
QString ProtocolUtils::getProtocolVersion(const QJsonObject &protocolConfig)
{
return protocolConfig.value(configKey::protocolVersion).toString();
}
QString ProtocolUtils::getProtocolVersionString(const QJsonObject &protocolConfig)
{
auto version = getProtocolVersion(protocolConfig);
if (version == protocols::awg::awgV2) return QObject::tr(" (version 2)");
if (version == protocols::awg::awgV1_5) return QObject::tr(" (version 1.5)");
return "";
}
+49
View File
@@ -0,0 +1,49 @@
#ifndef PROTOCOLUTILS_H
#define PROTOCOLUTILS_H
#include <QList>
#include <QMap>
#include <QString>
#include <QJsonObject>
#include "core/utils/protocolEnum.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
namespace amnezia
{
namespace ProtocolUtils
{
QList<Proto> allProtocols();
// spelling may differ for various protocols - TCP for OpenVPN, tcp for others
TransportProto transportProtoFromString(QString p);
QString transportProtoToString(TransportProto proto, Proto p = Proto::Unknown);
Proto protoFromString(QString p);
QString protoToString(Proto p);
QMap<Proto, QString> protocolHumanNames();
QMap<Proto, QString> protocolDescriptions();
ServiceType protocolService(Proto p);
int getPortForInstall(Proto p);
int defaultPort(Proto p);
bool defaultPortChangeable(Proto p);
TransportProto defaultTransportProto(Proto p);
bool defaultTransportProtoChangeable(Proto p);
QString key_proto_config_data(Proto p);
QString key_proto_config_path(Proto p);
QString getProtocolVersion(const QJsonObject &protocolConfig);
QString getProtocolVersionString(const QJsonObject &protocolConfig);
}
}
#endif // PROTOCOLUTILS_H
@@ -0,0 +1,42 @@
#ifndef QML_REGISTER_PROTOCOLS_H
#define QML_REGISTER_PROTOCOLS_H
#include "core/utils/protocolEnum.h"
#include <QObject>
#include <QDebug>
#include <QQmlEngine>
namespace amnezia {
using namespace amnezia::ProtocolEnumNS;
void declareQmlProtocolEnum() {
qmlRegisterUncreatableMetaObject(
ProtocolEnumNS::staticMetaObject,
"ProtocolEnum",
1, 0,
"ProtocolEnum",
"Error: only enums"
);
qmlRegisterUncreatableMetaObject(
ProtocolEnumNS::staticMetaObject,
"ProtocolEnum",
1, 0,
"TransportProto",
"Error: only enums"
);
qmlRegisterUncreatableMetaObject(
ProtocolEnumNS::staticMetaObject,
"ProtocolEnum",
1, 0,
"ServiceType",
"Error: only enums"
);
}
} // namespace amnezia
#endif // QML_REGISTER_PROTOCOLS_H
+162
View File
@@ -0,0 +1,162 @@
#include <QDebug>
#include <QTimer>
#include "core/utils/errorStrings.h"
#include "vpnProtocol.h"
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) and !defined MACOS_NE || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
#include "openVpnProtocol.h"
#include "wireGuardProtocol.h"
#include "xrayProtocol.h"
#endif
#ifdef Q_OS_WINDOWS
#include "ikev2VpnProtocolWindows.h"
#endif
VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject *parent)
: QObject(parent),
m_connectionState(Vpn::ConnectionState::Unknown),
m_rawConfig(configuration),
m_timeoutTimer(new QTimer(this)),
m_receivedBytes(0),
m_sentBytes(0)
{
m_timeoutTimer->setSingleShot(true);
connect(m_timeoutTimer, &QTimer::timeout, this, &VpnProtocol::onTimeout);
}
void VpnProtocol::setLastError(ErrorCode lastError)
{
m_lastError = lastError;
if (lastError) {
setConnectionState(Vpn::ConnectionState::Error);
}
qCritical().noquote() << "VpnProtocol error, code" << m_lastError << errorString(m_lastError);
}
ErrorCode VpnProtocol::lastError() const
{
return m_lastError;
}
void VpnProtocol::onTimeout()
{
qDebug() << "Timeout";
emit timeoutTimerEvent();
stop();
}
void VpnProtocol::startTimeoutTimer()
{
m_timeoutTimer->start(30000);
}
void VpnProtocol::stopTimeoutTimer()
{
m_timeoutTimer->stop();
}
Vpn::ConnectionState VpnProtocol::connectionState() const
{
return m_connectionState;
}
void VpnProtocol::setBytesChanged(quint64 receivedBytes, quint64 sentBytes)
{
quint64 rxDiff = receivedBytes - m_receivedBytes;
quint64 txDiff = sentBytes - m_sentBytes;
emit bytesChanged(rxDiff, txDiff);
m_receivedBytes = receivedBytes;
m_sentBytes = sentBytes;
}
void VpnProtocol::setConnectionState(Vpn::ConnectionState state)
{
qDebug() << "VpnProtocol::setConnectionState" << textConnectionState(state);
if (m_connectionState == state) {
return;
}
if (m_connectionState == Vpn::ConnectionState::Disconnected && state == Vpn::ConnectionState::Disconnecting) {
return;
}
m_connectionState = state;
if (m_connectionState == Vpn::ConnectionState::Disconnected) {
m_receivedBytes = 0;
m_sentBytes = 0;
}
qDebug().noquote() << QString("Connection state: '%1'").arg(textConnectionState());
emit connectionStateChanged(m_connectionState);
}
QString VpnProtocol::vpnGateway() const
{
return m_vpnGateway;
}
QString VpnProtocol::vpnLocalAddress() const
{
return m_vpnLocalAddress;
}
VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &configuration)
{
switch (container) {
#if defined(Q_OS_WINDOWS)
case DockerContainer::Ipsec: return new Ikev2Protocol(configuration);
#endif
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) and !defined MACOS_NE || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
case DockerContainer::OpenVpn: return new OpenVpnProtocol(configuration);
case DockerContainer::WireGuard: return new WireguardProtocol(configuration);
case DockerContainer::Awg2: return new WireguardProtocol(configuration);
case DockerContainer::Awg: return new WireguardProtocol(configuration);
case DockerContainer::Xray: return new XrayProtocol(configuration);
case DockerContainer::SSXray: return new XrayProtocol(configuration);
#endif
default: return nullptr;
}
}
QString VpnProtocol::routeGateway() const
{
return m_routeGateway;
}
QString VpnProtocol::textConnectionState(Vpn::ConnectionState connectionState)
{
switch (connectionState) {
case Vpn::ConnectionState::Unknown: return tr("Unknown");
case Vpn::ConnectionState::Disconnected: return tr("Disconnected");
case Vpn::ConnectionState::Preparing: return tr("Preparing");
case Vpn::ConnectionState::Connecting: return tr("Connecting...");
case Vpn::ConnectionState::Connected: return tr("Connected");
case Vpn::ConnectionState::Disconnecting: return tr("Disconnecting...");
case Vpn::ConnectionState::Reconnecting: return tr("Reconnecting...");
case Vpn::ConnectionState::Error: return tr("Error");
default:;
}
return QString();
}
QString VpnProtocol::textConnectionState() const
{
return textConnectionState(m_connectionState);
}
bool VpnProtocol::isConnected() const
{
return m_connectionState == Vpn::ConnectionState::Connected;
}
bool VpnProtocol::isDisconnected() const
{
return m_connectionState == Vpn::ConnectionState::Disconnected;
}
+107
View File
@@ -0,0 +1,107 @@
#ifndef VPNPROTOCOL_H
#define VPNPROTOCOL_H
#include <QObject>
#include <QString>
#include <QJsonObject>
#include <QtQml/qqml.h>
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
using namespace amnezia;
class QTimer;
//todo change name
namespace Vpn
{
Q_NAMESPACE
enum ConnectionState {
Unknown,
Disconnected,
Preparing,
Connecting,
Connected,
Disconnecting,
Reconnecting,
Error
};
Q_ENUM_NS(ConnectionState)
static void declareQmlVpnConnectionStateEnum() {
qmlRegisterUncreatableMetaObject(
Vpn::staticMetaObject,
"ConnectionState",
1, 0,
"ConnectionState",
"Error: only enums"
);
}
}
class VpnProtocol : public QObject
{
Q_OBJECT
public:
explicit VpnProtocol(const QJsonObject& configuration, QObject* parent = nullptr);
virtual ~VpnProtocol() override = default;
static QString textConnectionState(Vpn::ConnectionState connectionState);
virtual ErrorCode prepare() { return ErrorCode::NoError; }
virtual bool isConnected() const;
virtual bool isDisconnected() const;
virtual ErrorCode start() = 0;
virtual void stop() = 0;
Vpn::ConnectionState connectionState() const;
ErrorCode lastError() const;
QString textConnectionState() const;
void setLastError(ErrorCode lastError);
QString routeGateway() const;
QString vpnGateway() const;
QString vpnLocalAddress() const;
static VpnProtocol* factory(amnezia::DockerContainer container, const QJsonObject &configuration);
signals:
void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
void connectionStateChanged(Vpn::ConnectionState state);
void timeoutTimerEvent();
void protocolError(amnezia::ErrorCode e);
void tunnelAddressesUpdated(const QString& gateway, const QString& localAddress);
public slots:
virtual void onTimeout(); // todo: remove?
void setBytesChanged(quint64 receivedBytes, quint64 sentBytes);
void setConnectionState(Vpn::ConnectionState state);
protected:
void startTimeoutTimer();
void stopTimeoutTimer();
Vpn::ConnectionState m_connectionState;
QString m_routeGateway;
QString m_vpnLocalAddress;
QString m_vpnGateway;
QJsonObject m_rawConfig;
private:
QTimer* m_timeoutTimer;
ErrorCode m_lastError;
quint64 m_receivedBytes;
quint64 m_sentBytes;
};
#endif // VPNPROTOCOL_H
@@ -0,0 +1,79 @@
#include <QCoreApplication>
#include <QFileInfo>
#include <QProcess>
#include <QTcpSocket>
#include <QThread>
#include "wireGuardProtocol.h"
#include "core/utils/networkUtilities.h"
#include "mozilla/localsocketcontroller.h"
WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *parent)
: VpnProtocol(configuration, parent)
{
m_impl.reset(new LocalSocketController());
connect(m_impl.get(), &ControllerImpl::connected, this,
[this](const QString &pubkey, const QDateTime &connectionTimestamp) {
setConnectionState(Vpn::ConnectionState::Connected);
});
connect(m_impl.get(), &ControllerImpl::statusUpdated, this,
[this](const QString& serverIpv4Gateway,
const QString& deviceIpv4Address, uint64_t txBytes,
uint64_t rxBytes) {
const QString previousGateway = m_vpnGateway;
const QString previousLocal = m_vpnLocalAddress;
if (!serverIpv4Gateway.isEmpty()) {
m_vpnGateway = serverIpv4Gateway;
}
if (!deviceIpv4Address.isEmpty()) {
m_vpnLocalAddress = deviceIpv4Address;
}
if ((!m_vpnGateway.isEmpty() && m_vpnGateway != previousGateway) ||
(!m_vpnLocalAddress.isEmpty() && m_vpnLocalAddress != previousLocal)) {
emit tunnelAddressesUpdated(m_vpnGateway, m_vpnLocalAddress);
}
});
connect(m_impl.get(), &ControllerImpl::disconnected, this,
[this]() { setConnectionState(Vpn::ConnectionState::Disconnected); });
m_impl->initialize(nullptr, nullptr);
}
WireguardProtocol::~WireguardProtocol()
{
WireguardProtocol::stop();
QThread::msleep(200);
}
void WireguardProtocol::stop()
{
stopMzImpl();
return;
}
ErrorCode WireguardProtocol::startMzImpl()
{
QString protocolName = m_rawConfig.value("protocol").toString();
QJsonObject vpnConfigData = m_rawConfig.value(protocolName + "_config_data").toObject();
vpnConfigData[configKey::hostName] = NetworkUtilities::getIPAddress(vpnConfigData.value(configKey::hostName).toString());
m_rawConfig.insert(protocolName + "_config_data", vpnConfigData);
m_rawConfig[configKey::hostName] = NetworkUtilities::getIPAddress(m_rawConfig[configKey::hostName].toString());
m_impl->activate(m_rawConfig);
return ErrorCode::NoError;
}
ErrorCode WireguardProtocol::stopMzImpl()
{
m_impl->deactivate();
return ErrorCode::NoError;
}
ErrorCode WireguardProtocol::start()
{
return startMzImpl();
}
+33
View File
@@ -0,0 +1,33 @@
#ifndef WIREGUARDPROTOCOL_H
#define WIREGUARDPROTOCOL_H
#include <QObject>
#include <QProcess>
#include <QString>
#include <QTemporaryFile>
#include <QTimer>
#include "vpnProtocol.h"
#include "mozilla/controllerimpl.h"
class WireguardProtocol : public VpnProtocol
{
Q_OBJECT
public:
explicit WireguardProtocol(const QJsonObject& configuration, QObject* parent = nullptr);
virtual ~WireguardProtocol() override;
ErrorCode start() override;
void stop() override;
ErrorCode startMzImpl();
ErrorCode stopMzImpl();
private:
QScopedPointer<ControllerImpl> m_impl;
};
#endif // WIREGUARDPROTOCOL_H
+271
View File
@@ -0,0 +1,271 @@
#include "xrayProtocol.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/ipcClient.h"
#include "core/utils/networkUtilities.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/serialization/serialization.h"
#include "ipc.h"
#include <QCryptographicHash>
#include <QJsonObject>
#include <QNetworkInterface>
#include <QJsonDocument>
#include <QtCore/qlogging.h>
#include <QtCore/qobjectdefs.h>
#include <QtCore/qprocess.h>
#include <exception>
#ifdef Q_OS_MACOS
static const QString tunName = "utun22";
#else
static const QString tunName = "tun2";
#endif
XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent)
{
m_vpnGateway = amnezia::protocols::xray::defaultLocalAddr;
m_vpnLocalAddress = amnezia::protocols::xray::defaultLocalAddr;
m_routeGateway = NetworkUtilities::getGatewayAndIface().first;
m_routeMode = static_cast<amnezia::RouteMode>(configuration.value(amnezia::configKey::splitTunnelType).toInt());
m_remoteAddress = NetworkUtilities::getIPAddress(m_rawConfig.value(amnezia::configKey::hostName).toString());
const QString primaryDns = configuration.value(amnezia::configKey::dns1).toString();
m_dnsServers.push_back(QHostAddress(primaryDns));
if (primaryDns != amnezia::protocols::dns::amneziaDnsIp) {
const QString secondaryDns = configuration.value(amnezia::configKey::dns2).toString();
m_dnsServers.push_back(QHostAddress(secondaryDns));
}
QJsonObject xrayConfiguration = configuration.value(ProtocolUtils::key_proto_config_data(Proto::Xray)).toObject();
if (xrayConfiguration.isEmpty()) {
xrayConfiguration = configuration.value(ProtocolUtils::key_proto_config_data(Proto::SSXray)).toObject();
}
m_xrayConfig = xrayConfiguration;
}
XrayProtocol::~XrayProtocol()
{
qDebug() << "XrayProtocol::~XrayProtocol()";
XrayProtocol::stop();
}
ErrorCode XrayProtocol::start()
{
qDebug() << "XrayProtocol::start()";
// Inject SOCKS5 auth into the inbound before starting xray.
// Re-uses existing credentials if the config already has them (e.g. imported config).
amnezia::serialization::inbounds::InboundCredentials creds;
try {
creds = amnezia::serialization::inbounds::EnsureInboundAuth(m_xrayConfig);
} catch (const std::exception &e) {
qCritical() << "EnsureInboundAuth failed:" << e.what();
return ErrorCode::InternalError;
}
m_socksUser = creds.username;
m_socksPassword = creds.password;
m_socksPort = creds.port;
const QString xrayConfigStr = QJsonDocument(m_xrayConfig).toJson(QJsonDocument::Compact);
if (xrayConfigStr.isEmpty()) {
qCritical() << "Xray config is empty";
return ErrorCode::XrayExecutableCrashed;
}
return IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
auto xrayStart = iface->xrayStart(xrayConfigStr);
if (!xrayStart.waitForFinished() || !xrayStart.returnValue()) {
qCritical() << "Failed to start xray";
return ErrorCode::XrayExecutableCrashed;
}
return startTun2Socks();
}, [] () {
return ErrorCode::AmneziaServiceConnectionFailed;
});
}
void XrayProtocol::stop()
{
qDebug() << "XrayProtocol::stop()";
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
auto disableKillSwitch = iface->disableKillSwitch();
if (!disableKillSwitch.waitForFinished() || !disableKillSwitch.returnValue())
qWarning() << "Failed to disable killswitch";
auto StartRoutingIpv6 = iface->StartRoutingIpv6();
if (!StartRoutingIpv6.waitForFinished() || !StartRoutingIpv6.returnValue())
qWarning() << "Failed to start routing ipv6";
auto restoreResolvers = iface->restoreResolvers();
if (!restoreResolvers.waitForFinished() || !restoreResolvers.returnValue())
qWarning() << "Failed to restore resolvers";
auto deleteTun = iface->deleteTun(tunName);
if (!deleteTun.waitForFinished() || !deleteTun.returnValue())
qWarning() << "Failed to delete tun";
auto xrayStop = iface->xrayStop();
if (!xrayStop.waitForFinished() || !xrayStop.returnValue())
qWarning() << "Failed to stop xray";
});
if (m_tun2socksProcess) {
m_tun2socksProcess->blockSignals(true);
#ifndef Q_OS_WIN
m_tun2socksProcess->terminate();
auto waitForFinished = m_tun2socksProcess->waitForFinished(1000);
if (!waitForFinished.waitForFinished() || !waitForFinished.returnValue()) {
qWarning() << "Failed to terminate tun2socks. Killing the process...";
m_tun2socksProcess->kill();
}
#else
// terminate does not do anything useful on Windows
// so just kill the process
m_tun2socksProcess->kill();
#endif
m_tun2socksProcess->close();
m_tun2socksProcess.reset();
}
setConnectionState(Vpn::ConnectionState::Disconnected);
}
ErrorCode XrayProtocol::startTun2Socks()
{
m_tun2socksProcess = IpcClient::CreatePrivilegedProcess();
if (!m_tun2socksProcess->waitForSource()) {
return ErrorCode::AmneziaServiceConnectionFailed;
}
const QString proxyUrl = QString("socks5://%1:%2@127.0.0.1:%3")
.arg(m_socksUser, m_socksPassword, QString::number(m_socksPort));
m_tun2socksProcess->setProgram(PermittedProcess::Tun2Socks);
m_tun2socksProcess->setArguments({"-device", QString("tun://%1").arg(tunName), "-proxy", proxyUrl});
connect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardOutput, this, [this]() {
auto readAllStandardOutput = m_tun2socksProcess->readAllStandardOutput();
if (!readAllStandardOutput.waitForFinished()) {
qWarning() << "Failed to read output from tun2socks";
return;
}
const QString line = readAllStandardOutput.returnValue();
if (!line.contains("[TCP]") && !line.contains("[UDP]"))
qDebug() << "[tun2socks]:" << line;
if (line.contains("[STACK] tun://") && line.contains("<-> socks5://")) {
disconnect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardOutput, this, nullptr);
if (ErrorCode res = setupRouting(); res != ErrorCode::NoError) {
stop();
setLastError(res);
} else {
setConnectionState(Vpn::ConnectionState::Connected);
}
}
}, Qt::QueuedConnection);
connect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::finished, this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
if (exitStatus == QProcess::ExitStatus::CrashExit) {
qCritical() << "Tun2socks process crashed!";
} else {
qCritical() << QString("Tun2socks process was closed with %1 exit code").arg(exitCode);
}
stop();
setLastError(ErrorCode::Tun2SockExecutableCrashed);
}, Qt::QueuedConnection);
m_tun2socksProcess->start();
return ErrorCode::NoError;
}
ErrorCode XrayProtocol::setupRouting() {
return IpcClient::withInterface([this](QSharedPointer<IpcInterfaceReplica> iface) -> ErrorCode {
#ifdef Q_OS_WIN
const int inetAdapterIndex = NetworkUtilities::AdapterIndexTo(QHostAddress(m_remoteAddress));
#endif
auto createTun = iface->createTun(tunName, amnezia::protocols::xray::defaultLocalAddr);
if (!createTun.waitForFinished() || !createTun.returnValue()) {
qCritical() << "Failed to assign IP address for TUN";
return ErrorCode::InternalError;
}
auto updateResolvers = iface->updateResolvers(tunName, m_dnsServers);
if (!updateResolvers.waitForFinished() || !updateResolvers.returnValue()) {
qCritical() << "Failed to set DNS resolvers for TUN";
return ErrorCode::InternalError;
}
#ifdef Q_OS_WIN
int vpnAdapterIndex = -1;
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
for (auto& netInterface : netInterfaces) {
for (auto& address : netInterface.addressEntries()) {
if (m_vpnLocalAddress == address.ip().toString())
vpnAdapterIndex = netInterface.index();
}
}
#else
static const int vpnAdapterIndex = 0;
#endif
const bool killSwitchEnabled = QVariant(m_rawConfig.value(configKey::killSwitchOption).toString()).toBool();
if (killSwitchEnabled) {
if (vpnAdapterIndex != -1) {
QJsonObject config = m_rawConfig;
config.insert("vpnServer", m_remoteAddress);
auto enableKillSwitch = IpcClient::Interface()->enableKillSwitch(config, vpnAdapterIndex);
if (!enableKillSwitch.waitForFinished() || !enableKillSwitch.returnValue()) {
qCritical() << "Failed to enable killswitch";
return ErrorCode::InternalError;
}
} else
qWarning() << "Failed to get vpnAdapterIndex. Killswitch disabled";
}
if (m_routeMode == amnezia::RouteMode::VpnAllSites) {
static const QStringList subnets = { "1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1" };
auto routeAddList = iface->routeAddList(m_vpnGateway, subnets);
if (!routeAddList.waitForFinished() || routeAddList.returnValue() != subnets.count()) {
qCritical() << "Failed to set routes for TUN";
return ErrorCode::InternalError;
}
}
auto StopRoutingIpv6 = iface->StopRoutingIpv6();
if (!StopRoutingIpv6.waitForFinished() || !StopRoutingIpv6.returnValue()) {
qCritical() << "Failed to disable IPv6 routing";
return ErrorCode::InternalError;
}
#ifdef Q_OS_WIN
if (inetAdapterIndex != -1 && vpnAdapterIndex != -1) {
QJsonObject config = m_rawConfig;
config.insert("inetAdapterIndex", inetAdapterIndex);
config.insert("vpnAdapterIndex", vpnAdapterIndex);
config.insert("vpnGateway", m_vpnGateway);
config.insert("vpnServer", m_remoteAddress);
auto enablePeerTraffic = iface->enablePeerTraffic(config);
if (!enablePeerTraffic.waitForFinished() || !enablePeerTraffic.returnValue()) {
qCritical() << "Failed to enable peer traffic";
return ErrorCode::InternalError;
}
} else
qWarning() << "Failed to get adapter indexes. Split-tunneling disabled";
#endif
return ErrorCode::NoError;
},
[] () {
return ErrorCode::AmneziaServiceConnectionFailed;
});
}
+40
View File
@@ -0,0 +1,40 @@
#ifndef XRAYPROTOCOL_H
#define XRAYPROTOCOL_H
#include "QProcess"
#include <QtCore/qsharedpointer.h>
#include <QHostAddress>
#include <QList>
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#include "core/utils/ipcClient.h"
#include "vpnProtocol.h"
class XrayProtocol : public VpnProtocol
{
public:
XrayProtocol(const QJsonObject &configuration, QObject *parent = nullptr);
virtual ~XrayProtocol() override;
ErrorCode start() override;
void stop() override;
private:
ErrorCode setupRouting();
ErrorCode startTun2Socks();
QJsonObject m_xrayConfig;
amnezia::RouteMode m_routeMode;
QList<QHostAddress> m_dnsServers;
QString m_remoteAddress;
QString m_socksUser;
QString m_socksPassword;
int m_socksPort = 10808;
QSharedPointer<IpcProcessInterfaceReplica> m_tun2socksProcess;
};
#endif // XRAYPROTOCOL_H