Compare commits

...

22 Commits

Author SHA1 Message Date
vladimir.kuznetsov e9468a4c2f fixed display of cancel button on install/uninstall pages 2024-03-31 12:40:42 +05:00
pokamest db8d966fac Merge pull request #714 from amnezia-vpn/bugfix/wg-and-xray-remove-button 2024-03-29 09:40:13 +00:00
pokamest 6b69bc9618 Tiny fixes 2024-03-28 17:13:48 +00:00
pokamest 0089b0b799 Error code 206 description 2024-03-28 15:01:17 +00:00
vladimir.kuznetsov e4841e809b fixed remove button for wireguard and xray settings page 2024-03-28 15:33:23 +05:00
Mykola Baibuz ba4237f1dd Xray with Reality protocol (#494)
* Xray with Reality for desktops
2024-03-27 11:02:34 +00:00
pokamest f6acec53c0 Merge pull request #712 from amnezia-vpn/bugfix/page-home-recursive-rearrange
fixed recursive rearrange on PageHome
2024-03-27 10:59:01 +00:00
Shehab Ahmed 5f631eaa76 Refactoring/change application text (#687)
Changing some texts
2024-03-26 18:05:04 +00:00
albexk 7730dd510c Add error handling of enabled "always-on" during VPN connection (#698)
* Always add awg-go version to the log
* Display an error message always when no vpn permission is granted
2024-03-25 23:09:50 +00:00
pokamest 30bd264f17 Merge pull request #711 from amnezia-vpn/bugfix/anchors-page-home-warning
fixed anchors warning on PageHome
2024-03-25 18:38:20 +00:00
vladimir.kuznetsov 5206665fa0 fixed recursive rearrange on PageHome 2024-03-25 22:30:44 +05:00
vladimir.kuznetsov 073491ccb4 fixed anchors warning on PageHome 2024-03-25 21:52:38 +05:00
pokamest 561b62cd40 Merge pull request #705 from amnezia-vpn/bugfix/import-error-handling
fixed error handling for config import
2024-03-23 13:23:31 +00:00
pokamest 1284ed4d84 Merge pull request #706 from amnezia-vpn/translations/connection-label-fix
Fix connection button labels
2024-03-23 00:30:10 +00:00
Andrey Zaharow 6f34443191 Fix connection button labels 2024-03-21 21:34:51 +01:00
vladimir.kuznetsov 02f186c54e fixed error handling for config import 2024-03-21 23:32:11 +05:00
alexeyq2 784c6cf585 Fix AWG/WG on Linux - IPv6 gateway address is ULA now (#701) 2024-03-21 15:03:00 +00:00
pokamest 14f132e127 Merge pull request #703 from amnezia-vpn/feature/linux-ipc-fix
Increase timeout for IPC command
2024-03-21 13:29:14 +00:00
Mykola Baibuz 9cb624e681 Increase timeout for IPC command 2024-03-20 23:10:29 +02:00
isamnezia 516e3da7e2 Fix open log crash and side log improvements (#694)
Fix open log crash
2024-03-20 15:35:36 +00:00
Andrey Zaharow 0e83586cae Fix UI for Burmese language (#682)
* Fix UI for Burmese language
2024-03-20 15:20:09 +00:00
Nethius 95bdae68f4 Auto disable logs after 14 days (#610)
Auto disable logs after 14 days
2024-03-20 14:22:29 +00:00
123 changed files with 4522 additions and 2569 deletions
+2 -2
View File
@@ -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")
+6 -2
View File
@@ -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()
+3
View File
@@ -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());
+2
View File
@@ -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
+7
View File
@@ -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>
+7
View File
@@ -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
}
@@ -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())
}
-2
View File
@@ -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;
}
+8 -4
View File
@@ -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();
}
+2
View File
@@ -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;
}
+19
View File
@@ -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
+23 -2
View File
@@ -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;
}
+1
View File
@@ -22,6 +22,7 @@ namespace amnezia
Cloak,
ShadowSocks,
Ipsec,
Xray,
// non-vpn
TorWebSite,
+7 -7
View File
@@ -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 {
+4
View File
@@ -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,
+1
View File
@@ -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;
+1 -1
View File
@@ -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();
+439
View File
@@ -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
}
+32
View File
@@ -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
+3 -1
View File
@@ -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();
}
}
+2 -1
View File
@@ -27,7 +27,8 @@ enum ProtocolScriptType {
container_startup,
openvpn_template,
wireguard_template,
awg_template
awg_template,
xray_template
};
+5
View File
@@ -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

+5
View File
@@ -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

+2 -1
View File
@@ -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
)
+8 -4
View File
@@ -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));
+31 -4
View File
@@ -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 }
+26 -5
View File
@@ -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
}
}
}
}
+10 -10
View File
@@ -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)
}
@@ -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
}
}
}
+36 -338
View File
@@ -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 {
-11
View File
@@ -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;
+1 -9
View File
@@ -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;
}
+8
View File
@@ -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)
+16 -1
View File
@@ -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,
+2
View File
@@ -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;
}
+231
View File
@@ -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();
}
+41
View File
@@ -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
+9
View File
@@ -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>
+53
View File
@@ -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"
+26
View File
@@ -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
+46
View File
@@ -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
View File
@@ -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());
}
}
+3
View File
@@ -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;
+1
View File
@@ -37,6 +37,7 @@ public slots:
void generateAwgConfig(const QString &clientName);
void generateShadowSocksConfig();
void generateCloakConfig();
void generateXrayConfig();
QString getConfig();
QString getNativeConfigString();
+51 -4
View File
@@ -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;
+1 -1
View File
@@ -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();
+3 -3
View File
@@ -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
}
+2
View File
@@ -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();
+15 -1
View File
@@ -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 -5
View File
@@ -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;
}
+9
View File
@@ -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;
+2
View File
@@ -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
+2
View File
@@ -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
+51
View File
@@ -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
}
}
}
+2 -2
View File
@@ -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
View File
@@ -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")
+1 -1
View File
@@ -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
+20 -35
View File
@@ -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 dont 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")
}
}
}
}
}
+7 -11
View File
@@ -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: {
+2 -3
View File
@@ -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