mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-22 02:01:08 +07:00
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:
@@ -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
|
||||
@@ -0,0 +1,10 @@
|
||||
#include "awgProtocol.h"
|
||||
|
||||
Awg::Awg(const QJsonObject &configuration, QObject *parent)
|
||||
: WireguardProtocol(configuration, parent)
|
||||
{
|
||||
}
|
||||
|
||||
Awg::~Awg()
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 "";
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
Executable
+271
@@ -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;
|
||||
});
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user