mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-21 02:01:03 +07:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e9468a4c2f | |||
| db8d966fac | |||
| 6b69bc9618 | |||
| 0089b0b799 | |||
| e4841e809b | |||
| ba4237f1dd | |||
| f6acec53c0 | |||
| 5f631eaa76 | |||
| 7730dd510c | |||
| 30bd264f17 | |||
| 5206665fa0 | |||
| 073491ccb4 | |||
| 561b62cd40 | |||
| 1284ed4d84 | |||
| 6f34443191 | |||
| 02f186c54e | |||
| 784c6cf585 | |||
| 14f132e127 | |||
| 9cb624e681 | |||
| 516e3da7e2 | |||
| 0e83586cae | |||
| 95bdae68f4 |
+2
-2
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
|
||||
project(${PROJECT} VERSION 4.4.1.4
|
||||
project(${PROJECT} VERSION 4.5.0.0
|
||||
DESCRIPTION "AmneziaVPN"
|
||||
HOMEPAGE_URL "https://amnezia.org/"
|
||||
)
|
||||
@@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||
|
||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
set(APP_ANDROID_VERSION_CODE 49)
|
||||
set(APP_ANDROID_VERSION_CODE 50)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
|
||||
@@ -63,10 +63,10 @@ qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
set(AMNEZIAVPN_TS_FILES
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru_RU.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_zh_CN.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_fa_IR.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ar.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ar_EG.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_my_MM.ts
|
||||
)
|
||||
|
||||
@@ -129,6 +129,7 @@ set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h
|
||||
${CMAKE_CURRENT_BINARY_DIR}/version.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.h
|
||||
)
|
||||
|
||||
# Mozilla headres
|
||||
@@ -164,6 +165,7 @@ set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.cpp
|
||||
)
|
||||
|
||||
# Mozilla sources
|
||||
@@ -293,6 +295,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.h
|
||||
)
|
||||
|
||||
@@ -304,6 +307,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -343,6 +343,9 @@ void AmneziaApplication::initModels()
|
||||
m_awgConfigModel.reset(new AwgConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get());
|
||||
|
||||
m_xrayConfigModel.reset(new XrayConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("XrayConfigModel", m_xrayConfigModel.get());
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get());
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "ui/models/protocols/openvpnConfigModel.h"
|
||||
#include "ui/models/protocols/shadowsocksConfigModel.h"
|
||||
#include "ui/models/protocols/wireguardConfigModel.h"
|
||||
#include "ui/models/protocols/xrayConfigModel.h"
|
||||
#include "ui/models/protocols_model.h"
|
||||
#include "ui/models/servers_model.h"
|
||||
#include "ui/models/services/sftpConfigModel.h"
|
||||
@@ -102,6 +103,7 @@ private:
|
||||
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
|
||||
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
|
||||
QScopedPointer<CloakConfigModel> m_cloakConfigModel;
|
||||
QScopedPointer<XrayConfigModel> m_xrayConfigModel;
|
||||
QScopedPointer<WireGuardConfigModel> m_wireGuardConfigModel;
|
||||
QScopedPointer<AwgConfigModel> m_awgConfigModel;
|
||||
#ifdef Q_OS_WINDOWS
|
||||
|
||||
@@ -2,4 +2,11 @@
|
||||
<resources>
|
||||
<string name="connecting">Подключение</string>
|
||||
<string name="disconnecting">Отключение</string>
|
||||
<string name="cancel">Отмена</string>
|
||||
<string name="ok">ОК</string>
|
||||
<string name="vpnGranted">VPN-подключение разрешено</string>
|
||||
<string name="vpnDenied">VPN-подключение запрещено</string>
|
||||
<string name="vpnSetupFailed">Ошибка настройки VPN</string>
|
||||
<string name="vpnSetupFailedMessage">Чтобы подключиться к AmneziaVPN необходимо:\n\n- Разрешить приложению подключаться к сети VPN\n- Отключить функцию \"Постоянная VPN\" для всех остальных VPN-приложений в системных настройках VPN</string>
|
||||
<string name="openVpnSettings">Открыть настройки VPN</string>
|
||||
</resources>
|
||||
@@ -2,4 +2,11 @@
|
||||
<resources>
|
||||
<string name="connecting">Connecting</string>
|
||||
<string name="disconnecting">Disconnecting</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="vpnGranted">VPN permission granted</string>
|
||||
<string name="vpnDenied">VPN permission denied</string>
|
||||
<string name="vpnSetupFailed">VPN setup error</string>
|
||||
<string name="vpnSetupFailedMessage">To connect to AmneziaVPN, please do the following:\n\n- Allow the app to set up a VPN connection\n- Disable Always-on VPN for any other VPN app in the VPN system settings</string>
|
||||
<string name="openVpnSettings">Open VPN settings</string>
|
||||
</resources>
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.amnezia.vpn
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.content.Intent.EXTRA_MIME_TYPES
|
||||
@@ -14,6 +15,7 @@ import android.os.IBinder
|
||||
import android.os.Looper
|
||||
import android.os.Message
|
||||
import android.os.Messenger
|
||||
import android.provider.Settings
|
||||
import android.view.WindowManager.LayoutParams
|
||||
import android.webkit.MimeTypeMap
|
||||
import android.widget.Toast
|
||||
@@ -216,13 +218,13 @@ class AmneziaActivity : QtActivity() {
|
||||
when (resultCode) {
|
||||
RESULT_OK -> {
|
||||
Log.d(TAG, "Vpn permission granted")
|
||||
Toast.makeText(this, "Vpn permission granted", Toast.LENGTH_LONG).show()
|
||||
Toast.makeText(this, resources.getText(R.string.vpnGranted), Toast.LENGTH_LONG).show()
|
||||
checkVpnPermissionCallbacks?.run { onSuccess() }
|
||||
}
|
||||
|
||||
else -> {
|
||||
Log.w(TAG, "Vpn permission denied, resultCode: $resultCode")
|
||||
Toast.makeText(this, "Vpn permission denied", Toast.LENGTH_LONG).show()
|
||||
showOnVpnPermissionRejectDialog()
|
||||
checkVpnPermissionCallbacks?.run { onFail() }
|
||||
}
|
||||
}
|
||||
@@ -280,6 +282,17 @@ class AmneziaActivity : QtActivity() {
|
||||
onSuccess()
|
||||
}
|
||||
|
||||
private fun showOnVpnPermissionRejectDialog() {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.vpnSetupFailed)
|
||||
.setMessage(R.string.vpnSetupFailedMessage)
|
||||
.setNegativeButton(R.string.ok) { _, _ -> }
|
||||
.setPositiveButton(R.string.openVpnSettings) { _, _ ->
|
||||
startActivity(Intent(Settings.ACTION_VPN_SETTINGS))
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
@MainThread
|
||||
private fun startVpn(vpnConfig: String) {
|
||||
if (isServiceConnected) {
|
||||
|
||||
@@ -215,11 +215,9 @@ class AmneziaVpnService : VpnService() {
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
val isAlwaysOnCompat =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) isAlwaysOn
|
||||
else intent?.component?.packageName != packageName
|
||||
val isAlwaysOn = intent != null && intent.action == SERVICE_INTERFACE
|
||||
|
||||
if (isAlwaysOnCompat) {
|
||||
if (isAlwaysOn) {
|
||||
Log.d(TAG, "Start service via Always-on")
|
||||
connect()
|
||||
} else if (intent?.getBooleanExtra(AFTER_PERMISSION_CHECK, false) == true) {
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package org.amnezia.vpn
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.app.KeyguardManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_MASK
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import android.net.VpnService
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.result.ActivityResult
|
||||
@@ -52,19 +56,43 @@ class VpnRequestActivity : ComponentActivity() {
|
||||
}
|
||||
|
||||
private fun checkRequestResult(result: ActivityResult) {
|
||||
when (result.resultCode) {
|
||||
RESULT_OK -> onPermissionGranted()
|
||||
else -> Toast.makeText(this, "Vpn permission denied", Toast.LENGTH_LONG).show()
|
||||
when (val resultCode = result.resultCode) {
|
||||
RESULT_OK -> {
|
||||
onPermissionGranted()
|
||||
finish()
|
||||
}
|
||||
|
||||
else -> {
|
||||
Log.w(TAG, "Vpn permission denied, resultCode: $resultCode")
|
||||
showOnVpnPermissionRejectDialog()
|
||||
}
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun onPermissionGranted() {
|
||||
Toast.makeText(this, "Vpn permission granted", Toast.LENGTH_LONG).show()
|
||||
Toast.makeText(this, resources.getString(R.string.vpnGranted), Toast.LENGTH_LONG).show()
|
||||
Intent(applicationContext, AmneziaVpnService::class.java).apply {
|
||||
putExtra(AFTER_PERMISSION_CHECK, true)
|
||||
}.also {
|
||||
ContextCompat.startForegroundService(this, it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showOnVpnPermissionRejectDialog() {
|
||||
AlertDialog.Builder(this, getDialogTheme())
|
||||
.setTitle(R.string.vpnSetupFailed)
|
||||
.setMessage(R.string.vpnSetupFailedMessage)
|
||||
.setNegativeButton(R.string.ok) { _, _ -> }
|
||||
.setPositiveButton(R.string.openVpnSettings) { _, _ ->
|
||||
startActivity(Intent(Settings.ACTION_VPN_SETTINGS))
|
||||
}
|
||||
.setOnDismissListener { finish() }
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun getDialogTheme(): Int =
|
||||
if (resources.configuration.uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES)
|
||||
android.R.style.Theme_DeviceDefault_Dialog_Alert
|
||||
else
|
||||
android.R.style.Theme_DeviceDefault_Light_Dialog_Alert
|
||||
}
|
||||
|
||||
+1
-1
@@ -157,7 +157,7 @@ open class Wireguard : Protocol() {
|
||||
if (tunFd == null) {
|
||||
throw VpnStartException("Create VPN interface: permission not granted or revoked")
|
||||
}
|
||||
Log.v(TAG, "Wg-go backend ${GoBackend.awgVersion()}")
|
||||
Log.i(TAG, "awg-go backend ${GoBackend.awgVersion()}")
|
||||
tunnelHandle = GoBackend.awgTurnOn(ifName, tunFd.detachFd(), config.toWgUserspaceString())
|
||||
}
|
||||
|
||||
|
||||
@@ -42,9 +42,7 @@ set(SOURCES ${SOURCES}
|
||||
|
||||
foreach(abi IN ITEMS ${QT_ANDROID_ABIS})
|
||||
set_property(TARGET ${PROJECT} PROPERTY QT_ANDROID_EXTRA_LIBS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg.so
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg-go.so
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg-quick.so
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libck-ovpn-plugin.so
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpn3.so
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpnutil.so
|
||||
|
||||
@@ -48,6 +48,5 @@ QString CloakConfigurator::genCloakConfig(const ServerCredentials &credentials,
|
||||
QString textCfg = serverController.replaceVars(QJsonDocument(config).toJson(),
|
||||
serverController.genVarsForScript(credentials, container, containerConfig));
|
||||
|
||||
// qDebug().noquote() << textCfg;
|
||||
return textCfg;
|
||||
}
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
#include "ssh_configurator.h"
|
||||
#include "wireguard_configurator.h"
|
||||
#include "awg_configurator.h"
|
||||
|
||||
#include "xray_configurator.h"
|
||||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "containers/containers_defs.h"
|
||||
#include "settings.h"
|
||||
#include "utilities.h"
|
||||
#include "core/networkUtilities.h"
|
||||
|
||||
VpnConfigurator::VpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent)
|
||||
: ConfiguratorBase(settings, parent)
|
||||
@@ -25,6 +25,7 @@ VpnConfigurator::VpnConfigurator(std::shared_ptr<Settings> settings, QObject *pa
|
||||
ikev2Configurator = std::shared_ptr<Ikev2Configurator>(new Ikev2Configurator(settings, this));
|
||||
sshConfigurator = std::shared_ptr<SshConfigurator>(new SshConfigurator(settings, this));
|
||||
awgConfigurator = std::shared_ptr<AwgConfigurator>(new AwgConfigurator(settings, this));
|
||||
xrayConfigurator = std::shared_ptr<XrayConfigurator>(new XrayConfigurator(settings, this));
|
||||
}
|
||||
|
||||
QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
@@ -45,6 +46,9 @@ QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentia
|
||||
case Proto::Awg:
|
||||
return awgConfigurator->genAwgConfig(credentials, container, containerConfig, clientId, errorCode);
|
||||
|
||||
case Proto::Xray:
|
||||
return xrayConfigurator->genXrayConfig(credentials, container, containerConfig, clientId, errorCode);
|
||||
|
||||
case Proto::Ikev2: return ikev2Configurator->genIkev2Config(credentials, container, containerConfig, errorCode);
|
||||
|
||||
default: return "";
|
||||
@@ -61,13 +65,13 @@ QPair<QString, QString> VpnConfigurator::getDnsForConfig(int serverIndex)
|
||||
dns.first = server.value(config_key::dns1).toString();
|
||||
dns.second = server.value(config_key::dns2).toString();
|
||||
|
||||
if (dns.first.isEmpty() || !Utils::checkIPv4Format(dns.first)) {
|
||||
if (dns.first.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.first)) {
|
||||
if (useAmneziaDns && m_settings->containers(serverIndex).contains(DockerContainer::Dns)) {
|
||||
dns.first = protocols::dns::amneziaDnsIp;
|
||||
} else
|
||||
dns.first = m_settings->primaryDns();
|
||||
}
|
||||
if (dns.second.isEmpty() || !Utils::checkIPv4Format(dns.second)) {
|
||||
if (dns.second.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.second)) {
|
||||
dns.second = m_settings->secondaryDns();
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ class WireguardConfigurator;
|
||||
class Ikev2Configurator;
|
||||
class SshConfigurator;
|
||||
class AwgConfigurator;
|
||||
class XrayConfigurator;
|
||||
|
||||
// Retrieve connection settings from server
|
||||
class VpnConfigurator : public ConfiguratorBase
|
||||
@@ -42,6 +43,7 @@ public:
|
||||
std::shared_ptr<Ikev2Configurator> ikev2Configurator;
|
||||
std::shared_ptr<SshConfigurator> sshConfigurator;
|
||||
std::shared_ptr<AwgConfigurator> awgConfigurator;
|
||||
std::shared_ptr<XrayConfigurator> xrayConfigurator;
|
||||
|
||||
signals:
|
||||
void newVpnConfigCreated(const QString &clientId, const QString &clientName, const DockerContainer container,
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
#include "xray_configurator.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include "core/scripts_registry.h"
|
||||
#include "containers/containers_defs.h"
|
||||
#include "core/controllers/serverController.h"
|
||||
|
||||
XrayConfigurator::XrayConfigurator(std::shared_ptr<Settings> settings, QObject *parent):
|
||||
ConfiguratorBase(settings, parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QString XrayConfigurator::genXrayConfig(const ServerCredentials &credentials,
|
||||
DockerContainer container, const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode)
|
||||
{
|
||||
ErrorCode e = ErrorCode::NoError;
|
||||
ServerController serverController(m_settings);
|
||||
|
||||
QString config =
|
||||
serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container),
|
||||
serverController.genVarsForScript(credentials, container, containerConfig));
|
||||
|
||||
QString xrayPublicKey = serverController.getTextFileFromContainer(container, credentials,
|
||||
amnezia::protocols::xray::PublicKeyPath, &e);
|
||||
xrayPublicKey.replace("\n", "");
|
||||
|
||||
QString xrayUuid = serverController.getTextFileFromContainer(container, credentials,
|
||||
amnezia::protocols::xray::uuidPath, &e);
|
||||
xrayUuid.replace("\n", "");
|
||||
|
||||
QString xrayShortId = serverController.getTextFileFromContainer(container, credentials,
|
||||
amnezia::protocols::xray::shortidPath, &e);
|
||||
xrayShortId.replace("\n", "");
|
||||
|
||||
if (e) {
|
||||
if (errorCode) *errorCode = e;
|
||||
return "";
|
||||
}
|
||||
|
||||
config.replace("$XRAY_CLIENT_ID", xrayUuid);
|
||||
config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey);
|
||||
config.replace("$XRAY_SHORT_ID", xrayShortId);
|
||||
|
||||
return config;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef XRAY_CONFIGURATOR_H
|
||||
#define XRAY_CONFIGURATOR_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "configurator_base.h"
|
||||
#include "core/defs.h"
|
||||
|
||||
class XrayConfigurator : ConfiguratorBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
XrayConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
||||
|
||||
QString genXrayConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
|
||||
};
|
||||
|
||||
#endif // XRAY_CONFIGURATOR_H
|
||||
@@ -58,6 +58,8 @@ QVector<amnezia::Proto> ContainerProps::protocolsForContainer(amnezia::DockerCon
|
||||
|
||||
case DockerContainer::Ipsec: return { Proto::Ikev2 /*, Protocol::L2tp */ };
|
||||
|
||||
case DockerContainer::Xray: return { Proto::Xray };
|
||||
|
||||
case DockerContainer::Dns: return { Proto::Dns };
|
||||
|
||||
case DockerContainer::Sftp: return { Proto::Sftp };
|
||||
@@ -85,6 +87,7 @@ QMap<DockerContainer, QString> ContainerProps::containerHumanNames()
|
||||
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
|
||||
{ DockerContainer::WireGuard, "WireGuard" },
|
||||
{ DockerContainer::Awg, "AmneziaWG" },
|
||||
{ DockerContainer::Xray, "XRay" },
|
||||
{ DockerContainer::Ipsec, QObject::tr("IPsec") },
|
||||
|
||||
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
||||
@@ -111,6 +114,9 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
|
||||
QObject::tr("AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, "
|
||||
"but very resistant to blockages. "
|
||||
"Recommended for regions with high levels of censorship.") },
|
||||
{ DockerContainer::Xray,
|
||||
QObject::tr("XRay with REALITY - Suitable for countries with the highest level of internet censorship. "
|
||||
"Traffic masking as web traffic at the TLS level, and protection against detection by active probing methods.") },
|
||||
{ DockerContainer::Ipsec,
|
||||
QObject::tr("IKEv2 - Modern stable protocol, a bit faster than others, restores connection after "
|
||||
"signal loss. It has native support on the latest versions of Android and iOS.") },
|
||||
@@ -199,6 +205,17 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
||||
"* Minimum number of settings\n"
|
||||
"* Not recognised by DPI analysis systems, resistant to blocking\n"
|
||||
"* Works over UDP network protocol.") },
|
||||
{ DockerContainer::Xray,
|
||||
QObject::tr("The REALITY protocol, a pioneering development by the creators of XRay, "
|
||||
"is specifically designed to counteract the highest levels of internet censorship through its novel approach to evasion.\n"
|
||||
"It uniquely identifies censors during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting censors to genuine websites like google.com, "
|
||||
"thus presenting an authentic TLS certificate and data. \n"
|
||||
"This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, "
|
||||
"legitimate sites without the need for specific configurations. \n"
|
||||
"Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, "
|
||||
"REALITY's innovative \"friend or foe\" recognition at the TLS handshake enhances security and circumvents detection by sophisticated DPI systems employing active probing techniques. "
|
||||
"This makes REALITY a robust solution for maintaining internet freedom in environments with stringent censorship.")
|
||||
},
|
||||
{ DockerContainer::Ipsec,
|
||||
QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n"
|
||||
"One of its distinguishing features is its ability to swiftly switch between networks and devices, "
|
||||
@@ -213,7 +230,11 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
||||
|
||||
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
||||
{ DockerContainer::Dns, QObject::tr("DNS Service") },
|
||||
{ DockerContainer::Sftp, QObject::tr("Sftp file sharing service - is secure FTP service") }
|
||||
{ DockerContainer::Sftp,
|
||||
QObject::tr("After installation, Amnezia will create a\n\n file storage on your server. "
|
||||
"You will be able to access it using\n FileZilla or other SFTP clients, "
|
||||
"as well as mount the disk on your device to access\n it directly from your device.\n\n"
|
||||
"For more detailed information, you can\n find it in the support section under \"Create SFTP file storage.\" ") }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -231,6 +252,7 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
|
||||
case DockerContainer::ShadowSocks: return Proto::ShadowSocks;
|
||||
case DockerContainer::WireGuard: return Proto::WireGuard;
|
||||
case DockerContainer::Awg: return Proto::Awg;
|
||||
case DockerContainer::Xray: return Proto::Xray;
|
||||
case DockerContainer::Ipsec: return Proto::Ikev2;
|
||||
|
||||
case DockerContainer::TorWebSite: return Proto::TorWebSite;
|
||||
@@ -274,7 +296,6 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
|
||||
|
||||
#elif defined(Q_OS_LINUX)
|
||||
switch (c) {
|
||||
case DockerContainer::WireGuard: return true;
|
||||
case DockerContainer::Ipsec: return false;
|
||||
default: return true;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace amnezia
|
||||
Cloak,
|
||||
ShadowSocks,
|
||||
Ipsec,
|
||||
Xray,
|
||||
|
||||
// non-vpn
|
||||
TorWebSite,
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "logger.h"
|
||||
#include "core/scripts_registry.h"
|
||||
#include "core/server_defs.h"
|
||||
#include "core/networkUtilities.h"
|
||||
#include "settings.h"
|
||||
#include "utilities.h"
|
||||
|
||||
@@ -444,9 +445,6 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden
|
||||
stdOut += data + "\n";
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
// auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
||||
// stdOut += data + "\n";
|
||||
// };
|
||||
|
||||
e = runScript(credentials,
|
||||
replaceVars(amnezia::scriptData(SharedScriptType::build_container),
|
||||
@@ -466,9 +464,6 @@ ErrorCode ServerController::runContainerWorker(const ServerCredentials &credenti
|
||||
stdOut += data + "\n";
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
// auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
||||
// stdOut += data + "\n";
|
||||
// };
|
||||
|
||||
ErrorCode e = runScript(credentials,
|
||||
replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container),
|
||||
@@ -537,6 +532,7 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
||||
const QJsonObject &ssConfig = config.value(ProtocolProps::protoToString(Proto::ShadowSocks)).toObject();
|
||||
const QJsonObject &wireguarConfig = config.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject();
|
||||
const QJsonObject &amneziaWireguarConfig = config.value(ProtocolProps::protoToString(Proto::Awg)).toObject();
|
||||
const QJsonObject &xrayConfig = config.value(ProtocolProps::protoToString(Proto::Xray)).toObject();
|
||||
const QJsonObject &sftpConfig = config.value(ProtocolProps::protoToString(Proto::Sftp)).toObject();
|
||||
|
||||
Vars vars;
|
||||
@@ -594,6 +590,10 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
||||
vars.append({ { "$FAKE_WEB_SITE_ADDRESS",
|
||||
cloakConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite) } });
|
||||
|
||||
// Xray vars
|
||||
vars.append({ { "$XRAY_SITE_NAME",
|
||||
xrayConfig.value(config_key::site).toString(protocols::xray::defaultSite) } });
|
||||
|
||||
// Wireguard vars
|
||||
vars.append(
|
||||
{ { "$WIREGUARD_SUBNET_IP",
|
||||
@@ -652,7 +652,7 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
||||
vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER",
|
||||
amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } });
|
||||
|
||||
QString serverIp = Utils::getIPAddress(credentials.hostName);
|
||||
QString serverIp = NetworkUtilities::getIPAddress(credentials.hostName);
|
||||
if (!serverIp.isEmpty()) {
|
||||
vars.append({ { "$SERVER_IP_ADDRESS", serverIp } });
|
||||
} else {
|
||||
|
||||
@@ -59,6 +59,8 @@ namespace amnezia
|
||||
CloakExecutableMissing = 602,
|
||||
AmneziaServiceConnectionFailed = 603,
|
||||
ExecutableMissing = 604,
|
||||
XrayExecutableMissing = 605,
|
||||
Tun2SockExecutableMissing = 606,
|
||||
|
||||
// VPN errors
|
||||
OpenVpnAdaptersInUseError = 700,
|
||||
@@ -70,6 +72,8 @@ namespace amnezia
|
||||
OpenSslFailed = 800,
|
||||
ShadowSocksExecutableCrashed = 801,
|
||||
CloakExecutableCrashed = 802,
|
||||
XrayExecutableCrashed = 803,
|
||||
Tun2SockExecutableCrashed = 804,
|
||||
|
||||
// import and install errors
|
||||
ImportInvalidConfigError = 900,
|
||||
|
||||
@@ -19,6 +19,7 @@ QString errorString(ErrorCode code) {
|
||||
case(ServerDockerFailedError): errorMessage = QObject::tr("Server error: Docker failed"); break;
|
||||
case(ServerCancelInstallation): errorMessage = QObject::tr("Installation canceled by user"); break;
|
||||
case(ServerUserNotInSudo): errorMessage = QObject::tr("The user does not have permission to use sudo"); break;
|
||||
case(ServerPacketManagerError): errorMessage = QObject::tr("Server error: Packet manager error"); break;
|
||||
|
||||
// Libssh errors
|
||||
case(SshRequestDeniedError): errorMessage = QObject::tr("Ssh request was denied"); break;
|
||||
|
||||
@@ -71,7 +71,7 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
|
||||
}
|
||||
|
||||
QRemoteObjectPendingReply<int> futureResult = Instance()->m_ipcClient->createPrivilegedProcess();
|
||||
futureResult.waitForFinished(1000);
|
||||
futureResult.waitForFinished(5000);
|
||||
|
||||
int pid = futureResult.returnValue();
|
||||
|
||||
|
||||
@@ -0,0 +1,439 @@
|
||||
#include "networkUtilities.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <windows.h>
|
||||
#include <Ipexport.h>
|
||||
#include <Ws2tcpip.h>
|
||||
#include <ws2ipdef.h>
|
||||
#include <stdint.h>
|
||||
#include <Iphlpapi.h>
|
||||
#include <Iptypes.h>
|
||||
#include <WinSock2.h>
|
||||
#include <winsock.h>
|
||||
#endif
|
||||
#ifdef Q_OS_LINUX
|
||||
#include <arpa/inet.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
|
||||
#include <sys/param.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <net/route.h>
|
||||
#endif
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QHostInfo>
|
||||
|
||||
QRegularExpression NetworkUtilities::ipAddressRegExp()
|
||||
{
|
||||
return QRegularExpression("^((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])(\\.(?!$)|$)){4}$");
|
||||
}
|
||||
|
||||
QRegularExpression NetworkUtilities::ipAddressPortRegExp()
|
||||
{
|
||||
return QRegularExpression("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}"
|
||||
"(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\:[0-9]{1,5}){0,1}$");
|
||||
}
|
||||
|
||||
QRegExp NetworkUtilities::ipAddressWithSubnetRegExp()
|
||||
{
|
||||
return QRegExp("(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}"
|
||||
"(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\/[0-9]{1,2}){0,1}");
|
||||
}
|
||||
|
||||
QRegExp NetworkUtilities::ipNetwork24RegExp()
|
||||
{
|
||||
return QRegExp("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}"
|
||||
"0$");
|
||||
}
|
||||
|
||||
QRegExp NetworkUtilities::ipPortRegExp()
|
||||
{
|
||||
return QRegExp("^()([1-9]|[1-5]?[0-9]{2,4}|6[1-4][0-9]{3}|65[1-4][0-9]{2}|655[1-2][0-9]|6553[1-5])$");
|
||||
}
|
||||
|
||||
QRegExp NetworkUtilities::domainRegExp()
|
||||
{
|
||||
return QRegExp("(((?!\\-))(xn\\-\\-)?[a-z0-9\\-_]{0,61}[a-z0-9]{1,1}\\.)*(xn\\-\\-)?([a-z0-9\\-]{1,61}|[a-z0-"
|
||||
"9\\-]{1,30})\\.[a-z]{2,}");
|
||||
}
|
||||
|
||||
QString NetworkUtilities::netMaskFromIpWithSubnet(const QString ip)
|
||||
{
|
||||
if (!ip.contains("/"))
|
||||
return "255.255.255.255";
|
||||
|
||||
bool ok;
|
||||
int prefix = ip.split("/").at(1).toInt(&ok);
|
||||
if (!ok)
|
||||
return "255.255.255.255";
|
||||
|
||||
unsigned long mask = (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF;
|
||||
|
||||
return QString("%1.%2.%3.%4").arg(mask >> 24).arg((mask >> 16) & 0xFF).arg((mask >> 8) & 0xFF).arg(mask & 0xFF);
|
||||
}
|
||||
|
||||
QString NetworkUtilities::ipAddressFromIpWithSubnet(const QString ip)
|
||||
{
|
||||
if (ip.count(".") != 3)
|
||||
return "";
|
||||
return ip.split("/").first();
|
||||
}
|
||||
|
||||
QStringList NetworkUtilities::summarizeRoutes(const QStringList &ips, const QString cidr)
|
||||
{
|
||||
// QMap<int, int>
|
||||
// QHostAddress
|
||||
|
||||
// QMap<QString, QStringList> subnets; // <"a.b", <list subnets>>
|
||||
|
||||
// for (const QString &ip : ips) {
|
||||
// if (ip.count(".") != 3) continue;
|
||||
|
||||
// const QStringList &parts = ip.split(".");
|
||||
// subnets[parts.at(0) + "." + parts.at(1)].append(ip);
|
||||
// }
|
||||
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
QString NetworkUtilities::getIPAddress(const QString &host)
|
||||
{
|
||||
if (ipAddressRegExp().match(host).hasMatch()) {
|
||||
return host;
|
||||
}
|
||||
|
||||
QList<QHostAddress> addresses = QHostInfo::fromName(host).addresses();
|
||||
if (!addresses.isEmpty()) {
|
||||
return addresses.first().toString();
|
||||
}
|
||||
qDebug() << "Unable to resolve address for " << host;
|
||||
return "";
|
||||
}
|
||||
|
||||
QString NetworkUtilities::getStringBetween(const QString &s, const QString &a, const QString &b)
|
||||
{
|
||||
int ap = s.indexOf(a), bp = s.indexOf(b, ap + a.length());
|
||||
if (ap < 0 || bp < 0)
|
||||
return QString();
|
||||
ap += a.length();
|
||||
if (bp - ap <= 0)
|
||||
return QString();
|
||||
return s.mid(ap, bp - ap).trimmed();
|
||||
}
|
||||
|
||||
bool NetworkUtilities::checkIPv4Format(const QString &ip)
|
||||
{
|
||||
if (ip.isEmpty())
|
||||
return false;
|
||||
int count = ip.count(".");
|
||||
if (count != 3)
|
||||
return false;
|
||||
|
||||
QHostAddress addr(ip);
|
||||
return (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol);
|
||||
}
|
||||
|
||||
bool NetworkUtilities::checkIpSubnetFormat(const QString &ip)
|
||||
{
|
||||
if (!ip.contains("/"))
|
||||
return checkIPv4Format(ip);
|
||||
|
||||
QStringList parts = ip.split("/");
|
||||
if (parts.size() != 2)
|
||||
return false;
|
||||
|
||||
bool ok;
|
||||
int subnet = parts.at(1).toInt(&ok);
|
||||
if (subnet >= 0 && subnet <= 32 && ok)
|
||||
return checkIPv4Format(parts.at(0));
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
DWORD GetAdaptersAddressesWrapper(const ULONG Family,
|
||||
const ULONG Flags,
|
||||
const PVOID Reserved,
|
||||
_Out_ PIP_ADAPTER_ADDRESSES& pAdapterAddresses) {
|
||||
DWORD dwRetVal = 0;
|
||||
int iter = 0;
|
||||
constexpr int max_iter = 3;
|
||||
ULONG AdapterAddressesLen = 15000;
|
||||
do {
|
||||
// xassert2(pAdapterAddresses == nullptr);
|
||||
pAdapterAddresses = (IP_ADAPTER_ADDRESSES*)malloc(AdapterAddressesLen);
|
||||
if (pAdapterAddresses == nullptr) {
|
||||
qDebug() << "can not malloc" << AdapterAddressesLen << "bytes";
|
||||
return ERROR_OUTOFMEMORY;
|
||||
}
|
||||
|
||||
dwRetVal = GetAdaptersAddresses(Family, Flags, NULL, pAdapterAddresses, &AdapterAddressesLen);
|
||||
|
||||
if (dwRetVal == ERROR_BUFFER_OVERFLOW) {
|
||||
free(pAdapterAddresses);
|
||||
pAdapterAddresses = nullptr;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
iter++;
|
||||
} while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (iter < max_iter));
|
||||
|
||||
if (dwRetVal != NO_ERROR) {
|
||||
qDebug() << "Family: " << Family << ", Flags: " << Flags << " AdapterAddressesLen: " << AdapterAddressesLen <<
|
||||
", dwRetVal:" << dwRetVal << ", iter: " << iter;
|
||||
if (pAdapterAddresses) {
|
||||
free(pAdapterAddresses);
|
||||
pAdapterAddresses = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return dwRetVal;
|
||||
}
|
||||
#endif
|
||||
|
||||
QString NetworkUtilities::getGatewayAndIface()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
constexpr int BUFF_LEN = 100;
|
||||
char buff[BUFF_LEN] = {'\0'};
|
||||
QString result;
|
||||
|
||||
PIP_ADAPTER_ADDRESSES pAdapterAddresses = nullptr;
|
||||
DWORD dwRetVal =
|
||||
GetAdaptersAddressesWrapper(AF_INET, GAA_FLAG_INCLUDE_GATEWAYS, NULL, pAdapterAddresses);
|
||||
|
||||
if (dwRetVal != NO_ERROR) {
|
||||
qDebug() << "ipv4 stack detect GetAdaptersAddresses failed.";
|
||||
return "";
|
||||
}
|
||||
|
||||
PIP_ADAPTER_ADDRESSES pCurAddress = pAdapterAddresses;
|
||||
while (pCurAddress) {
|
||||
PIP_ADAPTER_GATEWAY_ADDRESS_LH gateway = pCurAddress->FirstGatewayAddress;
|
||||
if (gateway) {
|
||||
SOCKET_ADDRESS gateway_address = gateway->Address;
|
||||
if (gateway->Address.lpSockaddr->sa_family == AF_INET) {
|
||||
sockaddr_in* sa_in = (sockaddr_in*)gateway->Address.lpSockaddr;
|
||||
QString gw = inet_ntop(AF_INET, &(sa_in->sin_addr), buff, BUFF_LEN);
|
||||
qDebug() << "gateway IPV4:" << gw;
|
||||
struct sockaddr_in addr;
|
||||
if (inet_pton(AF_INET, buff, &addr.sin_addr) == 1) {
|
||||
qDebug() << "this is true v4 !";
|
||||
result = gw;
|
||||
}
|
||||
}
|
||||
}
|
||||
pCurAddress = pCurAddress->Next;
|
||||
}
|
||||
|
||||
free(pAdapterAddresses);
|
||||
return result;
|
||||
#endif
|
||||
#ifdef Q_OS_LINUX
|
||||
constexpr int BUFFER_SIZE = 100;
|
||||
int received_bytes = 0, msg_len = 0, route_attribute_len = 0;
|
||||
int sock = -1, msgseq = 0;
|
||||
struct nlmsghdr *nlh, *nlmsg;
|
||||
struct rtmsg *route_entry;
|
||||
// This struct contain route attributes (route type)
|
||||
struct rtattr *route_attribute;
|
||||
char gateway_address[INET_ADDRSTRLEN], interface[IF_NAMESIZE];
|
||||
char msgbuf[BUFFER_SIZE], buffer[BUFFER_SIZE];
|
||||
char *ptr = buffer;
|
||||
struct timeval tv;
|
||||
|
||||
if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
|
||||
perror("socket failed");
|
||||
return "";
|
||||
}
|
||||
|
||||
memset(msgbuf, 0, sizeof(msgbuf));
|
||||
memset(gateway_address, 0, sizeof(gateway_address));
|
||||
memset(interface, 0, sizeof(interface));
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
|
||||
/* point the header and the msg structure pointers into the buffer */
|
||||
nlmsg = (struct nlmsghdr *)msgbuf;
|
||||
|
||||
/* Fill in the nlmsg header*/
|
||||
nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
|
||||
nlmsg->nlmsg_type = RTM_GETROUTE; // Get the routes from kernel routing table .
|
||||
nlmsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; // The message is a request for dump.
|
||||
nlmsg->nlmsg_seq = msgseq++; // Sequence of the message packet.
|
||||
nlmsg->nlmsg_pid = getpid(); // PID of process sending the request.
|
||||
|
||||
/* 1 Sec Timeout to avoid stall */
|
||||
tv.tv_sec = 1;
|
||||
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (struct timeval *)&tv, sizeof(struct timeval));
|
||||
/* send msg */
|
||||
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
|
||||
perror("send failed");
|
||||
return "";
|
||||
}
|
||||
|
||||
/* receive response */
|
||||
do
|
||||
{
|
||||
received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0);
|
||||
if (received_bytes < 0) {
|
||||
perror("Error in recv");
|
||||
return "";
|
||||
}
|
||||
|
||||
nlh = (struct nlmsghdr *) ptr;
|
||||
|
||||
/* Check if the header is valid */
|
||||
if((NLMSG_OK(nlmsg, received_bytes) == 0) ||
|
||||
(nlmsg->nlmsg_type == NLMSG_ERROR))
|
||||
{
|
||||
perror("Error in received packet");
|
||||
return "";
|
||||
}
|
||||
|
||||
/* If we received all data break */
|
||||
if (nlh->nlmsg_type == NLMSG_DONE)
|
||||
break;
|
||||
else {
|
||||
ptr += received_bytes;
|
||||
msg_len += received_bytes;
|
||||
}
|
||||
|
||||
/* Break if its not a multi part message */
|
||||
if ((nlmsg->nlmsg_flags & NLM_F_MULTI) == 0)
|
||||
break;
|
||||
}
|
||||
while ((nlmsg->nlmsg_seq != msgseq) || (nlmsg->nlmsg_pid != getpid()));
|
||||
|
||||
/* parse response */
|
||||
for ( ; NLMSG_OK(nlh, received_bytes); nlh = NLMSG_NEXT(nlh, received_bytes))
|
||||
{
|
||||
/* Get the route data */
|
||||
route_entry = (struct rtmsg *) NLMSG_DATA(nlh);
|
||||
|
||||
/* We are just interested in main routing table */
|
||||
if (route_entry->rtm_table != RT_TABLE_MAIN)
|
||||
continue;
|
||||
|
||||
route_attribute = (struct rtattr *) RTM_RTA(route_entry);
|
||||
route_attribute_len = RTM_PAYLOAD(nlh);
|
||||
|
||||
/* Loop through all attributes */
|
||||
for ( ; RTA_OK(route_attribute, route_attribute_len);
|
||||
route_attribute = RTA_NEXT(route_attribute, route_attribute_len))
|
||||
{
|
||||
switch(route_attribute->rta_type) {
|
||||
case RTA_OIF:
|
||||
if_indextoname(*(int *)RTA_DATA(route_attribute), interface);
|
||||
break;
|
||||
case RTA_GATEWAY:
|
||||
inet_ntop(AF_INET, RTA_DATA(route_attribute),
|
||||
gateway_address, sizeof(gateway_address));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((*gateway_address) && (*interface)) {
|
||||
qDebug() << "Gateway " << gateway_address << " for interface " << interface;
|
||||
break;
|
||||
}
|
||||
}
|
||||
close(sock);
|
||||
return gateway_address;
|
||||
#endif
|
||||
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
|
||||
QString gateway;
|
||||
int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_FLAGS, RTF_GATEWAY};
|
||||
int afinet_type[] = {AF_INET, AF_INET6};
|
||||
|
||||
for (int ip_type = 0; ip_type <= 1; ip_type++)
|
||||
{
|
||||
mib[3] = afinet_type[ip_type];
|
||||
|
||||
size_t needed = 0;
|
||||
if (sysctl(mib, sizeof(mib) / sizeof(int), nullptr, &needed, nullptr, 0) < 0)
|
||||
return "";
|
||||
|
||||
char* buf;
|
||||
if ((buf = new char[needed]) == 0)
|
||||
return "";
|
||||
|
||||
if (sysctl(mib, sizeof(mib) / sizeof(int), buf, &needed, nullptr, 0) < 0)
|
||||
{
|
||||
qDebug() << "sysctl: net.route.0.0.dump";
|
||||
delete[] buf;
|
||||
return gateway;
|
||||
}
|
||||
|
||||
struct rt_msghdr* rt;
|
||||
for (char* p = buf; p < buf + needed; p += rt->rtm_msglen)
|
||||
{
|
||||
rt = reinterpret_cast<struct rt_msghdr*>(p);
|
||||
struct sockaddr* sa = reinterpret_cast<struct sockaddr*>(rt + 1);
|
||||
struct sockaddr* sa_tab[RTAX_MAX];
|
||||
for (int i = 0; i < RTAX_MAX; i++)
|
||||
{
|
||||
if (rt->rtm_addrs & (1 << i))
|
||||
{
|
||||
sa_tab[i] = sa;
|
||||
sa = reinterpret_cast<struct sockaddr*>(
|
||||
reinterpret_cast<char*>(sa) +
|
||||
((sa->sa_len) > 0 ? (1 + (((sa->sa_len) - 1) | (sizeof(long) - 1))) : sizeof(long)));
|
||||
}
|
||||
else
|
||||
{
|
||||
sa_tab[i] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (((rt->rtm_addrs & (RTA_DST | RTA_GATEWAY)) == (RTA_DST | RTA_GATEWAY)) &&
|
||||
sa_tab[RTAX_DST]->sa_family == afinet_type[ip_type] &&
|
||||
sa_tab[RTAX_GATEWAY]->sa_family == afinet_type[ip_type])
|
||||
{
|
||||
if (afinet_type[ip_type] == AF_INET)
|
||||
{
|
||||
if ((reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0)
|
||||
{
|
||||
char dstStr4[INET_ADDRSTRLEN];
|
||||
char srcStr4[INET_ADDRSTRLEN];
|
||||
memcpy(srcStr4,
|
||||
&(reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_GATEWAY]))->sin_addr,
|
||||
sizeof(struct in_addr));
|
||||
if (inet_ntop(AF_INET, srcStr4, dstStr4, INET_ADDRSTRLEN) != nullptr)
|
||||
gateway = dstStr4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (afinet_type[ip_type] == AF_INET6)
|
||||
{
|
||||
if ((reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0)
|
||||
{
|
||||
char dstStr6[INET6_ADDRSTRLEN];
|
||||
char srcStr6[INET6_ADDRSTRLEN];
|
||||
memcpy(srcStr6,
|
||||
&(reinterpret_cast<struct sockaddr_in6*>(sa_tab[RTAX_GATEWAY]))->sin6_addr,
|
||||
sizeof(struct in6_addr));
|
||||
if (inet_ntop(AF_INET6, srcStr6, dstStr6, INET6_ADDRSTRLEN) != nullptr)
|
||||
gateway = dstStr6;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
free(buf);
|
||||
}
|
||||
|
||||
return gateway;
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#ifndef NETWORKUTILITIES_H
|
||||
#define NETWORKUTILITIES_H
|
||||
|
||||
#include <QRegularExpression>
|
||||
#include <QRegExp>
|
||||
#include <QString>
|
||||
|
||||
class NetworkUtilities : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static QString getIPAddress(const QString &host);
|
||||
static QString getStringBetween(const QString &s, const QString &a, const QString &b);
|
||||
static bool checkIPv4Format(const QString &ip);
|
||||
static bool checkIpSubnetFormat(const QString &ip);
|
||||
static QString getGatewayAndIface();
|
||||
|
||||
static QRegularExpression ipAddressRegExp();
|
||||
static QRegularExpression ipAddressPortRegExp();
|
||||
static QRegExp ipAddressWithSubnetRegExp();
|
||||
static QRegExp ipNetwork24RegExp();
|
||||
static QRegExp ipPortRegExp();
|
||||
static QRegExp domainRegExp();
|
||||
|
||||
static QString netMaskFromIpWithSubnet(const QString ip);
|
||||
static QString ipAddressFromIpWithSubnet(const QString ip);
|
||||
|
||||
static QStringList summarizeRoutes(const QStringList &ips, const QString cidr);
|
||||
|
||||
};
|
||||
|
||||
#endif // NETWORKUTILITIES_H
|
||||
@@ -13,11 +13,12 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container)
|
||||
case DockerContainer::WireGuard: return QLatin1String("wireguard");
|
||||
case DockerContainer::Awg: return QLatin1String("awg");
|
||||
case DockerContainer::Ipsec: return QLatin1String("ipsec");
|
||||
case DockerContainer::Xray: return QLatin1String("xray");
|
||||
|
||||
case DockerContainer::TorWebSite: return QLatin1String("website_tor");
|
||||
case DockerContainer::Dns: return QLatin1String("dns");
|
||||
case DockerContainer::Sftp: return QLatin1String("sftp");
|
||||
default: return "";
|
||||
default: return QString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +48,7 @@ QString amnezia::scriptName(ProtocolScriptType type)
|
||||
case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn");
|
||||
case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf");
|
||||
case ProtocolScriptType::awg_template: return QLatin1String("template.conf");
|
||||
case ProtocolScriptType::xray_template: return QLatin1String("template.json");
|
||||
default: return QString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,8 @@ enum ProtocolScriptType {
|
||||
container_startup,
|
||||
openvpn_template,
|
||||
wireguard_template,
|
||||
awg_template
|
||||
awg_template,
|
||||
xray_template
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 8V12" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 16H12.01" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 518 B |
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 22H18C18.5304 22 19.0391 21.7893 19.4142 21.4142C19.7893 21.0391 20 20.5304 20 20V7.5L14.5 2H6C5.46957 2 4.96086 2.21071 4.58579 2.58579C4.21071 2.96086 4 3.46957 4 4V8" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14 2V8H20" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 15L5 17L9 13" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 581 B |
@@ -84,7 +84,8 @@ target_sources(networkextension PRIVATE
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+OpenVPNAdapterDelegate.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+WireGuard.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+OpenVPN.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/WGConfig.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/iosglue.mm
|
||||
)
|
||||
|
||||
@@ -124,10 +124,14 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
// json.insert("hopindex", QJsonValue((double)hop.m_hopindex));
|
||||
json.insert("privateKey", wgConfig.value(amnezia::config_key::client_priv_key));
|
||||
json.insert("deviceIpv4Address", wgConfig.value(amnezia::config_key::client_ip));
|
||||
// todo review wg ipv6
|
||||
#ifdef Q_OS_MACOS
|
||||
json.insert("deviceIpv6Address", "dead::1");
|
||||
#endif
|
||||
|
||||
// set up IPv6 unique-local-address, ULA, with "fd00::/8" prefix, not globally routable.
|
||||
// this will be default IPv6 gateway, OS recognizes that IPv6 link is local and switches to IPv4.
|
||||
// Otherwise some OSes (Linux) try IPv6 forever and hang.
|
||||
// https://en.wikipedia.org/wiki/Unique_local_address (RFC 4193)
|
||||
// https://man7.org/linux/man-pages/man5/gai.conf.5.html
|
||||
json.insert("deviceIpv6Address", "fd58:baa6:dead::1"); // simply "dead::1" is globally-routable, don't use it
|
||||
|
||||
json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key));
|
||||
json.insert("serverPskKey", wgConfig.value(amnezia::config_key::psk_key));
|
||||
json.insert("serverIpv4AddrIn", wgConfig.value(amnezia::config_key::hostName));
|
||||
|
||||
@@ -2,6 +2,8 @@ import Foundation
|
||||
import os.log
|
||||
|
||||
struct Log {
|
||||
static let osLog = Logger()
|
||||
|
||||
private static let IsLoggingEnabledKey = "IsLoggingEnabled"
|
||||
static var isLoggingEnabled: Bool {
|
||||
get {
|
||||
@@ -29,16 +31,23 @@ struct Log {
|
||||
return dateFormatter
|
||||
}()
|
||||
|
||||
var records: [Record]
|
||||
var records = [Record]()
|
||||
|
||||
var lastRecordDate = Date.distantPast
|
||||
|
||||
init() {
|
||||
self.records = []
|
||||
}
|
||||
|
||||
init(_ str: String) {
|
||||
self.records = str.split(whereSeparator: \.isNewline)
|
||||
.compactMap {
|
||||
Record(String($0))
|
||||
records = str.split(whereSeparator: \.isNewline)
|
||||
.map {
|
||||
if let record = Record(String($0)) {
|
||||
lastRecordDate = record.date
|
||||
return record
|
||||
} else {
|
||||
return Record(date: lastRecordDate, level: .error, message: "LOG: \($0)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +69,24 @@ struct Log {
|
||||
self.init(str)
|
||||
}
|
||||
|
||||
static func log(_ type: OSLogType, title: String = "", message: String, url: URL = neLogURL) {
|
||||
guard isLoggingEnabled else { return }
|
||||
|
||||
let date = Date()
|
||||
let level = Record.Level(from: type)
|
||||
let messages = message.split(whereSeparator: \.isNewline)
|
||||
|
||||
for index in 0..<messages.count {
|
||||
let message = String(messages[index])
|
||||
|
||||
if index != 0 && message.first != " " {
|
||||
Record(date: date, level: level, message: "\(title) \(message)").save(at: url)
|
||||
} else {
|
||||
Record(date: date, level: level, message: "\(title)\(message)").save(at: url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func clear(at url: URL) {
|
||||
if FileManager.default.fileExists(atPath: url.path) {
|
||||
guard let fileHandle = try? FileHandle(forUpdating: url) else { return }
|
||||
|
||||
@@ -30,6 +30,8 @@ extension Log {
|
||||
}
|
||||
|
||||
func save(at url: URL) {
|
||||
osLog.log(level: level.osLogType, "\(message)")
|
||||
|
||||
guard let data = "\n\(description)".data(using: .utf8) else { return }
|
||||
|
||||
if !FileManager.default.fileExists(atPath: url.path) {
|
||||
@@ -64,19 +66,38 @@ extension Log.Record {
|
||||
|
||||
init(from osLogType: OSLogType) {
|
||||
switch osLogType {
|
||||
case OSLogType.default:
|
||||
case .default:
|
||||
self = .info
|
||||
case OSLogType.info:
|
||||
case .info:
|
||||
self = .info
|
||||
case OSLogType.debug:
|
||||
case .debug:
|
||||
self = .debug
|
||||
case OSLogType.error:
|
||||
case .error:
|
||||
self = .error
|
||||
case OSLogType.fault:
|
||||
case .fault:
|
||||
self = .fatal
|
||||
default:
|
||||
self = .info
|
||||
}
|
||||
}
|
||||
|
||||
var osLogType: OSLogType {
|
||||
switch self {
|
||||
case .info:
|
||||
return .info
|
||||
case .debug:
|
||||
return .debug
|
||||
case .error:
|
||||
return .error
|
||||
case .fatal:
|
||||
return .fault
|
||||
case .warning:
|
||||
return .info
|
||||
case .critical:
|
||||
return .fault
|
||||
case .system:
|
||||
return .fault
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
public func wg_log(_ type: OSLogType, staticMessage: StaticString) {
|
||||
guard Log.isLoggingEnabled else { return }
|
||||
|
||||
Log.Record(date: Date(), level: Log.Record.Level(from: type), message: "\(staticMessage)").save(at: Log.neLogURL)
|
||||
public func wg_log(_ type: OSLogType, title: String = "", staticMessage: StaticString) {
|
||||
neLog(type, title: "WG: \(title)", message: "\(staticMessage)")
|
||||
}
|
||||
|
||||
public func wg_log(_ type: OSLogType, message: String) {
|
||||
log(type, message: message)
|
||||
public func wg_log(_ type: OSLogType, title: String = "", message: String) {
|
||||
neLog(type, title: "WG: \(title)", message: message)
|
||||
}
|
||||
|
||||
public func log(_ type: OSLogType, message: String) {
|
||||
guard Log.isLoggingEnabled else { return }
|
||||
|
||||
Log.Record(date: Date(), level: Log.Record.Level(from: type), message: message).save(at: Log.neLogURL)
|
||||
public func ovpnLog(_ type: OSLogType, title: String = "", message: String) {
|
||||
neLog(type, title: "OVPN: \(title)", message: message)
|
||||
}
|
||||
|
||||
public func neLog(_ type: OSLogType, title: String = "", message: String) {
|
||||
Log.log(type, title: "NE: \(title)", message: message)
|
||||
}
|
||||
|
||||
+103
-1
@@ -2,6 +2,106 @@ import Foundation
|
||||
import NetworkExtension
|
||||
import OpenVPNAdapter
|
||||
|
||||
struct OpenVPNConfig: Decodable {
|
||||
let config: String
|
||||
let splitTunnelType: Int
|
||||
let splitTunnelSites: [String]
|
||||
|
||||
var str: String {
|
||||
"splitTunnelType: \(splitTunnelType) splitTunnelSites: \(splitTunnelSites) config: \(config)"
|
||||
}
|
||||
}
|
||||
|
||||
extension PacketTunnelProvider {
|
||||
func startOpenVPN(completionHandler: @escaping (Error?) -> Void) {
|
||||
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
||||
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
||||
let openVPNConfigData = providerConfiguration[Constants.ovpnConfigKey] as? Data else {
|
||||
ovpnLog(.error, message: "Can't start")
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
// ovpnLog(.info, message: "providerConfiguration: \(String(decoding: openVPNConfigData, as: UTF8.self))")
|
||||
|
||||
let openVPNConfig = try JSONDecoder().decode(OpenVPNConfig.self, from: openVPNConfigData)
|
||||
ovpnLog(.info, title: "config: ", message: openVPNConfig.str)
|
||||
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
|
||||
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
|
||||
} catch {
|
||||
ovpnLog(.error, message: "Can't parse config: \(error.localizedDescription)")
|
||||
|
||||
if let underlyingError = (error as NSError).userInfo[NSUnderlyingErrorKey] as? NSError {
|
||||
ovpnLog(.error, message: "Can't parse config: \(underlyingError.localizedDescription)")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data,
|
||||
withShadowSocks viaSS: Bool = false,
|
||||
completionHandler: @escaping (Error?) -> Void) {
|
||||
ovpnLog(.info, message: "Setup and launch")
|
||||
|
||||
let str = String(decoding: ovpnConfiguration, as: UTF8.self)
|
||||
|
||||
let configuration = OpenVPNConfiguration()
|
||||
configuration.fileContent = ovpnConfiguration
|
||||
if str.contains("cloak") {
|
||||
configuration.setPTCloak()
|
||||
}
|
||||
|
||||
let evaluation: OpenVPNConfigurationEvaluation
|
||||
do {
|
||||
evaluation = try ovpnAdapter.apply(configuration: configuration)
|
||||
|
||||
} catch {
|
||||
completionHandler(error)
|
||||
return
|
||||
}
|
||||
|
||||
if !evaluation.autologin {
|
||||
ovpnLog(.info, message: "Implement login with user credentials")
|
||||
}
|
||||
|
||||
vpnReachability.startTracking { [weak self] status in
|
||||
guard status == .reachableViaWiFi else { return }
|
||||
self?.ovpnAdapter.reconnect(afterTimeInterval: 5)
|
||||
}
|
||||
|
||||
startHandler = completionHandler
|
||||
ovpnAdapter.connect(using: packetFlow)
|
||||
|
||||
// let ifaces = Interface.allInterfaces()
|
||||
// .filter { $0.family == .ipv4 }
|
||||
// .map { iface in iface.name }
|
||||
|
||||
// ovpn_log(.error, message: "Available TUN Interfaces: \(ifaces)")
|
||||
}
|
||||
|
||||
func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||
guard let completionHandler = completionHandler else { return }
|
||||
let bytesin = ovpnAdapter.transportStatistics.bytesIn
|
||||
let bytesout = ovpnAdapter.transportStatistics.bytesOut
|
||||
|
||||
let response: [String: Any] = [
|
||||
"rx_bytes": bytesin,
|
||||
"tx_bytes": bytesout
|
||||
]
|
||||
|
||||
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
||||
}
|
||||
|
||||
func stopOpenVPN(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||
stopHandler = completionHandler
|
||||
if vpnReachability.isTracking {
|
||||
vpnReachability.stopTracking()
|
||||
}
|
||||
ovpnAdapter.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
||||
// OpenVPNAdapter calls this delegate method to configure a VPN tunnel.
|
||||
// `completionHandler` callback requires an object conforming to `OpenVPNAdapterPacketFlow`
|
||||
@@ -116,6 +216,8 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
||||
// Use this method to process any log message returned by OpenVPN library.
|
||||
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {
|
||||
// Handle log messages
|
||||
wg_log(.info, message: logMessage)
|
||||
ovpnLog(.info, message: logMessage)
|
||||
}
|
||||
}
|
||||
|
||||
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
|
||||
@@ -0,0 +1,221 @@
|
||||
import Foundation
|
||||
import NetworkExtension
|
||||
|
||||
extension PacketTunnelProvider {
|
||||
func startWireguard(activationAttemptId: String?,
|
||||
errorNotifier: ErrorNotifier,
|
||||
completionHandler: @escaping (Error?) -> Void) {
|
||||
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
||||
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
||||
let wgConfigData: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else {
|
||||
wg_log(.error, message: "Can't start, config missing")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let wgConfig = try JSONDecoder().decode(WGConfig.self, from: wgConfigData)
|
||||
let wgConfigStr = wgConfig.str
|
||||
wg_log(.info, title: "config: ", message: wgConfig.redux)
|
||||
|
||||
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: wgConfigStr)
|
||||
|
||||
if tunnelConfiguration.peers.first!.allowedIPs
|
||||
.map({ $0.stringRepresentation })
|
||||
.joined(separator: ", ") == "0.0.0.0/0, ::/0" {
|
||||
if wgConfig.splitTunnelType == 1 {
|
||||
for index in tunnelConfiguration.peers.indices {
|
||||
tunnelConfiguration.peers[index].allowedIPs.removeAll()
|
||||
var allowedIPs = [IPAddressRange]()
|
||||
|
||||
for allowedIPString in wgConfig.splitTunnelSites {
|
||||
if let allowedIP = IPAddressRange(from: allowedIPString) {
|
||||
allowedIPs.append(allowedIP)
|
||||
}
|
||||
}
|
||||
|
||||
tunnelConfiguration.peers[index].allowedIPs = allowedIPs
|
||||
}
|
||||
} else if wgConfig.splitTunnelType == 2 {
|
||||
for index in tunnelConfiguration.peers.indices {
|
||||
var excludeIPs = [IPAddressRange]()
|
||||
|
||||
for excludeIPString in wgConfig.splitTunnelSites {
|
||||
if let excludeIP = IPAddressRange(from: excludeIPString) {
|
||||
excludeIPs.append(excludeIP)
|
||||
}
|
||||
}
|
||||
|
||||
tunnelConfiguration.peers[index].excludeIPs = excludeIPs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wg_log(.info, message: "Starting tunnel from the " +
|
||||
(activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
|
||||
|
||||
// Start the tunnel
|
||||
wgAdapter.start(tunnelConfiguration: tunnelConfiguration) { adapterError in
|
||||
guard let adapterError else {
|
||||
let interfaceName = self.wgAdapter.interfaceName ?? "unknown"
|
||||
wg_log(.info, message: "Tunnel interface is \(interfaceName)")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
switch adapterError {
|
||||
case .cannotLocateTunnelFileDescriptor:
|
||||
wg_log(.error, staticMessage: "Starting tunnel failed: could not determine file descriptor")
|
||||
errorNotifier.notify(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
||||
completionHandler(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
||||
case .dnsResolution(let dnsErrors):
|
||||
let hostnamesWithDnsResolutionFailure = dnsErrors.map { $0.address }
|
||||
.joined(separator: ", ")
|
||||
wg_log(.error, message:
|
||||
"DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure)")
|
||||
errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure)
|
||||
completionHandler(PacketTunnelProviderError.dnsResolutionFailure)
|
||||
case .setNetworkSettings(let error):
|
||||
wg_log(.error, message:
|
||||
"Starting tunnel failed with setTunnelNetworkSettings returning \(error.localizedDescription)")
|
||||
errorNotifier.notify(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
||||
completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
||||
case .startWireGuardBackend(let errorCode):
|
||||
wg_log(.error, message: "Starting tunnel failed with wgTurnOn returning \(errorCode)")
|
||||
errorNotifier.notify(PacketTunnelProviderError.couldNotStartBackend)
|
||||
completionHandler(PacketTunnelProviderError.couldNotStartBackend)
|
||||
case .invalidState:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
wg_log(.error, message: "Can't parse WG config: \(error.localizedDescription)")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||
guard let completionHandler = completionHandler else { return }
|
||||
wgAdapter.getRuntimeConfiguration { settings in
|
||||
var data: Data?
|
||||
if let settings {
|
||||
data = settings.data(using: .utf8)!
|
||||
}
|
||||
|
||||
let components = settings!.components(separatedBy: "\n")
|
||||
|
||||
var settingsDictionary: [String: String] = [:]
|
||||
for component in components {
|
||||
let pair = component.components(separatedBy: "=")
|
||||
if pair.count == 2 {
|
||||
settingsDictionary[pair[0]] = pair[1]
|
||||
}
|
||||
}
|
||||
|
||||
let response: [String: Any] = [
|
||||
"rx_bytes": settingsDictionary["rx_bytes"] ?? "0",
|
||||
"tx_bytes": settingsDictionary["tx_bytes"] ?? "0"
|
||||
]
|
||||
|
||||
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
||||
}
|
||||
}
|
||||
|
||||
private func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||
guard let completionHandler = completionHandler else { return }
|
||||
if messageData.count == 1 && messageData[0] == 0 {
|
||||
wgAdapter.getRuntimeConfiguration { settings in
|
||||
var data: Data?
|
||||
if let settings {
|
||||
data = settings.data(using: .utf8)!
|
||||
}
|
||||
completionHandler(data)
|
||||
}
|
||||
} else if messageData.count >= 1 {
|
||||
// Updates the tunnel configuration and responds with the active configuration
|
||||
wg_log(.info, message: "Switching tunnel configuration")
|
||||
guard let configString = String(data: messageData, encoding: .utf8)
|
||||
else {
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString)
|
||||
wgAdapter.update(tunnelConfiguration: tunnelConfiguration) { error in
|
||||
if let error {
|
||||
wg_log(.error, message: "Failed to switch tunnel configuration: \(error.localizedDescription)")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
self.wgAdapter.getRuntimeConfiguration { settings in
|
||||
var data: Data?
|
||||
if let settings {
|
||||
data = settings.data(using: .utf8)!
|
||||
}
|
||||
completionHandler(data)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
completionHandler(nil)
|
||||
}
|
||||
} else {
|
||||
completionHandler(nil)
|
||||
}
|
||||
}
|
||||
|
||||
// private func startEmptyTunnel(completionHandler: @escaping (Error?) -> Void) {
|
||||
// dispatchPrecondition(condition: .onQueue(dispatchQueue))
|
||||
//
|
||||
// let emptyTunnelConfiguration = TunnelConfiguration(
|
||||
// name: nil,
|
||||
// interface: InterfaceConfiguration(privateKey: PrivateKey()),
|
||||
// peers: []
|
||||
// )
|
||||
//
|
||||
// wgAdapter.start(tunnelConfiguration: emptyTunnelConfiguration) { error in
|
||||
// self.dispatchQueue.async {
|
||||
// if let error {
|
||||
// wg_log(.error, message: "Failed to start an empty tunnel")
|
||||
// completionHandler(error)
|
||||
// } else {
|
||||
// wg_log(.info, message: "Started an empty tunnel")
|
||||
// self.tunnelAdapterDidStart()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// let settings = NETunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1")
|
||||
//
|
||||
// self.setTunnelNetworkSettings(settings) { error in
|
||||
// completionHandler(error)
|
||||
// }
|
||||
// }
|
||||
|
||||
// private func tunnelAdapterDidStart() {
|
||||
// dispatchPrecondition(condition: .onQueue(dispatchQueue))
|
||||
// // ...
|
||||
// }
|
||||
|
||||
func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||
wg_log(.info, staticMessage: "Stopping tunnel")
|
||||
|
||||
wgAdapter.stop { error in
|
||||
ErrorNotifier.removeLastErrorFile()
|
||||
|
||||
if let error {
|
||||
wg_log(.error, message: "Failed to stop WireGuard adapter: \(error.localizedDescription)")
|
||||
}
|
||||
completionHandler()
|
||||
|
||||
#if os(macOS)
|
||||
// HACK: This is a filthy hack to work around Apple bug 32073323 (dup'd by us as 47526107).
|
||||
// Remove it when they finally fix this upstream and the fix has been rolled out to
|
||||
// sufficient quantities of users.
|
||||
exit(0)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ struct Constants {
|
||||
static let ovpnConfigKey = "ovpn"
|
||||
static let wireGuardConfigKey = "wireguard"
|
||||
static let loggerTag = "NET"
|
||||
|
||||
|
||||
static let kActionStart = "start"
|
||||
static let kActionRestart = "restart"
|
||||
static let kActionStop = "stop"
|
||||
@@ -34,62 +34,68 @@ struct Constants {
|
||||
}
|
||||
|
||||
class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
private lazy var wgAdapter = {
|
||||
lazy var wgAdapter = {
|
||||
WireGuardAdapter(with: self) { logLevel, message in
|
||||
wg_log(logLevel.osLogLevel, message: message)
|
||||
}
|
||||
}()
|
||||
|
||||
private lazy var ovpnAdapter: OpenVPNAdapter = {
|
||||
|
||||
lazy var ovpnAdapter: OpenVPNAdapter = {
|
||||
let adapter = OpenVPNAdapter()
|
||||
adapter.delegate = self
|
||||
return adapter
|
||||
}()
|
||||
|
||||
|
||||
/// Internal queue.
|
||||
private let dispatchQueue = DispatchQueue(label: "PacketTunnel", qos: .utility)
|
||||
|
||||
private var openVPNConfig: Data?
|
||||
|
||||
var splitTunnelType: Int!
|
||||
var splitTunnelSites: [String]!
|
||||
|
||||
|
||||
let vpnReachability = OpenVPNReachability()
|
||||
|
||||
|
||||
var startHandler: ((Error?) -> Void)?
|
||||
var stopHandler: (() -> Void)?
|
||||
var protoType: TunnelProtoType = .none
|
||||
|
||||
|
||||
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||
let tmpStr = String(data: messageData, encoding: .utf8)!
|
||||
wg_log(.error, message: tmpStr)
|
||||
guard let message = String(data: messageData, encoding: .utf8) else {
|
||||
if let completionHandler {
|
||||
completionHandler(nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
neLog(.info, title: "App said: ", message: message)
|
||||
|
||||
guard let message = try? JSONSerialization.jsonObject(with: messageData, options: []) as? [String: Any] else {
|
||||
log(.error, message: "Failed to serialize message from app")
|
||||
neLog(.error, message: "Failed to serialize message from app")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
guard let completionHandler else {
|
||||
log(.error, message: "Missing message completion handler")
|
||||
neLog(.error, message: "Missing message completion handler")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
guard let action = message[Constants.kMessageKeyAction] as? String else {
|
||||
log(.error, message: "Missing action key in app message")
|
||||
neLog(.error, message: "Missing action key in app message")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if action == Constants.kActionStatus {
|
||||
handleStatusAppMessage(messageData, completionHandler: completionHandler)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
|
||||
dispatchQueue.async {
|
||||
let activationAttemptId = options?[Constants.kActivationAttemptId] as? String
|
||||
let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId)
|
||||
|
||||
log(.info, message: "PacketTunnelProvider startTunnel")
|
||||
|
||||
|
||||
neLog(.info, message: "Start tunnel")
|
||||
|
||||
if let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol {
|
||||
let providerConfiguration = protocolConfiguration.providerConfiguration
|
||||
if (providerConfiguration?[Constants.ovpnConfigKey] as? Data) != nil {
|
||||
@@ -100,7 +106,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
} else {
|
||||
self.protoType = .none
|
||||
}
|
||||
|
||||
|
||||
switch self.protoType {
|
||||
case .wireguard:
|
||||
self.startWireguard(activationAttemptId: activationAttemptId,
|
||||
@@ -116,7 +122,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||
dispatchQueue.async {
|
||||
switch self.protoType {
|
||||
@@ -132,7 +138,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func handleStatusAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||
switch protoType {
|
||||
case .wireguard:
|
||||
@@ -146,291 +152,18 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Private methods
|
||||
private func startWireguard(activationAttemptId: String?,
|
||||
errorNotifier: ErrorNotifier,
|
||||
completionHandler: @escaping (Error?) -> Void) {
|
||||
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
||||
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
||||
let wgConfigData: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else {
|
||||
wg_log(.error, message: "Can't start WireGuard config missing")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let wgConfig = try JSONDecoder().decode(WGConfig.self, from: wgConfigData)
|
||||
let wgConfigStr = wgConfig.str
|
||||
log(.info, message: "wgConfig: \(wgConfig.redux.replacingOccurrences(of: "\n", with: " "))")
|
||||
|
||||
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: wgConfigStr)
|
||||
|
||||
if tunnelConfiguration.peers.first!.allowedIPs
|
||||
.map({ $0.stringRepresentation })
|
||||
.joined(separator: ", ") == "0.0.0.0/0, ::/0" {
|
||||
if wgConfig.splitTunnelType == 1 {
|
||||
for index in tunnelConfiguration.peers.indices {
|
||||
tunnelConfiguration.peers[index].allowedIPs.removeAll()
|
||||
var allowedIPs = [IPAddressRange]()
|
||||
|
||||
for allowedIPString in wgConfig.splitTunnelSites {
|
||||
if let allowedIP = IPAddressRange(from: allowedIPString) {
|
||||
allowedIPs.append(allowedIP)
|
||||
}
|
||||
}
|
||||
|
||||
tunnelConfiguration.peers[index].allowedIPs = allowedIPs
|
||||
}
|
||||
} else if wgConfig.splitTunnelType == 2 {
|
||||
for index in tunnelConfiguration.peers.indices {
|
||||
var excludeIPs = [IPAddressRange]()
|
||||
|
||||
for excludeIPString in wgConfig.splitTunnelSites {
|
||||
if let excludeIP = IPAddressRange(from: excludeIPString) {
|
||||
excludeIPs.append(excludeIP)
|
||||
}
|
||||
}
|
||||
|
||||
tunnelConfiguration.peers[index].excludeIPs = excludeIPs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wg_log(.info, message: "Starting wireguard tunnel from the " +
|
||||
(activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
|
||||
|
||||
// Start the tunnel
|
||||
wgAdapter.start(tunnelConfiguration: tunnelConfiguration) { adapterError in
|
||||
guard let adapterError else {
|
||||
let interfaceName = self.wgAdapter.interfaceName ?? "unknown"
|
||||
wg_log(.info, message: "Tunnel interface is \(interfaceName)")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
switch adapterError {
|
||||
case .cannotLocateTunnelFileDescriptor:
|
||||
wg_log(.error, staticMessage: "Starting tunnel failed: could not determine file descriptor")
|
||||
errorNotifier.notify(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
||||
completionHandler(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
||||
case .dnsResolution(let dnsErrors):
|
||||
let hostnamesWithDnsResolutionFailure = dnsErrors.map { $0.address }
|
||||
.joined(separator: ", ")
|
||||
wg_log(.error, message:
|
||||
"DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure)")
|
||||
errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure)
|
||||
completionHandler(PacketTunnelProviderError.dnsResolutionFailure)
|
||||
case .setNetworkSettings(let error):
|
||||
wg_log(.error, message:
|
||||
"Starting tunnel failed with setTunnelNetworkSettings returning \(error.localizedDescription)")
|
||||
errorNotifier.notify(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
||||
completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
||||
case .startWireGuardBackend(let errorCode):
|
||||
wg_log(.error, message: "Starting tunnel failed with wgTurnOn returning \(errorCode)")
|
||||
errorNotifier.notify(PacketTunnelProviderError.couldNotStartBackend)
|
||||
completionHandler(PacketTunnelProviderError.couldNotStartBackend)
|
||||
case .invalidState:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
log(.error, message: "Can't parse WG config: \(error.localizedDescription)")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private func startOpenVPN(completionHandler: @escaping (Error?) -> Void) {
|
||||
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
||||
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
||||
let openVPNConfigData = providerConfiguration[Constants.ovpnConfigKey] as? Data else {
|
||||
wg_log(.error, message: "Can't start startOpenVPN()")
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
log(.info, message: "providerConfiguration: \(String(decoding: openVPNConfigData, as: UTF8.self).replacingOccurrences(of: "\n", with: " "))")
|
||||
|
||||
let openVPNConfig = try JSONDecoder().decode(OpenVPNConfig.self, from: openVPNConfigData)
|
||||
log(.info, message: "openVPNConfig: \(openVPNConfig.str.replacingOccurrences(of: "\n", with: " "))")
|
||||
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
|
||||
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
|
||||
} catch {
|
||||
log(.error, message: "Can't parse OpenVPN config: \(error.localizedDescription)")
|
||||
|
||||
if let underlyingError = (error as NSError).userInfo[NSUnderlyingErrorKey] as? NSError {
|
||||
log(.error, message: "Can't parse OpenVPN config: \(underlyingError.localizedDescription)")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||
wg_log(.info, staticMessage: "Stopping tunnel")
|
||||
|
||||
wgAdapter.stop { error in
|
||||
ErrorNotifier.removeLastErrorFile()
|
||||
|
||||
if let error {
|
||||
wg_log(.error, message: "Failed to stop WireGuard adapter: \(error.localizedDescription)")
|
||||
}
|
||||
completionHandler()
|
||||
|
||||
#if os(macOS)
|
||||
// HACK: This is a filthy hack to work around Apple bug 32073323 (dup'd by us as 47526107).
|
||||
// Remove it when they finally fix this upstream and the fix has been rolled out to
|
||||
// sufficient quantities of users.
|
||||
exit(0)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private func stopOpenVPN(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||
stopHandler = completionHandler
|
||||
if vpnReachability.isTracking {
|
||||
vpnReachability.stopTracking()
|
||||
}
|
||||
ovpnAdapter.disconnect()
|
||||
}
|
||||
|
||||
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||
guard let completionHandler = completionHandler else { return }
|
||||
wgAdapter.getRuntimeConfiguration { settings in
|
||||
var data: Data?
|
||||
if let settings {
|
||||
data = settings.data(using: .utf8)!
|
||||
}
|
||||
|
||||
let components = settings!.components(separatedBy: "\n")
|
||||
|
||||
var settingsDictionary: [String: String] = [:]
|
||||
for component in components {
|
||||
let pair = component.components(separatedBy: "=")
|
||||
if pair.count == 2 {
|
||||
settingsDictionary[pair[0]] = pair[1]
|
||||
}
|
||||
}
|
||||
|
||||
let response: [String: Any] = [
|
||||
"rx_bytes": settingsDictionary["rx_bytes"] ?? "0",
|
||||
"tx_bytes": settingsDictionary["tx_bytes"] ?? "0"
|
||||
]
|
||||
|
||||
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
||||
}
|
||||
}
|
||||
|
||||
private func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||
guard let completionHandler = completionHandler else { return }
|
||||
if messageData.count == 1 && messageData[0] == 0 {
|
||||
wgAdapter.getRuntimeConfiguration { settings in
|
||||
var data: Data?
|
||||
if let settings {
|
||||
data = settings.data(using: .utf8)!
|
||||
}
|
||||
completionHandler(data)
|
||||
}
|
||||
} else if messageData.count >= 1 {
|
||||
// Updates the tunnel configuration and responds with the active configuration
|
||||
wg_log(.info, message: "Switching tunnel configuration")
|
||||
guard let configString = String(data: messageData, encoding: .utf8)
|
||||
else {
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString)
|
||||
wgAdapter.update(tunnelConfiguration: tunnelConfiguration) { error in
|
||||
if let error {
|
||||
wg_log(.error, message: "Failed to switch tunnel configuration: \(error.localizedDescription)")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
self.wgAdapter.getRuntimeConfiguration { settings in
|
||||
var data: Data?
|
||||
if let settings {
|
||||
data = settings.data(using: .utf8)!
|
||||
}
|
||||
completionHandler(data)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
completionHandler(nil)
|
||||
}
|
||||
} else {
|
||||
completionHandler(nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||
guard let completionHandler = completionHandler else { return }
|
||||
let bytesin = ovpnAdapter.transportStatistics.bytesIn
|
||||
let bytesout = ovpnAdapter.transportStatistics.bytesOut
|
||||
|
||||
let response: [String: Any] = [
|
||||
"rx_bytes": bytesin,
|
||||
"tx_bytes": bytesout
|
||||
]
|
||||
|
||||
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
||||
}
|
||||
|
||||
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data,
|
||||
withShadowSocks viaSS: Bool = false,
|
||||
completionHandler: @escaping (Error?) -> Void) {
|
||||
wg_log(.info, message: "setupAndlaunchOpenVPN")
|
||||
|
||||
let str = String(decoding: ovpnConfiguration, as: UTF8.self)
|
||||
|
||||
let configuration = OpenVPNConfiguration()
|
||||
configuration.fileContent = ovpnConfiguration
|
||||
if str.contains("cloak") {
|
||||
configuration.setPTCloak()
|
||||
}
|
||||
|
||||
let evaluation: OpenVPNConfigurationEvaluation
|
||||
do {
|
||||
evaluation = try ovpnAdapter.apply(configuration: configuration)
|
||||
|
||||
} catch {
|
||||
completionHandler(error)
|
||||
return
|
||||
}
|
||||
|
||||
if !evaluation.autologin {
|
||||
wg_log(.info, message: "Implement login with user credentials")
|
||||
}
|
||||
|
||||
vpnReachability.startTracking { [weak self] status in
|
||||
guard status == .reachableViaWiFi else { return }
|
||||
self?.ovpnAdapter.reconnect(afterTimeInterval: 5)
|
||||
}
|
||||
|
||||
startHandler = completionHandler
|
||||
ovpnAdapter.connect(using: packetFlow)
|
||||
|
||||
// let ifaces = Interface.allInterfaces()
|
||||
// .filter { $0.family == .ipv4 }
|
||||
// .map { iface in iface.name }
|
||||
|
||||
// wg_log(.error, message: "Available TUN Interfaces: \(ifaces)")
|
||||
}
|
||||
|
||||
|
||||
// MARK: Network observing methods
|
||||
|
||||
|
||||
private func startListeningForNetworkChanges() {
|
||||
stopListeningForNetworkChanges()
|
||||
addObserver(self, forKeyPath: Constants.kDefaultPathKey, options: .old, context: nil)
|
||||
}
|
||||
|
||||
|
||||
private func stopListeningForNetworkChanges() {
|
||||
removeObserver(self, forKeyPath: Constants.kDefaultPathKey)
|
||||
}
|
||||
|
||||
|
||||
override func observeValue(forKeyPath keyPath: String?,
|
||||
of object: Any?,
|
||||
change: [NSKeyValueChangeKey: Any]?,
|
||||
@@ -450,48 +183,13 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
self.handle(networkChange: self.defaultPath!) { _ in }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func handle(networkChange changePath: NWPath, completion: @escaping (Error?) -> Void) {
|
||||
wg_log(.info, message: "Tunnel restarted.")
|
||||
startTunnel(options: nil, completionHandler: completion)
|
||||
}
|
||||
|
||||
private func startEmptyTunnel(completionHandler: @escaping (Error?) -> Void) {
|
||||
dispatchPrecondition(condition: .onQueue(dispatchQueue))
|
||||
|
||||
let emptyTunnelConfiguration = TunnelConfiguration(
|
||||
name: nil,
|
||||
interface: InterfaceConfiguration(privateKey: PrivateKey()),
|
||||
peers: []
|
||||
)
|
||||
|
||||
wgAdapter.start(tunnelConfiguration: emptyTunnelConfiguration) { error in
|
||||
self.dispatchQueue.async {
|
||||
if let error {
|
||||
log(.error, message: "Failed to start an empty tunnel")
|
||||
completionHandler(error)
|
||||
} else {
|
||||
log(.info, message: "Started an empty tunnel")
|
||||
self.tunnelAdapterDidStart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let settings = NETunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1")
|
||||
|
||||
self.setTunnelNetworkSettings(settings) { error in
|
||||
completionHandler(error)
|
||||
}
|
||||
}
|
||||
|
||||
private func tunnelAdapterDidStart() {
|
||||
dispatchPrecondition(condition: .onQueue(dispatchQueue))
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
|
||||
|
||||
extension WireGuardLogLevel {
|
||||
var osLogLevel: OSLogType {
|
||||
switch self {
|
||||
|
||||
@@ -89,14 +89,3 @@ struct WGConfig: Decodable {
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
struct OpenVPNConfig: Decodable {
|
||||
let config: String
|
||||
let mtu: String
|
||||
let splitTunnelType: Int
|
||||
let splitTunnelSites: [String]
|
||||
|
||||
var str: String {
|
||||
"splitTunnelType: \(splitTunnelType) splitTunnelSites: \(splitTunnelSites) mtu: \(mtu) config: \(config)"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,10 @@
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include "../utilities.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "core/networkUtilities.h"
|
||||
|
||||
namespace {
|
||||
Logger logger("LinuxRouteMonitor");
|
||||
@@ -163,7 +165,7 @@ bool LinuxRouteMonitor::rtmSendRoute(int action, int flags, int type,
|
||||
|
||||
if (rtm->rtm_type == RTN_THROW) {
|
||||
struct in_addr ip4;
|
||||
inet_pton(AF_INET, getgatewayandiface().toUtf8(), &ip4);
|
||||
inet_pton(AF_INET, NetworkUtilities::getGatewayAndIface().toUtf8(), &ip4);
|
||||
nlmsg_append_attr(nlmsg, sizeof(buf), RTA_GATEWAY, &ip4, sizeof(ip4));
|
||||
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 0);
|
||||
rtm->rtm_type = RTN_UNICAST;
|
||||
@@ -221,122 +223,6 @@ void LinuxRouteMonitor::nlsockReady() {
|
||||
}
|
||||
}
|
||||
|
||||
#define BUFFER_SIZE 4096
|
||||
|
||||
QString LinuxRouteMonitor::getgatewayandiface()
|
||||
{
|
||||
int received_bytes = 0, msg_len = 0, route_attribute_len = 0;
|
||||
int sock = -1, msgseq = 0;
|
||||
struct nlmsghdr *nlh, *nlmsg;
|
||||
struct rtmsg *route_entry;
|
||||
// This struct contain route attributes (route type)
|
||||
struct rtattr *route_attribute;
|
||||
char gateway_address[INET_ADDRSTRLEN], interface[IF_NAMESIZE];
|
||||
char msgbuf[BUFFER_SIZE], buffer[BUFFER_SIZE];
|
||||
char *ptr = buffer;
|
||||
struct timeval tv;
|
||||
|
||||
if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
|
||||
perror("socket failed");
|
||||
return "";
|
||||
}
|
||||
|
||||
memset(msgbuf, 0, sizeof(msgbuf));
|
||||
memset(gateway_address, 0, sizeof(gateway_address));
|
||||
memset(interface, 0, sizeof(interface));
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
|
||||
/* point the header and the msg structure pointers into the buffer */
|
||||
nlmsg = (struct nlmsghdr *)msgbuf;
|
||||
|
||||
/* Fill in the nlmsg header*/
|
||||
nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
|
||||
nlmsg->nlmsg_type = RTM_GETROUTE; // Get the routes from kernel routing table .
|
||||
nlmsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; // The message is a request for dump.
|
||||
nlmsg->nlmsg_seq = msgseq++; // Sequence of the message packet.
|
||||
nlmsg->nlmsg_pid = getpid(); // PID of process sending the request.
|
||||
|
||||
/* 1 Sec Timeout to avoid stall */
|
||||
tv.tv_sec = 1;
|
||||
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (struct timeval *)&tv, sizeof(struct timeval));
|
||||
/* send msg */
|
||||
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
|
||||
perror("send failed");
|
||||
return "";
|
||||
}
|
||||
|
||||
/* receive response */
|
||||
do
|
||||
{
|
||||
received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0);
|
||||
if (received_bytes < 0) {
|
||||
perror("Error in recv");
|
||||
return "";
|
||||
}
|
||||
|
||||
nlh = (struct nlmsghdr *) ptr;
|
||||
|
||||
/* Check if the header is valid */
|
||||
if((NLMSG_OK(nlmsg, received_bytes) == 0) ||
|
||||
(nlmsg->nlmsg_type == NLMSG_ERROR))
|
||||
{
|
||||
perror("Error in received packet");
|
||||
return "";
|
||||
}
|
||||
|
||||
/* If we received all data break */
|
||||
if (nlh->nlmsg_type == NLMSG_DONE)
|
||||
break;
|
||||
else {
|
||||
ptr += received_bytes;
|
||||
msg_len += received_bytes;
|
||||
}
|
||||
|
||||
/* Break if its not a multi part message */
|
||||
if ((nlmsg->nlmsg_flags & NLM_F_MULTI) == 0)
|
||||
break;
|
||||
}
|
||||
while ((nlmsg->nlmsg_seq != msgseq) || (nlmsg->nlmsg_pid != getpid()));
|
||||
|
||||
/* parse response */
|
||||
for ( ; NLMSG_OK(nlh, received_bytes); nlh = NLMSG_NEXT(nlh, received_bytes))
|
||||
{
|
||||
/* Get the route data */
|
||||
route_entry = (struct rtmsg *) NLMSG_DATA(nlh);
|
||||
|
||||
/* We are just interested in main routing table */
|
||||
if (route_entry->rtm_table != RT_TABLE_MAIN)
|
||||
continue;
|
||||
|
||||
route_attribute = (struct rtattr *) RTM_RTA(route_entry);
|
||||
route_attribute_len = RTM_PAYLOAD(nlh);
|
||||
|
||||
/* Loop through all attributes */
|
||||
for ( ; RTA_OK(route_attribute, route_attribute_len);
|
||||
route_attribute = RTA_NEXT(route_attribute, route_attribute_len))
|
||||
{
|
||||
switch(route_attribute->rta_type) {
|
||||
case RTA_OIF:
|
||||
if_indextoname(*(int *)RTA_DATA(route_attribute), interface);
|
||||
break;
|
||||
case RTA_GATEWAY:
|
||||
inet_ntop(AF_INET, RTA_DATA(route_attribute),
|
||||
gateway_address, sizeof(gateway_address));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((*gateway_address) && (*interface)) {
|
||||
logger.debug() << "Gateway " << gateway_address << " for interface " << interface;
|
||||
break;
|
||||
}
|
||||
}
|
||||
close(sock);
|
||||
return gateway_address;
|
||||
}
|
||||
|
||||
static bool buildAllowedIp(wg_allowedip* ip,
|
||||
const IPAddress& prefix) {
|
||||
const char* addrString = qPrintable(prefix.address().toString());
|
||||
|
||||
@@ -31,7 +31,6 @@ class LinuxRouteMonitor final : public QObject {
|
||||
static QString addrToString(const QByteArray& data);
|
||||
bool rtmSendRoute(int action, int flags, int type,
|
||||
const IPAddress& prefix);
|
||||
QString getgatewayandiface();
|
||||
QString m_ifname;
|
||||
unsigned int m_ifindex = 0;
|
||||
int m_nlsock = -1;
|
||||
|
||||
@@ -26,7 +26,6 @@ OpenVpnProtocol::~OpenVpnProtocol()
|
||||
|
||||
QString OpenVpnProtocol::defaultConfigFileName()
|
||||
{
|
||||
// qDebug() << "OpenVpnProtocol::defaultConfigFileName" << defaultConfigPath() + QString("/%1.ovpn").arg(APPLICATION_NAME);
|
||||
return defaultConfigPath() + QString("/%1.ovpn").arg(APPLICATION_NAME);
|
||||
}
|
||||
|
||||
@@ -161,7 +160,6 @@ void OpenVpnProtocol::updateRouteGateway(QString line)
|
||||
|
||||
ErrorCode OpenVpnProtocol::start()
|
||||
{
|
||||
// qDebug() << "Start OpenVPN connection";
|
||||
OpenVpnProtocol::stop();
|
||||
|
||||
if (!QFileInfo::exists(Utils::openVpnExecPath())) {
|
||||
@@ -196,9 +194,6 @@ ErrorCode OpenVpnProtocol::start()
|
||||
}
|
||||
#endif
|
||||
|
||||
// QString vpnLogFileNamePath = Utils::systemLogPath() + "/openvpn.log";
|
||||
// Utils::createEmptyFile(vpnLogFileNamePath);
|
||||
|
||||
uint mgmtPort = selectMgmtPort();
|
||||
qDebug() << "OpenVpnProtocol::start mgmt port selected:" << mgmtPort;
|
||||
|
||||
@@ -212,12 +207,11 @@ ErrorCode OpenVpnProtocol::start()
|
||||
m_openVpnProcess = IpcClient::CreatePrivilegedProcess();
|
||||
|
||||
if (!m_openVpnProcess) {
|
||||
// qWarning() << "IpcProcess replica is not created!";
|
||||
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
|
||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||
}
|
||||
|
||||
m_openVpnProcess->waitForSource(1000);
|
||||
m_openVpnProcess->waitForSource(5000);
|
||||
if (!m_openVpnProcess->isInitialized()) {
|
||||
qWarning() << "IpcProcess replica is not connected!";
|
||||
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
|
||||
@@ -242,8 +236,6 @@ ErrorCode OpenVpnProtocol::start()
|
||||
|
||||
m_openVpnProcess->start();
|
||||
|
||||
// startTimeoutTimer();
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ QMap<amnezia::Proto, QString> ProtocolProps::protocolHumanNames()
|
||||
{ Proto::Awg, "AmneziaWG" },
|
||||
{ Proto::Ikev2, "IKEv2" },
|
||||
{ Proto::L2tp, "L2TP" },
|
||||
{ Proto::Xray, "XRay" },
|
||||
|
||||
{ Proto::TorWebSite, "Website in Tor network" },
|
||||
{ Proto::Dns, "DNS Service" },
|
||||
@@ -92,6 +93,7 @@ amnezia::ServiceType ProtocolProps::protocolService(Proto p)
|
||||
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;
|
||||
@@ -122,6 +124,7 @@ int ProtocolProps::defaultPort(Proto p)
|
||||
case Proto::ShadowSocks: return QString(protocols::shadowsocks::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::L2tp: return -1;
|
||||
|
||||
@@ -162,6 +165,8 @@ TransportProto ProtocolProps::defaultTransportProto(Proto p)
|
||||
case Proto::Awg: return TransportProto::Udp;
|
||||
case Proto::Ikev2: return TransportProto::Udp;
|
||||
case Proto::L2tp: return TransportProto::Udp;
|
||||
case Proto::Xray: return TransportProto::Tcp;
|
||||
|
||||
// non-vpn
|
||||
case Proto::TorWebSite: return TransportProto::Tcp;
|
||||
case Proto::Dns: return TransportProto::Udp;
|
||||
@@ -180,12 +185,15 @@ bool ProtocolProps::defaultTransportProtoChangeable(Proto p)
|
||||
case Proto::Awg: return false;
|
||||
case Proto::Ikev2: return false;
|
||||
case Proto::L2tp: return false;
|
||||
case Proto::Xray: return false;
|
||||
|
||||
// non-vpn
|
||||
case Proto::TorWebSite: return false;
|
||||
case Proto::Dns: return false;
|
||||
case Proto::Sftp: return false;
|
||||
default: return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QString ProtocolProps::key_proto_config_data(Proto p)
|
||||
|
||||
@@ -82,6 +82,7 @@ namespace amnezia
|
||||
constexpr char cloak[] = "cloak";
|
||||
constexpr char sftp[] = "sftp";
|
||||
constexpr char awg[] = "awg";
|
||||
constexpr char xray[] = "xray";
|
||||
|
||||
constexpr char configVersion[] = "config_version";
|
||||
|
||||
@@ -134,6 +135,20 @@ namespace amnezia
|
||||
constexpr char defaultCipher[] = "chacha20-ietf-poly1305";
|
||||
}
|
||||
|
||||
namespace xray
|
||||
{
|
||||
constexpr char serverConfigPath[] = "/opt/amnezia/xray/server.json";
|
||||
constexpr char uuidPath[] = "/opt/amnezia/xray/xray_uuid.key";
|
||||
constexpr char PublicKeyPath[] = "/opt/amnezia/xray/xray_public.key";
|
||||
constexpr char PrivateKeyPath[] = "/opt/amnezia/xray/xray_private.key";
|
||||
constexpr char shortidPath[] = "/opt/amnezia/xray/xray_short_id.key";
|
||||
constexpr char defaultSite[] = "www.googletagmanager.com";
|
||||
|
||||
constexpr char defaultPort[] = "443";
|
||||
constexpr char defaultLocalProxyPort[] = "10808";
|
||||
constexpr char defaultLocalAddr[] = "10.33.0.2";
|
||||
}
|
||||
|
||||
namespace cloak
|
||||
{
|
||||
constexpr char ckPublicKeyPath[] = "/opt/amnezia/cloak/cloak_public.key";
|
||||
@@ -142,7 +157,6 @@ namespace amnezia
|
||||
constexpr char defaultPort[] = "443";
|
||||
constexpr char defaultRedirSite[] = "tile.openstreetmap.org";
|
||||
constexpr char defaultCipher[] = "chacha20-poly1305";
|
||||
|
||||
}
|
||||
|
||||
namespace wireguard
|
||||
@@ -206,6 +220,7 @@ namespace amnezia
|
||||
Awg,
|
||||
Ikev2,
|
||||
L2tp,
|
||||
Xray,
|
||||
|
||||
// non-vpn
|
||||
TorWebSite,
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "openvpnprotocol.h"
|
||||
#include "shadowsocksvpnprotocol.h"
|
||||
#include "wireguardprotocol.h"
|
||||
#include "xrayprotocol.h"
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
@@ -114,6 +115,7 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &
|
||||
case DockerContainer::ShadowSocks: return new ShadowSocksVpnProtocol(configuration);
|
||||
case DockerContainer::WireGuard: return new WireguardProtocol(configuration);
|
||||
case DockerContainer::Awg: return new WireguardProtocol(configuration);
|
||||
case DockerContainer::Xray: return new XrayProtocol(configuration);
|
||||
#endif
|
||||
default: return nullptr;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
#include "xrayprotocol.h"
|
||||
|
||||
#include "utilities.h"
|
||||
#include "containers/containers_defs.h"
|
||||
#include "core/networkUtilities.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkInterface>
|
||||
|
||||
|
||||
XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent):
|
||||
VpnProtocol(configuration, parent)
|
||||
{
|
||||
readXrayConfiguration(configuration);
|
||||
m_routeGateway = NetworkUtilities::getGatewayAndIface();
|
||||
m_vpnGateway = amnezia::protocols::xray::defaultLocalAddr;
|
||||
m_vpnLocalAddress = amnezia::protocols::xray::defaultLocalAddr;
|
||||
}
|
||||
|
||||
XrayProtocol::~XrayProtocol()
|
||||
{
|
||||
XrayProtocol::stop();
|
||||
QThread::msleep(200);
|
||||
m_xrayProcess.close();
|
||||
}
|
||||
|
||||
ErrorCode XrayProtocol::start()
|
||||
{
|
||||
qDebug().noquote() << "XrayProtocol xrayExecPath():" << xrayExecPath();
|
||||
|
||||
if (!QFileInfo::exists(xrayExecPath())) {
|
||||
setLastError(ErrorCode::XrayExecutableMissing);
|
||||
return lastError();
|
||||
}
|
||||
|
||||
if (Utils::processIsRunning(Utils::executable(xrayExecPath(), true))) {
|
||||
Utils::killProcessByName(Utils::executable(xrayExecPath(), true));
|
||||
}
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
m_xrayCfgFile.setAutoRemove(false);
|
||||
#endif
|
||||
m_xrayCfgFile.open();
|
||||
m_xrayCfgFile.write(QJsonDocument(m_xrayConfig).toJson());
|
||||
m_xrayCfgFile.close();
|
||||
|
||||
QStringList args = QStringList() << "-c" << m_xrayCfgFile.fileName() << "-format=json";
|
||||
|
||||
qDebug().noquote() << "XrayProtocol::start()"
|
||||
<< xrayExecPath() << args.join(" ");
|
||||
|
||||
m_xrayProcess.setProcessChannelMode(QProcess::MergedChannels);
|
||||
|
||||
m_xrayProcess.setProgram(xrayExecPath());
|
||||
m_xrayProcess.setArguments(args);
|
||||
|
||||
connect(&m_xrayProcess, &QProcess::readyReadStandardOutput, this, [this]() {
|
||||
#ifdef QT_DEBUG
|
||||
qDebug().noquote() << "xray:" << m_xrayProcess.readAllStandardOutput();
|
||||
#endif
|
||||
});
|
||||
|
||||
connect(&m_xrayProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
|
||||
qDebug().noquote() << "XrayProtocol finished, exitCode, exiStatus" << exitCode << exitStatus;
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
if (exitStatus != QProcess::NormalExit) {
|
||||
emit protocolError(amnezia::ErrorCode::XrayExecutableCrashed);
|
||||
stop();
|
||||
}
|
||||
if (exitCode != 0) {
|
||||
emit protocolError(amnezia::ErrorCode::InternalError);
|
||||
stop();
|
||||
}
|
||||
});
|
||||
|
||||
m_xrayProcess.start();
|
||||
m_xrayProcess.waitForStarted();
|
||||
|
||||
if (m_xrayProcess.state() == QProcess::ProcessState::Running) {
|
||||
setConnectionState(Vpn::ConnectionState::Connecting);
|
||||
QThread::msleep(1000);
|
||||
return startTun2Sock();
|
||||
}
|
||||
else return ErrorCode::XrayExecutableMissing;
|
||||
}
|
||||
|
||||
|
||||
ErrorCode XrayProtocol::startTun2Sock()
|
||||
{
|
||||
if (!QFileInfo::exists(Utils::tun2socksPath())) {
|
||||
setLastError(ErrorCode::Tun2SockExecutableMissing);
|
||||
return lastError();
|
||||
}
|
||||
|
||||
m_t2sProcess = IpcClient::CreatePrivilegedProcess();
|
||||
|
||||
if (!m_t2sProcess) {
|
||||
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
|
||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||
}
|
||||
|
||||
m_t2sProcess->waitForSource(1000);
|
||||
if (!m_t2sProcess->isInitialized()) {
|
||||
qWarning() << "IpcProcess replica is not connected!";
|
||||
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
|
||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||
}
|
||||
|
||||
QString XrayConStr = "socks5://127.0.0.1:" + QString::number(m_localPort);
|
||||
|
||||
m_t2sProcess->setProgram(PermittedProcess::Tun2Socks);
|
||||
#ifdef Q_OS_WIN
|
||||
QStringList arguments({"-device", "tun://tun2", "-proxy", XrayConStr, "-tun-post-up",
|
||||
QString("cmd /c netsh interface ip set address name=\"tun2\" static %1 255.255.255.255").arg(amnezia::protocols::xray::defaultLocalAddr)});
|
||||
#endif
|
||||
#ifdef Q_OS_LINUX
|
||||
QStringList arguments({"-device", "tun://tun2", "-proxy", XrayConStr});
|
||||
#endif
|
||||
#ifdef Q_OS_MAC
|
||||
QStringList arguments({"-device", "utun22", "-proxy", XrayConStr});
|
||||
#endif
|
||||
m_t2sProcess->setArguments(arguments);
|
||||
|
||||
qDebug() << arguments.join(" ");
|
||||
connect(m_t2sProcess.data(), &PrivilegedProcess::errorOccurred,
|
||||
[&](QProcess::ProcessError error) { qDebug() << "PrivilegedProcess errorOccurred" << error; });
|
||||
|
||||
connect(m_t2sProcess.data(), &PrivilegedProcess::stateChanged,
|
||||
[&](QProcess::ProcessState newState) {
|
||||
qDebug() << "PrivilegedProcess stateChanged" << newState;
|
||||
if (newState == QProcess::Running)
|
||||
{
|
||||
setConnectionState(Vpn::ConnectionState::Connecting);
|
||||
QList<QHostAddress> dnsAddr;
|
||||
dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns1).toString()));
|
||||
dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns2).toString()));
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
QThread::msleep(5000);
|
||||
IpcClient::Interface()->createTun("utun22", amnezia::protocols::xray::defaultLocalAddr);
|
||||
IpcClient::Interface()->updateResolvers("utun22", dnsAddr);
|
||||
IpcClient::Interface()->enableKillSwitch(m_configData, 0);
|
||||
#endif
|
||||
#ifdef Q_OS_WINDOWS
|
||||
QThread::msleep(15000);
|
||||
#endif
|
||||
#ifdef Q_OS_LINUX
|
||||
QThread::msleep(1000);
|
||||
IpcClient::Interface()->createTun("tun2", amnezia::protocols::xray::defaultLocalAddr);
|
||||
IpcClient::Interface()->updateResolvers("tun2", dnsAddr);
|
||||
IpcClient::Interface()->enableKillSwitch(m_configData, 0);
|
||||
#endif
|
||||
if (m_routeMode == 0) {
|
||||
IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "0.0.0.0/1");
|
||||
IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "128.0.0.0/1");
|
||||
IpcClient::Interface()->routeAddList(m_routeGateway, QStringList() << m_remoteAddress);
|
||||
}
|
||||
IpcClient::Interface()->StopRoutingIpv6();
|
||||
#ifdef Q_OS_WIN
|
||||
IpcClient::Interface()->updateResolvers("tun2", dnsAddr);
|
||||
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
|
||||
for (int i = 0; i < netInterfaces.size(); i++) {
|
||||
for (int j=0; j < netInterfaces.at(i).addressEntries().size(); j++)
|
||||
{
|
||||
if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) {
|
||||
IpcClient::Interface()->enableKillSwitch(QJsonObject(), netInterfaces.at(i).index());
|
||||
m_configData.insert("vpnGateway", m_vpnGateway);
|
||||
IpcClient::Interface()->enablePeerTraffic(m_configData);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
setConnectionState(Vpn::ConnectionState::Connected);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
#if !defined(Q_OS_MACOS)
|
||||
connect(m_t2sProcess.data(), &PrivilegedProcess::finished, this,
|
||||
[&]() {
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
IpcClient::Interface()->deleteTun("tun2");
|
||||
IpcClient::Interface()->StartRoutingIpv6();
|
||||
IpcClient::Interface()->clearSavedRoutes();
|
||||
});
|
||||
#endif
|
||||
|
||||
m_t2sProcess->start();
|
||||
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
void XrayProtocol::stop()
|
||||
{
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||
IpcClient::Interface()->disableKillSwitch();
|
||||
#endif
|
||||
qDebug() << "XrayProtocol::stop()";
|
||||
m_xrayProcess.terminate();
|
||||
if (m_t2sProcess) {
|
||||
m_t2sProcess->close();
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
Utils::signalCtrl(m_xrayProcess.processId(), CTRL_C_EVENT);
|
||||
#endif
|
||||
}
|
||||
|
||||
QString XrayProtocol::xrayExecPath()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
return Utils::executable(QString("xray/xray"), true);
|
||||
#else
|
||||
return Utils::executable(QString("xray"), true);
|
||||
#endif
|
||||
}
|
||||
|
||||
void XrayProtocol::readXrayConfiguration(const QJsonObject &configuration)
|
||||
{
|
||||
m_configData = configuration;
|
||||
QJsonObject xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::Xray)).toObject();
|
||||
m_xrayConfig = xrayConfiguration;
|
||||
m_localPort = QString(amnezia::protocols::xray::defaultLocalProxyPort).toInt();
|
||||
m_remoteAddress = configuration.value(amnezia::config_key::hostName).toString();
|
||||
m_routeMode = configuration.value(amnezia::config_key::splitTunnelType).toInt();
|
||||
m_primaryDNS = configuration.value(amnezia::config_key::dns1).toString();
|
||||
m_secondaryDNS = configuration.value(amnezia::config_key::dns2).toString();
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef XRAYPROTOCOL_H
|
||||
#define XRAYPROTOCOL_H
|
||||
|
||||
#include "openvpnprotocol.h"
|
||||
#include "QProcess"
|
||||
#include "containers/containers_defs.h"
|
||||
|
||||
class XrayProtocol : public VpnProtocol
|
||||
{
|
||||
public:
|
||||
XrayProtocol(const QJsonObject& configuration, QObject* parent = nullptr);
|
||||
virtual ~XrayProtocol() override;
|
||||
|
||||
ErrorCode start() override;
|
||||
ErrorCode startTun2Sock();
|
||||
void stop() override;
|
||||
|
||||
protected:
|
||||
void readXrayConfiguration(const QJsonObject &configuration);
|
||||
|
||||
protected:
|
||||
QJsonObject m_xrayConfig;
|
||||
|
||||
private:
|
||||
static QString xrayExecPath();
|
||||
static QString tun2SocksExecPath();
|
||||
private:
|
||||
int m_localPort;
|
||||
QString m_remoteAddress;
|
||||
int m_routeMode;
|
||||
QJsonObject m_configData;
|
||||
QString m_primaryDNS;
|
||||
QString m_secondaryDNS;
|
||||
#ifndef Q_OS_IOS
|
||||
QProcess m_xrayProcess;
|
||||
QSharedPointer<PrivilegedProcess> m_t2sProcess;
|
||||
#endif
|
||||
QTemporaryFile m_xrayCfgFile;
|
||||
};
|
||||
|
||||
#endif // XRAYPROTOCOL_H
|
||||
@@ -198,6 +198,7 @@
|
||||
<file>ui/qml/Pages2/PageProtocolOpenVpnSettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolShadowSocksSettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolCloakSettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolXraySettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolRaw.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsLogging.qml</file>
|
||||
<file>ui/qml/Pages2/PageServiceSftpSettings.qml</file>
|
||||
@@ -224,9 +225,17 @@
|
||||
<file>ui/qml/Pages2/PageShareFullAccess.qml</file>
|
||||
<file>images/controls/close.svg</file>
|
||||
<file>images/controls/search.svg</file>
|
||||
<file>server_scripts/xray/configure_container.sh</file>
|
||||
<file>server_scripts/xray/Dockerfile</file>
|
||||
<file>server_scripts/xray/run_container.sh</file>
|
||||
<file>server_scripts/xray/start.sh</file>
|
||||
<file>server_scripts/xray/template.json</file>
|
||||
<file>ui/qml/Pages2/PageProtocolWireGuardSettings.qml</file>
|
||||
<file>ui/qml/Components/HomeSplitTunnelingDrawer.qml</file>
|
||||
<file>images/controls/split-tunneling.svg</file>
|
||||
<file>ui/qml/Controls2/DrawerType2.qml</file>
|
||||
<file>images/controls/alert-circle.svg</file>
|
||||
<file>images/controls/file-check-2.svg</file>
|
||||
<file>ui/qml/Controls2/WarningType.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
FROM alpine:3.15
|
||||
LABEL maintainer="AmneziaVPN"
|
||||
|
||||
ARG XRAY_RELEASE="v1.8.6"
|
||||
|
||||
RUN apk add --no-cache curl unzip bash openssl netcat-openbsd dumb-init rng-tools xz
|
||||
RUN apk --update upgrade --no-cache
|
||||
|
||||
RUN mkdir -p /opt/amnezia
|
||||
RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh
|
||||
RUN chmod a+x /opt/amnezia/start.sh
|
||||
|
||||
RUN mkdir -p /opt/amnezia/xray
|
||||
|
||||
RUN curl -L https://github.com/XTLS/Xray-core/releases/download/${XRAY_RELEASE}/Xray-linux-64.zip > /root/xray.zip;\
|
||||
unzip /root/xray.zip -d /usr/bin/;\
|
||||
chmod a+x /usr/bin/xray;
|
||||
|
||||
# Tune network
|
||||
RUN echo -e " \n\
|
||||
fs.file-max = 51200 \n\
|
||||
\n\
|
||||
net.core.rmem_max = 67108864 \n\
|
||||
net.core.wmem_max = 67108864 \n\
|
||||
net.core.netdev_max_backlog = 250000 \n\
|
||||
net.core.somaxconn = 4096 \n\
|
||||
net.core.default_qdisc=fq \n\
|
||||
\n\
|
||||
net.ipv4.tcp_syncookies = 1 \n\
|
||||
net.ipv4.tcp_tw_reuse = 1 \n\
|
||||
net.ipv4.tcp_tw_recycle = 0 \n\
|
||||
net.ipv4.tcp_fin_timeout = 30 \n\
|
||||
net.ipv4.tcp_keepalive_time = 1200 \n\
|
||||
net.ipv4.ip_local_port_range = 10000 65000 \n\
|
||||
net.ipv4.tcp_max_syn_backlog = 8192 \n\
|
||||
net.ipv4.tcp_max_tw_buckets = 5000 \n\
|
||||
net.ipv4.tcp_fastopen = 3 \n\
|
||||
net.ipv4.tcp_mem = 25600 51200 102400 \n\
|
||||
net.ipv4.tcp_rmem = 4096 87380 67108864 \n\
|
||||
net.ipv4.tcp_wmem = 4096 65536 67108864 \n\
|
||||
net.ipv4.tcp_mtu_probing = 1 \n\
|
||||
net.ipv4.tcp_congestion_control = bbr \n\
|
||||
" | sed -e 's/^\s\+//g' | tee -a /etc/sysctl.conf && \
|
||||
mkdir -p /etc/security && \
|
||||
echo -e " \n\
|
||||
* soft nofile 51200 \n\
|
||||
* hard nofile 51200 \n\
|
||||
" | sed -e 's/^\s\+//g' | tee -a /etc/security/limits.conf
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
ENTRYPOINT [ "dumb-init", "/opt/amnezia/start.sh" ]
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
cd /opt/amnezia/xray
|
||||
XRAY_CLIENT_ID=$(xray uuid) && echo $XRAY_CLIENT_ID > /opt/amnezia/xray/xray_uuid.key
|
||||
XRAY_SHORT_ID=$(openssl rand -hex 8) && echo $XRAY_SHORT_ID > /opt/amnezia/xray/xray_short_id.key
|
||||
|
||||
KEYPAIR=$(xray x25519)
|
||||
LINE_NUM=1
|
||||
while IFS= read -r line; do
|
||||
if [[ $LINE_NUM -gt 1 ]]
|
||||
then
|
||||
IFS=":" read FIST XRAY_PUBLIC_KEY <<< "$line"
|
||||
else
|
||||
LINE_NUM=$((LINE_NUM + 1))
|
||||
IFS=":" read FIST XRAY_PRIVATE_KEY <<< "$line"
|
||||
fi
|
||||
done <<< "$KEYPAIR"
|
||||
|
||||
XRAY_PRIVATE_KEY=$(echo $XRAY_PRIVATE_KEY | tr -d ' ')
|
||||
XRAY_PUBLIC_KEY=$(echo $XRAY_PUBLIC_KEY | tr -d ' ')
|
||||
|
||||
|
||||
echo $XRAY_PUBLIC_KEY > /opt/amnezia/xray/xray_public.key
|
||||
echo $XRAY_PRIVATE_KEY > /opt/amnezia/xray/xray_private.key
|
||||
|
||||
|
||||
cat > /opt/amnezia/xray/server.json <<EOF
|
||||
{
|
||||
"log": {
|
||||
"loglevel": "error"
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"port": 443,
|
||||
"protocol": "vless",
|
||||
"settings": {
|
||||
"clients": [
|
||||
{
|
||||
"id": "$XRAY_CLIENT_ID",
|
||||
"flow": "xtls-rprx-vision"
|
||||
}
|
||||
],
|
||||
"decryption": "none"
|
||||
},
|
||||
"streamSettings": {
|
||||
"network": "tcp",
|
||||
"security": "reality",
|
||||
"realitySettings": {
|
||||
"dest": "$XRAY_SITE_NAME:443",
|
||||
"serverNames": [
|
||||
"$XRAY_SITE_NAME"
|
||||
],
|
||||
"privateKey": "$XRAY_PRIVATE_KEY",
|
||||
"shortIds": [
|
||||
"$XRAY_SHORT_ID"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"protocol": "freedom"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Run container
|
||||
sudo docker run -d \
|
||||
--privileged \
|
||||
--log-driver none \
|
||||
--restart always \
|
||||
--cap-add=NET_ADMIN \
|
||||
-p 443:443/tcp \
|
||||
--name $CONTAINER_NAME $CONTAINER_NAME
|
||||
|
||||
sudo docker network connect amnezia-dns-net $CONTAINER_NAME
|
||||
|
||||
# Create tun device if not exist
|
||||
sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /dev/net; if [ ! -c /dev/net/tun ]; then mknod /dev/net/tun c 10 200; fi'
|
||||
|
||||
# Prevent to route packets outside of the container in case if server behind of the NAT
|
||||
sudo docker exec -i $CONTAINER_NAME sh -c "ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up"
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This scripts copied from Amnezia client to Docker container to /opt/amnezia and launched every time container starts
|
||||
|
||||
echo "Container startup"
|
||||
ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up
|
||||
|
||||
iptables -A INPUT -i lo -j ACCEPT
|
||||
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
||||
iptables -A INPUT -p icmp -j ACCEPT
|
||||
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
|
||||
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
|
||||
iptables -P INPUT DROP
|
||||
|
||||
ip6tables -A INPUT -i lo -j ACCEPT
|
||||
ip6tables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
|
||||
ip6tables -A INPUT -p ipv6-icmp -j ACCEPT
|
||||
ip6tables -P INPUT DROP
|
||||
|
||||
# kill daemons in case of restart
|
||||
killall -KILL xray
|
||||
|
||||
# start daemons if configured
|
||||
if [ -f /opt/amnezia/xray/server.json ]; then (xray -config /opt/amnezia/xray/server.json); fi
|
||||
|
||||
tail -f /dev/null
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"log": {
|
||||
"loglevel": "error"
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"listen": "127.0.0.1",
|
||||
"port": 10808,
|
||||
"protocol": "socks",
|
||||
"settings": {
|
||||
"udp": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"protocol": "vless",
|
||||
"settings": {
|
||||
"vnext": [
|
||||
{
|
||||
"address": "$SERVER_IP_ADDRESS",
|
||||
"port": 443,
|
||||
"users": [
|
||||
{
|
||||
"id": "$XRAY_CLIENT_ID",
|
||||
"flow": "xtls-rprx-vision",
|
||||
"encryption": "none"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"streamSettings": {
|
||||
"network": "tcp",
|
||||
"security": "reality",
|
||||
"realitySettings": {
|
||||
"fingerprint": "chrome",
|
||||
"serverName": "$XRAY_SITE_NAME",
|
||||
"publicKey": "$XRAY_PUBLIC_KEY",
|
||||
"shortId": "$XRAY_SHORT_ID",
|
||||
"spiderX": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+16
-3
@@ -3,7 +3,7 @@
|
||||
#include "QThread"
|
||||
#include "QCoreApplication"
|
||||
|
||||
#include "utilities.h"
|
||||
#include "core/networkUtilities.h"
|
||||
#include "version.h"
|
||||
|
||||
#include "containers/containers_defs.h"
|
||||
@@ -226,9 +226,22 @@ void Settings::setSaveLogs(bool enabled)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (enabled) {
|
||||
setLogEnableDate(QDateTime::currentDateTime());
|
||||
}
|
||||
emit saveLogsChanged(enabled);
|
||||
}
|
||||
|
||||
QDateTime Settings::getLogEnableDate()
|
||||
{
|
||||
return value("Conf/logEnableDate").toDateTime();
|
||||
}
|
||||
|
||||
void Settings::setLogEnableDate(QDateTime date)
|
||||
{
|
||||
setValue("Conf/logEnableDate", date);
|
||||
}
|
||||
|
||||
QString Settings::routeModeString(RouteMode mode) const
|
||||
{
|
||||
switch (mode) {
|
||||
@@ -275,9 +288,9 @@ QStringList Settings::getVpnIps(RouteMode mode) const
|
||||
QStringList ips;
|
||||
const QVariantMap &m = vpnSites(mode);
|
||||
for (auto i = m.constBegin(); i != m.constEnd(); ++i) {
|
||||
if (Utils::checkIpSubnetFormat(i.key())) {
|
||||
if (NetworkUtilities::checkIpSubnetFormat(i.key())) {
|
||||
ips.append(i.key());
|
||||
} else if (Utils::checkIpSubnetFormat(i.value().toString())) {
|
||||
} else if (NetworkUtilities::checkIpSubnetFormat(i.value().toString())) {
|
||||
ips.append(i.value().toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,9 @@ public:
|
||||
}
|
||||
void setSaveLogs(bool enabled);
|
||||
|
||||
QDateTime getLogEnableDate();
|
||||
void setLogEnableDate(QDateTime date);
|
||||
|
||||
enum RouteMode {
|
||||
VpnAllSites,
|
||||
VpnOnlyForwardSites,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -62,7 +62,7 @@ void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state)
|
||||
m_state = state;
|
||||
|
||||
m_isConnected = false;
|
||||
m_connectionStateText = tr("Connection...");
|
||||
m_connectionStateText = tr("Connecting...");
|
||||
switch (state) {
|
||||
case Vpn::ConnectionState::Connected: {
|
||||
m_isConnectionInProgress = false;
|
||||
@@ -76,7 +76,7 @@ void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state)
|
||||
}
|
||||
case Vpn::ConnectionState::Reconnecting: {
|
||||
m_isConnectionInProgress = true;
|
||||
m_connectionStateText = tr("Reconnection...");
|
||||
m_connectionStateText = tr("Reconnecting...");
|
||||
break;
|
||||
}
|
||||
case Vpn::ConnectionState::Disconnected: {
|
||||
@@ -86,7 +86,7 @@ void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state)
|
||||
}
|
||||
case Vpn::ConnectionState::Disconnecting: {
|
||||
m_isConnectionInProgress = true;
|
||||
m_connectionStateText = tr("Disconnection...");
|
||||
m_connectionStateText = tr("Disconnecting...");
|
||||
break;
|
||||
}
|
||||
case Vpn::ConnectionState::Preparing: {
|
||||
|
||||
@@ -337,6 +337,38 @@ void ExportController::generateCloakConfig()
|
||||
emit exportConfigChanged();
|
||||
}
|
||||
|
||||
void ExportController::generateXrayConfig()
|
||||
{
|
||||
clearPreviousConfig();
|
||||
|
||||
int serverIndex = m_serversModel->getProcessedServerIndex();
|
||||
ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex);
|
||||
|
||||
DockerContainer container = static_cast<DockerContainer>(m_containersModel->getCurrentlyProcessedContainerIndex());
|
||||
QJsonObject containerConfig = m_containersModel->getContainerConfig(container);
|
||||
containerConfig.insert(config_key::container, ContainerProps::containerToString(container));
|
||||
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
|
||||
QString clientId;
|
||||
QString config =
|
||||
m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, Proto::Xray, clientId, &errorCode);
|
||||
|
||||
if (errorCode) {
|
||||
emit exportErrorOccurred(errorString(errorCode));
|
||||
return;
|
||||
}
|
||||
config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::Xray, config);
|
||||
QJsonObject configJson = QJsonDocument::fromJson(config.toUtf8()).object();
|
||||
|
||||
QStringList lines = QString(QJsonDocument(configJson).toJson()).replace("\r", "").split("\n");
|
||||
for (const QString &line : lines) {
|
||||
m_config.append(line + "\n");
|
||||
}
|
||||
|
||||
emit exportConfigChanged();
|
||||
}
|
||||
|
||||
QString ExportController::getConfig()
|
||||
{
|
||||
return m_config;
|
||||
|
||||
@@ -37,6 +37,7 @@ public slots:
|
||||
void generateAwgConfig(const QString &clientName);
|
||||
void generateShadowSocksConfig();
|
||||
void generateCloakConfig();
|
||||
void generateXrayConfig();
|
||||
|
||||
QString getConfig();
|
||||
QString getNativeConfigString();
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace
|
||||
Amnezia,
|
||||
OpenVpn,
|
||||
WireGuard,
|
||||
Xray,
|
||||
Backup,
|
||||
Invalid
|
||||
};
|
||||
@@ -34,6 +35,9 @@ namespace
|
||||
const QString wireguardConfigPatternSectionInterface = "[Interface]";
|
||||
const QString wireguardConfigPatternSectionPeer = "[Peer]";
|
||||
|
||||
const QString xrayConfigPatternInbound = "inbounds";
|
||||
const QString xrayConfigPatternOutbound = "outbounds";
|
||||
|
||||
const QString amneziaConfigPattern = "containers";
|
||||
const QString amneziaFreeConfigPattern = "api_key";
|
||||
const QString backupPattern = "Servers/serversList";
|
||||
@@ -49,6 +53,8 @@ namespace
|
||||
} else if (config.contains(wireguardConfigPatternSectionInterface)
|
||||
&& config.contains(wireguardConfigPatternSectionPeer)) {
|
||||
return ConfigTypes::WireGuard;
|
||||
} else if ((config.contains(xrayConfigPatternInbound)) && (config.contains(xrayConfigPatternOutbound))) {
|
||||
return ConfigTypes::Xray;
|
||||
}
|
||||
return ConfigTypes::Invalid;
|
||||
}
|
||||
@@ -79,7 +85,7 @@ bool ImportController::extractConfigFromFile(const QString &fileName)
|
||||
return extractConfigFromData(data);
|
||||
}
|
||||
|
||||
emit importErrorOccurred(tr("Unable to open file"));
|
||||
emit importErrorOccurred(tr("Unable to open file"), false);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -109,6 +115,10 @@ bool ImportController::extractConfigFromData(QString data)
|
||||
m_config = extractWireGuardConfig(config);
|
||||
return m_config.empty() ? false : true;
|
||||
}
|
||||
case ConfigTypes::Xray: {
|
||||
m_config = extractXrayConfig(config);
|
||||
return m_config.empty() ? false : true;
|
||||
}
|
||||
case ConfigTypes::Amnezia: {
|
||||
m_config = QJsonDocument::fromJson(config.toUtf8()).object();
|
||||
return m_config.empty() ? false : true;
|
||||
@@ -117,12 +127,12 @@ bool ImportController::extractConfigFromData(QString data)
|
||||
if (!m_serversModel->getServersCount()) {
|
||||
emit restoreAppConfig(config.toUtf8());
|
||||
} else {
|
||||
emit importErrorOccurred(tr("Invalid configuration file"));
|
||||
emit importErrorOccurred(tr("Invalid configuration file"), false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ConfigTypes::Invalid: {
|
||||
emit importErrorOccurred(tr("Invalid configuration file"));
|
||||
emit importErrorOccurred(tr("Invalid configuration file"), false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -257,6 +267,7 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
|
||||
} else {
|
||||
qDebug() << "Key parameter 'Endpoint' is missing";
|
||||
emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError), false);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (hostNameAndPortMatch.hasCaptured(2)) {
|
||||
@@ -282,7 +293,7 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
|
||||
lastConfig[config_key::server_pub_key] = configMap.value("PublicKey");
|
||||
} else {
|
||||
qDebug() << "One of the key parameters is missing (PrivateKey, Address, PublicKey)";
|
||||
emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError));
|
||||
emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError), false);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
@@ -348,6 +359,42 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
|
||||
return config;
|
||||
}
|
||||
|
||||
QJsonObject ImportController::extractXrayConfig(const QString &data)
|
||||
{
|
||||
QJsonParseError parserErr;
|
||||
QJsonDocument jsonConf = QJsonDocument::fromJson(data.toLocal8Bit(), &parserErr);
|
||||
|
||||
QJsonObject xrayVpnConfig;
|
||||
xrayVpnConfig[config_key::config] = jsonConf.toJson().constData();
|
||||
QJsonObject lastConfig;
|
||||
lastConfig[config_key::last_config] = jsonConf.toJson().constData();
|
||||
lastConfig[config_key::isThirdPartyConfig] = true;
|
||||
|
||||
QJsonObject containers;
|
||||
containers.insert(config_key::container, QJsonValue("amnezia-xray"));
|
||||
containers.insert(config_key::xray, QJsonValue(lastConfig));
|
||||
|
||||
QJsonArray arr;
|
||||
arr.push_back(containers);
|
||||
|
||||
QString hostName;
|
||||
|
||||
const static QRegularExpression hostNameRegExp("\"address\":\\s*\"([^\"]+)");
|
||||
QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(data);
|
||||
if (hostNameMatch.hasMatch()) {
|
||||
hostName = hostNameMatch.captured(1);
|
||||
}
|
||||
|
||||
QJsonObject config;
|
||||
config[config_key::containers] = arr;
|
||||
config[config_key::defaultContainer] = "amnezia-xray";
|
||||
config[config_key::description] = m_settings->nextAvailableServerName();
|
||||
|
||||
config[config_key::hostName] = hostName;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
static QMutex qrDecodeMutex;
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ public slots:
|
||||
signals:
|
||||
void importFinished();
|
||||
void importErrorOccurred(const QString &errorMessage, bool goToPageHome);
|
||||
void importErrorOccurred(const QString &errorMessage);
|
||||
|
||||
void qrDecodingFinished();
|
||||
|
||||
@@ -48,6 +47,7 @@ signals:
|
||||
private:
|
||||
QJsonObject extractOpenVpnConfig(const QString &data);
|
||||
QJsonObject extractWireGuardConfig(const QString &data);
|
||||
QJsonObject extractXrayConfig(const QString &data);
|
||||
|
||||
#if defined Q_OS_ANDROID || defined Q_OS_IOS
|
||||
void stopDecodingQr();
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "core/errorstrings.h"
|
||||
#include "core/controllers/serverController.h"
|
||||
#include "core/networkUtilities.h"
|
||||
#include "utilities.h"
|
||||
#include "ui/models/protocols/awgConfigModel.h"
|
||||
#include "ui/models/protocols/wireguardConfigModel.h"
|
||||
@@ -352,12 +353,12 @@ void InstallController::removeCurrentlyProcessedContainer()
|
||||
|
||||
QRegularExpression InstallController::ipAddressPortRegExp()
|
||||
{
|
||||
return Utils::ipAddressPortRegExp();
|
||||
return NetworkUtilities::ipAddressPortRegExp();
|
||||
}
|
||||
|
||||
QRegularExpression InstallController::ipAddressRegExp()
|
||||
{
|
||||
return Utils::ipAddressRegExp();
|
||||
return NetworkUtilities::ipAddressRegExp();
|
||||
}
|
||||
|
||||
void InstallController::setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName,
|
||||
@@ -450,7 +451,6 @@ void InstallController::mountSftpDrive(const QString &port, const QString &passw
|
||||
process->write((password + "\n").toUtf8());
|
||||
}
|
||||
|
||||
// qDebug().noquote() << "onPushButtonSftpMountDriveClicked" << args;
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ namespace PageLoader
|
||||
PageProtocolOpenVpnSettings,
|
||||
PageProtocolShadowSocksSettings,
|
||||
PageProtocolCloakSettings,
|
||||
PageProtocolXraySettings,
|
||||
PageProtocolWireGuardSettings,
|
||||
PageProtocolAwgSettings,
|
||||
PageProtocolIKev2Settings,
|
||||
@@ -109,6 +110,7 @@ signals:
|
||||
|
||||
void showBusyIndicator(bool visible);
|
||||
void disableControls(bool disabled);
|
||||
void disableTabBar(bool disabled);
|
||||
|
||||
void hideMainWindow();
|
||||
void raiseMainWindow();
|
||||
|
||||
@@ -27,6 +27,7 @@ SettingsController::SettingsController(const QSharedPointer<ServersModel> &serve
|
||||
m_settings(settings)
|
||||
{
|
||||
m_appVersion = QString("%1 (%2, %3)").arg(QString(APP_VERSION), __DATE__, GIT_COMMIT_HASH);
|
||||
checkIfNeedDisableLogs();
|
||||
}
|
||||
|
||||
void SettingsController::toggleAmneziaDns(bool enable)
|
||||
@@ -71,8 +72,11 @@ void SettingsController::toggleLogging(bool enable)
|
||||
{
|
||||
m_settings->setSaveLogs(enable);
|
||||
#ifdef Q_OS_IOS
|
||||
AmneziaVPN::toggleLogging(enable);
|
||||
AmneziaVPN::toggleLogging(enable);
|
||||
#endif
|
||||
if (enable == true) {
|
||||
checkIfNeedDisableLogs();
|
||||
}
|
||||
emit loggingStateChanged();
|
||||
}
|
||||
|
||||
@@ -205,3 +209,13 @@ bool SettingsController::isCameraPresent()
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void SettingsController::checkIfNeedDisableLogs()
|
||||
{
|
||||
m_loggingDisableDate = m_settings->getLogEnableDate().addDays(14);
|
||||
if (m_loggingDisableDate <= QDateTime::currentDateTime()) {
|
||||
toggleLogging(false);
|
||||
clearLogs();
|
||||
emit loggingDisableByWatcher();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,8 @@ signals:
|
||||
|
||||
void amneziaDnsToggled(bool enable);
|
||||
|
||||
void loggingDisableByWatcher();
|
||||
|
||||
private:
|
||||
QSharedPointer<ServersModel> m_serversModel;
|
||||
QSharedPointer<ContainersModel> m_containersModel;
|
||||
@@ -85,6 +87,10 @@ private:
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
|
||||
QString m_appVersion;
|
||||
|
||||
QDateTime m_loggingDisableDate;
|
||||
|
||||
void checkIfNeedDisableLogs();
|
||||
};
|
||||
|
||||
#endif // SETTINGSCONTROLLER_H
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include "systemController.h"
|
||||
#include "utilities.h"
|
||||
#include "core/networkUtilities.h"
|
||||
|
||||
SitesController::SitesController(const std::shared_ptr<Settings> &settings,
|
||||
const QSharedPointer<VpnConnection> &vpnConnection,
|
||||
@@ -25,7 +25,7 @@ void SitesController::addSite(QString hostname)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
|
||||
if (!NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
|
||||
// get domain name if it present
|
||||
hostname.replace("https://", "");
|
||||
hostname.replace("http://", "");
|
||||
@@ -40,7 +40,7 @@ void SitesController::addSite(QString hostname)
|
||||
if (!ip.isEmpty()) {
|
||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection,
|
||||
Q_ARG(QStringList, QStringList() << ip));
|
||||
} else if (Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
|
||||
} else if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
|
||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection,
|
||||
Q_ARG(QStringList, QStringList() << hostname));
|
||||
}
|
||||
@@ -57,7 +57,7 @@ void SitesController::addSite(QString hostname)
|
||||
}
|
||||
};
|
||||
|
||||
if (Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
|
||||
if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
|
||||
processSite(hostname, "");
|
||||
} else {
|
||||
processSite(hostname, "");
|
||||
@@ -110,7 +110,7 @@ void SitesController::importSites(const QString &fileName, bool replaceExisting)
|
||||
auto hostname = jsonObject.value("hostname").toString("");
|
||||
auto ip = jsonObject.value("ip").toString("");
|
||||
|
||||
if (!hostname.contains(".") && !Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
|
||||
if (!hostname.contains(".") && !NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
|
||||
qDebug() << hostname << " not look like ip adress or domain name";
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -81,6 +81,15 @@ int LanguageModel::getCurrentLanguageIndex()
|
||||
}
|
||||
}
|
||||
|
||||
int LanguageModel::getLineHeightAppend()
|
||||
{
|
||||
int langIndex = getCurrentLanguageIndex();
|
||||
switch (langIndex) {
|
||||
case 5: return 10; break; // Burmese
|
||||
default: return 0; break;
|
||||
}
|
||||
}
|
||||
|
||||
QString LanguageModel::getCurrentLanguageName()
|
||||
{
|
||||
return m_availableLanguages[getCurrentLanguageIndex()].name;
|
||||
|
||||
@@ -49,10 +49,12 @@ public:
|
||||
|
||||
Q_PROPERTY(QString currentLanguageName READ getCurrentLanguageName NOTIFY translationsUpdated)
|
||||
Q_PROPERTY(int currentLanguageIndex READ getCurrentLanguageIndex NOTIFY translationsUpdated)
|
||||
Q_PROPERTY(int lineHeightAppend READ getLineHeightAppend NOTIFY translationsUpdated)
|
||||
|
||||
public slots:
|
||||
void changeLanguage(const LanguageSettings::AvailableLanguageEnum language);
|
||||
int getCurrentLanguageIndex();
|
||||
int getLineHeightAppend();
|
||||
QString getCurrentLanguageName();
|
||||
|
||||
signals:
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
#include "xrayConfigModel.h"
|
||||
|
||||
#include "protocols/protocols_defs.h"
|
||||
|
||||
XrayConfigModel::XrayConfigModel(QObject *parent) : QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
int XrayConfigModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool XrayConfigModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
{
|
||||
if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (role) {
|
||||
case Roles::SiteRole: m_protocolConfig.insert(config_key::site, value.toString()); break;
|
||||
}
|
||||
|
||||
emit dataChanged(index, index, QList { role });
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariant XrayConfigModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (role) {
|
||||
case Roles::SiteRole: return m_protocolConfig.value(config_key::site).toString(protocols::xray::defaultSite);
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void XrayConfigModel::updateModel(const QJsonObject &config)
|
||||
{
|
||||
beginResetModel();
|
||||
m_container = ContainerProps::containerFromString(config.value(config_key::container).toString());
|
||||
|
||||
m_fullConfig = config;
|
||||
QJsonObject protocolConfig = config.value(config_key::xray).toObject();
|
||||
|
||||
m_protocolConfig.insert(config_key::site,
|
||||
protocolConfig.value(config_key::site).toString(protocols::xray::defaultSite));
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
QJsonObject XrayConfigModel::getConfig()
|
||||
{
|
||||
m_fullConfig.insert(config_key::xray, m_protocolConfig);
|
||||
return m_fullConfig;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> XrayConfigModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
|
||||
roles[SiteRole] = "site";
|
||||
|
||||
return roles;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef XRAYCONFIGMODEL_H
|
||||
#define XRAYCONFIGMODEL_H
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "containers/containers_defs.h"
|
||||
|
||||
class XrayConfigModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
SiteRole
|
||||
};
|
||||
|
||||
explicit XrayConfigModel(QObject *parent = nullptr);
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
public slots:
|
||||
void updateModel(const QJsonObject &config);
|
||||
QJsonObject getConfig();
|
||||
|
||||
protected:
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
DockerContainer m_container;
|
||||
QJsonObject m_protocolConfig;
|
||||
QJsonObject m_fullConfig;
|
||||
};
|
||||
|
||||
#endif // XRAYCONFIGMODEL_H
|
||||
@@ -79,6 +79,8 @@ PageLoader::PageEnum ProtocolsModel::protocolPage(Proto protocol) const
|
||||
case Proto::WireGuard: return PageLoader::PageEnum::PageProtocolWireGuardSettings;
|
||||
case Proto::Ikev2: return PageLoader::PageEnum::PageProtocolIKev2Settings;
|
||||
case Proto::L2tp: return PageLoader::PageEnum::PageProtocolIKev2Settings;
|
||||
case Proto::Xray: return PageLoader::PageEnum::PageProtocolXraySettings;
|
||||
|
||||
// non-vpn
|
||||
case Proto::TorWebSite: return PageLoader::PageEnum::PageServiceTorWebsiteSettings;
|
||||
case Proto::Dns: return PageLoader::PageEnum::PageServiceDnsSettings;
|
||||
|
||||
@@ -57,6 +57,12 @@ ListView {
|
||||
PageController.goToPage(PageEnum.PageProtocolOpenVpnSettings)
|
||||
break
|
||||
}
|
||||
case ContainerEnum.Xray: {
|
||||
XrayConfigModel.updateModel(config)
|
||||
PageController.goToPage(PageEnum.PageProtocolXraySettings)
|
||||
break
|
||||
}
|
||||
|
||||
case ContainerEnum.WireGuard: {
|
||||
WireGuardConfigModel.updateModel(config)
|
||||
PageController.goToPage(PageEnum.PageProtocolWireGuardSettings)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
|
||||
Text {
|
||||
lineHeight: 16
|
||||
lineHeight: 16 + LanguageModel.getLineHeightAppend()
|
||||
lineHeightMode: Text.FixedHeight
|
||||
|
||||
color: "#0E0E11"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
|
||||
Text {
|
||||
lineHeight: 38
|
||||
lineHeight: 38 + LanguageModel.getLineHeightAppend()
|
||||
lineHeightMode: Text.FixedHeight
|
||||
|
||||
color: "#D7D8DB"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
|
||||
Text {
|
||||
lineHeight: 30
|
||||
lineHeight: 30 + LanguageModel.getLineHeightAppend()
|
||||
lineHeightMode: Text.FixedHeight
|
||||
|
||||
color: "#D7D8DB"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
|
||||
Text {
|
||||
lineHeight: 16
|
||||
lineHeight: 16 + LanguageModel.getLineHeightAppend()
|
||||
lineHeightMode: Text.FixedHeight
|
||||
|
||||
color: "#878B91"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
|
||||
Text {
|
||||
lineHeight: 21.6
|
||||
lineHeight: 21.6 + LanguageModel.getLineHeightAppend()
|
||||
lineHeightMode: Text.FixedHeight
|
||||
|
||||
color: "#D7D8DB"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
|
||||
Text {
|
||||
lineHeight: 24
|
||||
lineHeight: 24 + LanguageModel.getLineHeightAppend()
|
||||
lineHeightMode: Text.FixedHeight
|
||||
|
||||
color: "#D7D8DB"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
|
||||
Text {
|
||||
lineHeight: 20
|
||||
lineHeight: 20 + LanguageModel.getLineHeightAppend()
|
||||
lineHeightMode: Text.FixedHeight
|
||||
|
||||
color: "#D7D8DB"
|
||||
|
||||
@@ -89,12 +89,12 @@ RadioButton {
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
implicitWidth: content.implicitWidth
|
||||
implicitHeight: content.implicitHeight
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: 8 + background.width
|
||||
|
||||
implicitHeight: content.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import "TextTypes"
|
||||
|
||||
Rectangle {
|
||||
property string textColor: "#D7D8DB"
|
||||
property string backGroundColor: "#1C1D21"
|
||||
property string textString
|
||||
|
||||
property string iconPath
|
||||
property real iconWidth: 16
|
||||
property real iconHeight: 16
|
||||
|
||||
color: backGroundColor
|
||||
radius: 8
|
||||
implicitHeight: content.implicitHeight + content.anchors.topMargin + content.anchors.bottomMargin
|
||||
|
||||
RowLayout {
|
||||
id: content
|
||||
width: parent.width
|
||||
anchors.fill: parent
|
||||
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
anchors.topMargin: 8
|
||||
anchors.bottomMargin: 8
|
||||
|
||||
spacing: 0
|
||||
|
||||
Image {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
width: iconWidth
|
||||
height: iconHeight
|
||||
|
||||
source: iconPath
|
||||
}
|
||||
|
||||
CaptionTextType {
|
||||
id: supportingText
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 8
|
||||
|
||||
text: textString
|
||||
color: textColor
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,8 @@ import "../Config"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
Component.onCompleted: PageController.disableControls(true)
|
||||
Component.onDestruction: PageController.disableControls(false)
|
||||
Component.onCompleted: PageController.disableTabBar(true)
|
||||
Component.onDestruction: PageController.disableTabBar(false)
|
||||
|
||||
SortFilterProxyModel {
|
||||
id: proxyServersModel
|
||||
|
||||
+139
-130
@@ -33,44 +33,71 @@ PageType {
|
||||
anchors.fill: parent
|
||||
anchors.bottomMargin: drawer.collapsedHeight
|
||||
|
||||
ConnectButton {
|
||||
id: connectButton
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 34
|
||||
anchors.bottomMargin: 34
|
||||
leftPadding: 16
|
||||
rightPadding: 16
|
||||
|
||||
implicitHeight: 36
|
||||
BasicButtonType {
|
||||
property bool isLoggingEnabled: SettingsController.isLoggingEnabled
|
||||
|
||||
defaultColor: "transparent"
|
||||
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
|
||||
pressedColor: Qt.rgba(1, 1, 1, 0.12)
|
||||
disabledColor: "#878B91"
|
||||
textColor: "#878B91"
|
||||
leftImageColor: "transparent"
|
||||
borderWidth: 0
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
property bool isSplitTunnelingEnabled: SitesModel.isTunnelingEnabled ||
|
||||
(ServersModel.isDefaultServerDefaultContainerHasSplitTunneling && ServersModel.getDefaultServerData("isServerFromApi"))
|
||||
implicitHeight: 36
|
||||
|
||||
text: isSplitTunnelingEnabled ? qsTr("Split tunneling enabled") : qsTr("Split tunneling disabled")
|
||||
defaultColor: "transparent"
|
||||
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
|
||||
pressedColor: Qt.rgba(1, 1, 1, 0.12)
|
||||
disabledColor: "#878B91"
|
||||
textColor: "#878B91"
|
||||
borderWidth: 0
|
||||
|
||||
imageSource: isSplitTunnelingEnabled ? "qrc:/images/controls/split-tunneling.svg" : ""
|
||||
rightImageSource: "qrc:/images/controls/chevron-down.svg"
|
||||
visible: isLoggingEnabled ? true : false
|
||||
text: qsTr("Logging enabled")
|
||||
|
||||
onClicked: {
|
||||
homeSplitTunnelingDrawer.open()
|
||||
onClicked: {
|
||||
PageController.goToPage(PageEnum.PageSettingsLogging)
|
||||
}
|
||||
}
|
||||
|
||||
HomeSplitTunnelingDrawer {
|
||||
id: homeSplitTunnelingDrawer
|
||||
ConnectButton {
|
||||
id: connectButton
|
||||
Layout.fillHeight: true
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
}
|
||||
|
||||
parent: root
|
||||
BasicButtonType {
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
|
||||
Layout.bottomMargin: 34
|
||||
leftPadding: 16
|
||||
rightPadding: 16
|
||||
|
||||
implicitHeight: 36
|
||||
|
||||
defaultColor: "transparent"
|
||||
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
|
||||
pressedColor: Qt.rgba(1, 1, 1, 0.12)
|
||||
disabledColor: "#878B91"
|
||||
textColor: "#878B91"
|
||||
leftImageColor: "transparent"
|
||||
borderWidth: 0
|
||||
|
||||
property bool isSplitTunnelingEnabled: SitesModel.isTunnelingEnabled ||
|
||||
(ServersModel.isDefaultServerDefaultContainerHasSplitTunneling && ServersModel.getDefaultServerData("isServerFromApi"))
|
||||
|
||||
text: isSplitTunnelingEnabled ? qsTr("Split tunneling enabled") : qsTr("Split tunneling disabled")
|
||||
|
||||
imageSource: isSplitTunnelingEnabled ? "qrc:/images/controls/split-tunneling.svg" : ""
|
||||
rightImageSource: "qrc:/images/controls/chevron-down.svg"
|
||||
|
||||
onClicked: {
|
||||
homeSplitTunnelingDrawer.open()
|
||||
}
|
||||
|
||||
HomeSplitTunnelingDrawer {
|
||||
id: homeSplitTunnelingDrawer
|
||||
parent: root
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -272,129 +299,111 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
Flickable {
|
||||
id: serversContainer
|
||||
|
||||
ButtonGroup {
|
||||
id: serversRadioButtonGroup
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: serversMenuContent
|
||||
|
||||
anchors.top: serversMenuHeader.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.topMargin: 16
|
||||
|
||||
contentHeight: col.height + col.anchors.bottomMargin
|
||||
implicitHeight: parent.height - serversMenuHeader.implicitHeight
|
||||
clip: true
|
||||
model: ServersModel
|
||||
currentIndex: ServersModel.defaultIndex
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
id: scrollBar
|
||||
policy: serversContainer.height >= serversContainer.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn
|
||||
policy: serversMenuContent.height >= serversMenuContent.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn
|
||||
}
|
||||
|
||||
Keys.onUpPressed: scrollBar.decrease()
|
||||
Keys.onDownPressed: scrollBar.increase()
|
||||
|
||||
Column {
|
||||
id: col
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottomMargin: 32
|
||||
|
||||
spacing: 16
|
||||
|
||||
ButtonGroup {
|
||||
id: serversRadioButtonGroup
|
||||
Connections {
|
||||
target: ServersModel
|
||||
function onDefaultServerIndexChanged(serverIndex) {
|
||||
serversMenuContent.currentIndex = serverIndex
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: serversMenuContent
|
||||
width: parent.width
|
||||
height: serversMenuContent.contentItem.height
|
||||
clip: true
|
||||
|
||||
model: ServersModel
|
||||
currentIndex: ServersModel.defaultIndex
|
||||
delegate: Item {
|
||||
id: menuContentDelegate
|
||||
|
||||
Connections {
|
||||
target: ServersModel
|
||||
function onDefaultServerIndexChanged(serverIndex) {
|
||||
serversMenuContent.currentIndex = serverIndex
|
||||
property variant delegateData: model
|
||||
|
||||
implicitWidth: serversMenuContent.width
|
||||
implicitHeight: serverRadioButtonContent.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: serverRadioButtonContent
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 16
|
||||
anchors.leftMargin: 16
|
||||
|
||||
spacing: 0
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
VerticalRadioButton {
|
||||
id: serverRadioButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: name
|
||||
descriptionText: serverDescription
|
||||
|
||||
checked: index === serversMenuContent.currentIndex
|
||||
checkable: !ConnectionController.isConnected
|
||||
|
||||
ButtonGroup.group: serversRadioButtonGroup
|
||||
|
||||
onClicked: {
|
||||
if (ConnectionController.isConnected) {
|
||||
PageController.showNotificationMessage(qsTr("Unable change server while there is an active connection"))
|
||||
return
|
||||
}
|
||||
|
||||
serversMenuContent.currentIndex = index
|
||||
|
||||
ServersModel.defaultIndex = index
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: serverRadioButton
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
|
||||
ImageButtonType {
|
||||
image: "qrc:/images/controls/settings.svg"
|
||||
imageColor: "#D7D8DB"
|
||||
|
||||
implicitWidth: 56
|
||||
implicitHeight: 56
|
||||
|
||||
z: 1
|
||||
|
||||
onClicked: function() {
|
||||
ServersModel.processedIndex = index
|
||||
PageController.goToPage(PageEnum.PageSettingsServerInfo)
|
||||
drawer.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clip: true
|
||||
interactive: false
|
||||
|
||||
delegate: Item {
|
||||
id: menuContentDelegate
|
||||
|
||||
property variant delegateData: model
|
||||
|
||||
implicitWidth: serversMenuContent.width
|
||||
implicitHeight: serverRadioButtonContent.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: serverRadioButtonContent
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 16
|
||||
anchors.leftMargin: 16
|
||||
|
||||
spacing: 0
|
||||
|
||||
RowLayout {
|
||||
VerticalRadioButton {
|
||||
id: serverRadioButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: name
|
||||
descriptionText: serverDescription
|
||||
|
||||
checked: index === serversMenuContent.currentIndex
|
||||
checkable: !ConnectionController.isConnected
|
||||
|
||||
ButtonGroup.group: serversRadioButtonGroup
|
||||
|
||||
onClicked: {
|
||||
if (ConnectionController.isConnected) {
|
||||
PageController.showNotificationMessage(qsTr("Unable change server while there is an active connection"))
|
||||
return
|
||||
}
|
||||
|
||||
serversMenuContent.currentIndex = index
|
||||
|
||||
ServersModel.defaultIndex = index
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: serverRadioButton
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
|
||||
ImageButtonType {
|
||||
image: "qrc:/images/controls/settings.svg"
|
||||
imageColor: "#D7D8DB"
|
||||
|
||||
implicitWidth: 56
|
||||
implicitHeight: 56
|
||||
|
||||
z: 1
|
||||
|
||||
onClicked: function() {
|
||||
ServersModel.processedIndex = index
|
||||
PageController.goToPage(PageEnum.PageSettingsServerInfo)
|
||||
drawer.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 0
|
||||
Layout.rightMargin: 0
|
||||
}
|
||||
}
|
||||
DividerType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 0
|
||||
Layout.rightMargin: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,7 +325,7 @@ PageType {
|
||||
|
||||
onClicked: {
|
||||
var headerText = qsTr("Remove AmneziaWG from server?")
|
||||
var descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.")
|
||||
var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")
|
||||
var yesButtonText = qsTr("Continue")
|
||||
var noButtonText = qsTr("Cancel")
|
||||
|
||||
@@ -359,15 +359,24 @@ PageType {
|
||||
|
||||
text: qsTr("Save")
|
||||
|
||||
clickedFunc: function() {
|
||||
forceActiveFocus()
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateContainer(AwgConfigModel.getConfig())
|
||||
onClicked: {
|
||||
var headerText = qsTr("Save settings?")
|
||||
var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")
|
||||
var yesButtonText = qsTr("Continue")
|
||||
var noButtonText = qsTr("Cancel")
|
||||
|
||||
var yesButtonFunction = function() {
|
||||
forceActiveFocus()
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateContainer(AwgConfigModel.getConfig())
|
||||
}
|
||||
var noButtonFunction = function() {
|
||||
}
|
||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,7 +382,7 @@ PageType {
|
||||
|
||||
clickedFunc: function() {
|
||||
var headerText = qsTr("Remove OpenVpn from server?")
|
||||
var descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.")
|
||||
var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")
|
||||
var yesButtonText = qsTr("Continue")
|
||||
var noButtonText = qsTr("Cancel")
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ PageType {
|
||||
|
||||
clickedFunction: function() {
|
||||
var headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName())
|
||||
var descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.")
|
||||
var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")
|
||||
var yesButtonText = qsTr("Continue")
|
||||
var noButtonText = qsTr("Cancel")
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ PageType {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
enabled: ServersModel.isCurrentlyProcessedServerHasWriteAccess()
|
||||
enabled: ServersModel.isProcessedServerHasWriteAccess()
|
||||
|
||||
ListView {
|
||||
id: listview
|
||||
@@ -126,21 +126,20 @@ PageType {
|
||||
|
||||
text: qsTr("Remove WG")
|
||||
|
||||
onClicked: {
|
||||
questionDrawer.headerText = qsTr("Remove WG from server?")
|
||||
questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.")
|
||||
questionDrawer.yesButtonText = qsTr("Continue")
|
||||
questionDrawer.noButtonText = qsTr("Cancel")
|
||||
clickedFunc: function() {
|
||||
var headerText = qsTr("Remove WG from server?")
|
||||
var descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.")
|
||||
var yesButtonText = qsTr("Continue")
|
||||
var noButtonText = qsTr("Cancel")
|
||||
|
||||
questionDrawer.yesButtonFunction = function() {
|
||||
questionDrawer.visible = false
|
||||
var yesButtonFunction = function() {
|
||||
PageController.goToPage(PageEnum.PageDeinstalling)
|
||||
InstallController.removeCurrentlyProcessedContainer()
|
||||
}
|
||||
questionDrawer.noButtonFunction = function() {
|
||||
questionDrawer.visible = false
|
||||
var noButtonFunction = function() {
|
||||
}
|
||||
questionDrawer.visible = true
|
||||
|
||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
import PageEnum 1.0
|
||||
import ContainerEnum 1.0
|
||||
|
||||
import "./"
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
import "../Config"
|
||||
import "../Components"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
ColumnLayout {
|
||||
id: backButton
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
anchors.topMargin: 20
|
||||
|
||||
BackButtonType {
|
||||
}
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
id: fl
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
contentHeight: content.implicitHeight
|
||||
|
||||
Column {
|
||||
id: content
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
enabled: ServersModel.isProcessedServerHasWriteAccess()
|
||||
|
||||
ListView {
|
||||
id: listview
|
||||
|
||||
width: parent.width
|
||||
height: listview.contentItem.height
|
||||
|
||||
clip: true
|
||||
interactive: false
|
||||
|
||||
model: XrayConfigModel
|
||||
|
||||
delegate: Item {
|
||||
implicitWidth: listview.width
|
||||
implicitHeight: col.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: col
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
|
||||
spacing: 0
|
||||
|
||||
HeaderType {
|
||||
Layout.fillWidth: true
|
||||
headerText: qsTr("XRay settings")
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 32
|
||||
|
||||
headerText: qsTr("Disguised as traffic from")
|
||||
textFieldText: site
|
||||
|
||||
textField.onEditingFinished: {
|
||||
if (textFieldText !== site) {
|
||||
var tmpText = textFieldText
|
||||
tmpText = tmpText.toLocaleLowerCase()
|
||||
|
||||
var indexHttps = tmpText.indexOf("https://")
|
||||
if (indexHttps === 0) {
|
||||
tmpText = textFieldText.substring(8)
|
||||
} else {
|
||||
site = textFieldText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
Layout.topMargin: 24
|
||||
Layout.leftMargin: -8
|
||||
implicitHeight: 32
|
||||
|
||||
visible: ContainersModel.getCurrentlyProcessedContainerIndex() === ContainerEnum.Xray
|
||||
|
||||
defaultColor: "transparent"
|
||||
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
|
||||
pressedColor: Qt.rgba(1, 1, 1, 0.12)
|
||||
textColor: "#EB5757"
|
||||
|
||||
text: qsTr("Remove XRay")
|
||||
|
||||
clickedFunc: function() {
|
||||
var headerText = qsTr("Remove XRay from server?")
|
||||
var descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.")
|
||||
var yesButtonText = qsTr("Continue")
|
||||
var noButtonText = qsTr("Cancel")
|
||||
|
||||
var yesButtonFunction = function() {
|
||||
PageController.goToPage(PageEnum.PageDeinstalling)
|
||||
InstallController.removeCurrentlyProcessedContainer()
|
||||
}
|
||||
var noButtonFunction = function() {
|
||||
}
|
||||
|
||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
}
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
Layout.bottomMargin: 24
|
||||
|
||||
text: qsTr("Save and Restart Amnezia")
|
||||
|
||||
onClicked: {
|
||||
forceActiveFocus()
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateContainer(XrayConfigModel.getConfig())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QuestionDrawer {
|
||||
id: questionDrawer
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -121,7 +121,7 @@ PageType {
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Login")
|
||||
text: qsTr("User name")
|
||||
descriptionText: username
|
||||
|
||||
descriptionOnTop: true
|
||||
|
||||
@@ -68,44 +68,10 @@ PageType {
|
||||
height: 20
|
||||
font.pixelSize: 14
|
||||
|
||||
text: qsTr("This is a free and open source application. If you like it, support the developers with a donation. ") +
|
||||
qsTr("And if you don’t like the application, all the more reason to support it - the donation will be used for the improving the application.")
|
||||
text: qsTr("Amnezia is a free and open-source application. You can support the developers if you like it.")
|
||||
color: "#CCCAC8"
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
text: qsTr("Card on Patreon")
|
||||
|
||||
clickedFunc: function() {
|
||||
Qt.openUrlExternally(qsTr("https://www.patreon.com/amneziavpn"))
|
||||
}
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
defaultColor: "transparent"
|
||||
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
|
||||
pressedColor: Qt.rgba(1, 1, 1, 0.12)
|
||||
disabledColor: "#878B91"
|
||||
textColor: "#D7D8DB"
|
||||
borderWidth: 1
|
||||
|
||||
text: qsTr("Show other methods on Github")
|
||||
|
||||
clickedFunc: function() {
|
||||
Qt.openUrlExternally(qsTr("https://github.com/amnezia-vpn/amnezia-client#donate"))
|
||||
}
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 32
|
||||
@@ -197,6 +163,25 @@ PageType {
|
||||
Qt.openUrlExternally("https://github.com/amnezia-vpn/desktop-client/releases/latest")
|
||||
}
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.bottomMargin: 16
|
||||
Layout.topMargin: -15
|
||||
implicitHeight: 25
|
||||
|
||||
defaultColor: "transparent"
|
||||
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
|
||||
pressedColor: Qt.rgba(1, 1, 1, 0.12)
|
||||
disabledColor: "#878B91"
|
||||
textColor: "#FBB26A"
|
||||
|
||||
text: qsTr("Privacy Policy")
|
||||
|
||||
clickedFunc: function() {
|
||||
Qt.openUrlExternally("https://amnezia.org/en/policy")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,22 +63,18 @@ PageType {
|
||||
HeaderType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
headerText: qsTr("Backup")
|
||||
headerText: qsTr("Back up your configuration")
|
||||
descriptionText: qsTr("You can save your settings to a backup file to restore them the next time you install the application.")
|
||||
}
|
||||
|
||||
ListItemTitleType {
|
||||
WarningType {
|
||||
Layout.topMargin: 16
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 10
|
||||
|
||||
text: qsTr("Configuration backup")
|
||||
}
|
||||
textString: qsTr("The backup will contain your passwords and private keys for all servers added " +
|
||||
"to AmneziaVPN. Keep this information in a secure place.")
|
||||
|
||||
CaptionTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: -12
|
||||
|
||||
text: qsTr("You can save your settings to a backup file to restore them the next time you install the application.")
|
||||
color: "#878B91"
|
||||
iconPath: "qrc:/images/controls/alert-circle.svg"
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
|
||||
@@ -15,6 +15,18 @@ import "../Controls2/TextTypes"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
Connections {
|
||||
target: SettingsController
|
||||
|
||||
function onLoggingStateChanged() {
|
||||
if (SettingsController.isLoggingEnabled) {
|
||||
var message = qsTr("Logging is enabled. Note that logs will be automatically \
|
||||
disabled after 14 days, and all log files will be deleted.")
|
||||
PageController.showNotificationMessage(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
@@ -45,6 +57,8 @@ PageType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
headerText: qsTr("Logging")
|
||||
descriptionText: qsTr("Enabling this function will save application's logs automatically, " +
|
||||
"By default, logging functionality is disabled. Enable log saving in case of application malfunction.")
|
||||
}
|
||||
|
||||
SwitcherType {
|
||||
|
||||
@@ -166,7 +166,7 @@ PageType {
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Remove this server from the app")
|
||||
text: qsTr("Remove server from application")
|
||||
textColor: "#EB5757"
|
||||
|
||||
clickedFunction: function() {
|
||||
@@ -196,12 +196,12 @@ PageType {
|
||||
visible: content.isServerWithWriteAccess
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Clear server Amnezia-installed services")
|
||||
text: qsTr("Clear server from Amnezia software")
|
||||
textColor: "#EB5757"
|
||||
|
||||
clickedFunction: function() {
|
||||
var headerText = qsTr("Do you want to clear server Amnezia-installed services?")
|
||||
var descriptionText = qsTr("All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.")
|
||||
var headerText = qsTr("Do you want to clear server from Amnezia software?")
|
||||
var descriptionText = qsTr("All users whom you shared a connection with will no longer be able to connect to it.")
|
||||
var yesButtonText = qsTr("Continue")
|
||||
var noButtonText = qsTr("Cancel")
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ PageType {
|
||||
}
|
||||
TabButtonType {
|
||||
isSelected: tabBar.currentIndex === 2
|
||||
text: qsTr("Data")
|
||||
text: qsTr("Management")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@ PageType {
|
||||
case ProtocolEnum.OpenVpn: OpenVpnConfigModel.updateModel(ProtocolsModel.getConfig()); break;
|
||||
case ProtocolEnum.ShadowSocks: ShadowSocksConfigModel.updateModel(ProtocolsModel.getConfig()); break;
|
||||
case ProtocolEnum.Cloak: CloakConfigModel.updateModel(ProtocolsModel.getConfig()); break;
|
||||
case ProtocolEnum.Xray: XrayConfigModel.updateModel(ProtocolsModel.getConfig()); break;
|
||||
case ProtocolEnum.WireGuard: WireGuardConfigModel.updateModel(ProtocolsModel.getConfig()); break;
|
||||
case ProtocolEnum.Ipsec: Ikev2ConfigModel.updateModel(ProtocolsModel.getConfig()); break;
|
||||
}
|
||||
|
||||
@@ -69,8 +69,8 @@ PageType {
|
||||
leftImageSource: "qrc:/images/controls/folder-open.svg"
|
||||
|
||||
clickedFunction: function() {
|
||||
var nameFilter = !ServersModel.getServersCount() ? "Config or backup files (*.vpn *.ovpn *.conf *.backup)" :
|
||||
"Config files (*.vpn *.ovpn *.conf)"
|
||||
var nameFilter = !ServersModel.getServersCount() ? "Config or backup files (*.vpn *.ovpn *.conf *.json *.backup)" :
|
||||
"Config files (*.vpn *.ovpn *.conf *.json)"
|
||||
var fileName = SystemController.getFileName(qsTr("Open config file"), nameFilter)
|
||||
if (fileName !== "") {
|
||||
if (ImportController.extractConfigFromFile(fileName)) {
|
||||
|
||||
@@ -67,7 +67,7 @@ PageType {
|
||||
id: username
|
||||
|
||||
Layout.fillWidth: true
|
||||
headerText: qsTr("Login to connect via SSH")
|
||||
headerText: qsTr("SSH Username")
|
||||
textFieldPlaceholderText: "root"
|
||||
|
||||
textField.onFocusChanged: {
|
||||
|
||||
@@ -136,8 +136,7 @@ PageType {
|
||||
CardType {
|
||||
implicitWidth: parent.width
|
||||
|
||||
headerText: qsTr("Set up a VPN yourself")
|
||||
bodyText: qsTr("I want to choose a VPN protocol")
|
||||
headerText: qsTr("Choose a VPN protocol")
|
||||
|
||||
ButtonGroup.group: buttonGroup
|
||||
|
||||
@@ -194,7 +193,7 @@ PageType {
|
||||
return true
|
||||
}
|
||||
|
||||
text: qsTr("Set up later")
|
||||
text: qsTr("Skip setup")
|
||||
|
||||
clickedFunc: function() {
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user