Compare commits

...

55 Commits

Author SHA1 Message Date
KsZnak db1dc78e38 Update PageSettingsServerData.qml 2024-02-04 21:59:55 +02:00
agalehaga fd98ef1250 Reboot server button (#553)
* add button Reboot Server
2024-02-04 16:52:03 +00:00
pokamest cdf46c968a Merge pull request #480 from amnezia-vpn/feature/error-code-output
added error code output
2024-02-01 05:05:36 -08:00
pokamest 72b20ef563 Version bump - 4.3.0.0 2024-01-31 20:37:10 +00:00
pokamest 30a0ac0def Merge pull request #527 from amnezia-vpn/feature/api-awg
added support for awg configs for api
2024-01-31 12:04:54 -08:00
albexk 090208bd2c Bug fix for processing exclude routes for older android versions 2024-01-31 19:59:00 +03:00
vladimir.kuznetsov 1e5c9c9c4d if allowedIps from the backend is empty, split tunneling of the application works 2024-01-31 21:41:46 +07:00
vladimir.kuznetsov 5918f37ffa Merge branch 'feature/api-awg' of github.com:amnezia-vpn/amnezia-client into feature/api-awg 2024-01-31 21:30:47 +07:00
vladimir.kuznetsov 554e1b1b91 Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into feature/api-awg 2024-01-31 21:30:18 +07:00
vladimir.kuznetsov 8f510c1431 added allowedIPs processing for configs from the backend 2024-01-31 21:29:39 +07:00
vladimir.kuznetsov 4723019624 Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into HEAD 2024-01-31 12:42:19 +07:00
vladimir.kuznetsov 1be9078b6c added break after each line in errorstrings 2024-01-31 12:42:05 +07:00
pokamest 520658a295 Merge pull request #550 from amnezia-vpn/KsZnak-patch-1
Ks znak patch 1
2024-01-30 15:58:50 -08:00
pokamest b9ec722abb Merge pull request #543 from amnezia-vpn/KsZnak-patch-7
Update PageSettingsSplitTunneling.qml
2024-01-30 15:57:40 -08:00
pokamest d917c798d7 Merge pull request #544 from amnezia-vpn/KsZnak-patch-8
Update PageSettingsAbout.qml
2024-01-30 15:57:10 -08:00
pokamest 0d168c039f Merge pull request #545 from amnezia-vpn/KsZnak-patch-6
Update PageSettingsSplitTunneling.qml
2024-01-30 15:56:50 -08:00
pokamest af8f265555 Merge pull request #546 from amnezia-vpn/KsZnak-patch-5
Update PageSettingsConnection.qml
2024-01-30 15:56:29 -08:00
pokamest c0e0d64284 Merge pull request #547 from amnezia-vpn/KsZnak-patch-4
Update PageServiceTorWebsiteSettings.qml
2024-01-30 15:56:02 -08:00
pokamest 9466a71141 Merge pull request #548 from amnezia-vpn/KsZnak-patch-3
Update PageProtocolOpenVpnSettings.qml
2024-01-30 15:55:29 -08:00
pokamest d28a586a97 Merge pull request #549 from amnezia-vpn/KsZnak-patch-2
Update containers_defs.cpp
2024-01-30 15:55:04 -08:00
pokamest 6fde0b6663 Merge pull request #551 from amnezia-vpn/KsZnak-change-eng-text
Update PageSetupWizardCredentials.qml
2024-01-30 15:54:10 -08:00
KsZnak d143b9213b Update PageSettingsAbout.qml 2024-01-30 20:50:30 +02:00
KsZnak 16433e9e46 Update PageSettingsSplitTunneling.qml 2024-01-30 20:40:11 +02:00
KsZnak 39479d1999 Update PageSettingsSplitTunneling.qml 2024-01-30 20:35:05 +02:00
KsZnak a03b766e33 Update PageSettingsConnection.qml 2024-01-30 20:27:01 +02:00
KsZnak 7df2655ba0 Update PageServiceTorWebsiteSettings.qml 2024-01-30 20:22:26 +02:00
KsZnak 5c2ca9803d Update PageProtocolOpenVpnSettings.qml 2024-01-30 20:08:14 +02:00
KsZnak 4e6af947fa Update containers_defs.cpp 2024-01-30 20:05:18 +02:00
pokamest bc9d5c8fd6 Merge branch 'dev' into feature/api-awg 2024-01-30 10:15:13 +00:00
pokamest f412ac6260 Merge pull request #537 from amnezia-vpn/ios-log-3
Disable ioslogger (due memory leak)
2024-01-29 11:37:17 -08:00
pokamest 26d8dfbb7f Merge branch 'dev' into feature/api-awg 2024-01-29 19:35:35 +00:00
pokamest b45517bafd Merge pull request #444 from amnezia-vpn/feature/killswitch
Kill Switch for desktop client
2024-01-29 11:23:23 -08:00
vladimir.kuznetsov 3edb6755b4 fixed filling in the wg private key 2024-01-30 00:47:22 +07:00
pokamest bda64fa391 Merge pull request #536 from amnezia-vpn/bugfix/revoke-openvpn-client
fixed cache clearing when deleting admin configure
2024-01-29 07:31:09 -08:00
vladimir.kuznetsov e7040f7cc8 specified error codes 2024-01-29 21:33:35 +07:00
vladimir.kuznetsov 3240aa3cb3 Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into HEAD 2024-01-28 21:24:23 +07:00
Mykola Baibuz e0891e1a15 Change license text 2024-01-28 05:39:12 -05:00
vladimir.kuznetsov f7df621c56 fixed cache clearing when deleting admin configure
- added permissions for the crl.pem file
2024-01-27 18:09:14 +03:00
Mykola Baibuz 30af81fe0a Move linuxfirewall header to "headers" part 2024-01-27 07:59:36 -05:00
Mykola Baibuz 427b43c99b Add code license 2024-01-27 07:50:50 -05:00
KsZnak ed08ac6b46 Update containers_defs.cpp 2024-01-27 00:58:40 +02:00
KsZnak 1a2c1fa1b5 Update PageSetupWizardCredentials.qml 2024-01-27 00:18:17 +02:00
Igor Sorokin 709fbac231 Disable ioslogger (due memory leak) 2024-01-25 19:07:22 +03:00
Mykola Baibuz 5c9d45a8a8 Use MacOS logic for LinuxFirewall 2024-01-24 17:20:50 -05:00
Mykola Baibuz 874de74ac8 Add exclusion for VPN Server host (MacOS/OpenVPN) 2024-01-22 20:32:40 +02:00
vladimir.kuznetsov f7b9d2bae7 added support for awg configs for api 2024-01-22 16:51:11 +03:00
pokamest 3702d69b9d Merge branch 'dev' into feature/killswitch 2024-01-19 14:58:28 +00:00
pokamest 65a04799ef Merge branch 'dev' into feature/killswitch 2024-01-11 13:45:56 +00:00
vladimir.kuznetsov 11641c5e22 added error code output 2023-12-24 21:11:47 +07:00
Mykola Baibuz a8f5e95fb1 MacOS OpenVPN/OpenVPN over Cloak killswitch 2023-12-23 16:04:17 +02:00
Mykola Baibuz a4f3d08c02 Fix PF config path 2023-12-23 14:21:13 +02:00
Mykola Baibuz 3d2174d84e MacOS WG/AWG killswitch 2023-12-23 12:51:55 +02:00
Mykola Baibuz 1a17f2956a Fix build action 2023-12-16 10:05:54 -05:00
Mykola Baibuz d94e27bfa9 Linux killswitch 2023-12-16 09:19:04 -05:00
Mykola Baibuz c3fdd977b1 Windows OpenVPN/OpenVPN+Cloak killswitch feature 2023-11-29 22:50:36 +02:00
61 changed files with 2025 additions and 589 deletions
+2 -2
View File
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN) set(PROJECT AmneziaVPN)
project(${PROJECT} VERSION 4.2.1.1 project(${PROJECT} VERSION 4.3.0.0
DESCRIPTION "AmneziaVPN" DESCRIPTION "AmneziaVPN"
HOMEPAGE_URL "https://amnezia.org/" HOMEPAGE_URL "https://amnezia.org/"
) )
@@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}") set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 43) set(APP_ANDROID_VERSION_CODE 44)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux") set(MZ_PLATFORM_NAME "linux")
@@ -128,7 +128,8 @@ open class ProtocolConfig protected constructor(
} }
private fun processExcludedRoutes() { private fun processExcludedRoutes() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU && excludedRoutes.isNotEmpty()) {
// todo: rewrite, taking into account the current routes
// for older versions of Android, build a list of subnets without excluded routes // for older versions of Android, build a list of subnets without excluded routes
// and add them to routes // and add them to routes
val ipRangeSet = IpRangeSet() val ipRangeSet = IpRangeSet()
@@ -31,12 +31,12 @@ public:
QString processConfigWithLocalSettings(QString config); QString processConfigWithLocalSettings(QString config);
QString processConfigWithExportSettings(QString config); QString processConfigWithExportSettings(QString config);
static ConnectionData genClientKeys();
private: private:
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container, ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
ConnectionData genClientKeys();
bool m_isAwg; bool m_isAwg;
QString m_serverConfigPath; QString m_serverConfigPath;
QString m_serverPublicKeyPath; QString m_serverPublicKeyPath;
+7 -7
View File
@@ -98,11 +98,11 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its " QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its "
"own security protocol with SSL/TLS for key exchange.") }, "own security protocol with SSL/TLS for key exchange.") },
{ DockerContainer::ShadowSocks, { DockerContainer::ShadowSocks,
QObject::tr("ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is " QObject::tr("ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but it "
"recognised by analysis systems in some highly censored regions.") }, "may be recognized by analysis systems in some highly censored regions.") },
{ DockerContainer::Cloak, { DockerContainer::Cloak,
QObject::tr("OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against " QObject::tr("OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against "
"active-probbing detection. Ideal for bypassing blocking in regions with the highest levels " "active-probing detection. Ideal for bypassing blocking in regions with the highest levels "
"of censorship.") }, "of censorship.") },
{ DockerContainer::WireGuard, { DockerContainer::WireGuard,
QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power " QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power "
@@ -119,7 +119,7 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
{ DockerContainer::Dns, { DockerContainer::Dns,
QObject::tr("Replace the current DNS server with your own. This will increase your privacy level.") }, QObject::tr("Replace the current DNS server with your own. This will increase your privacy level.") },
{ DockerContainer::Sftp, { DockerContainer::Sftp,
QObject::tr("Creates a file vault on your server to securely store and transfer files.") } }; QObject::tr("Create a file vault on your server to securely store and transfer files.") } };
} }
QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions() QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
@@ -153,8 +153,8 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
"* Works over TCP network protocol.") }, "* Works over TCP network protocol.") },
{ DockerContainer::Cloak, { DockerContainer::Cloak,
QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for " QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for "
"blocking protection.\n\n" "protecting against blocking.\n\n"
"OpenVPN provides a secure VPN connection by encrypting all Internet traffic between the client " "OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client "
"and the server.\n\n" "and the server.\n\n"
"Cloak protects OpenVPN from detection and blocking. \n\n" "Cloak protects OpenVPN from detection and blocking. \n\n"
"Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, " "Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, "
@@ -172,7 +172,7 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
"* Works over TCP network protocol, 443 port.\n") }, "* Works over TCP network protocol, 443 port.\n") },
{ DockerContainer::WireGuard, { DockerContainer::WireGuard,
QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n" QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n"
"Provides stable VPN connection, high performance on all devices. Uses hard-coded encryption " "WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption "
"settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n" "settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n"
"WireGuard is very susceptible to blocking due to its distinct packet signatures. " "WireGuard is very susceptible to blocking due to its distinct packet signatures. "
"Unlike some other VPN protocols that employ obfuscation techniques, " "Unlike some other VPN protocols that employ obfuscation techniques, "
@@ -225,6 +225,24 @@ ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credential
return ErrorCode::NoError; return ErrorCode::NoError;
} }
ErrorCode ServerController::rebootServer(const ServerCredentials &credentials)
{
QString script = QString("sudo reboot");
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data;
return ErrorCode::NoError;
};
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
return runScript(credentials, script, cbReadStdOut, cbReadStdErr);
}
ErrorCode ServerController::removeAllContainers(const ServerCredentials &credentials) ErrorCode ServerController::removeAllContainers(const ServerCredentials &credentials)
{ {
return runScript(credentials, amnezia::scriptData(SharedScriptType::remove_all_containers)); return runScript(credentials, amnezia::scriptData(SharedScriptType::remove_all_containers));
@@ -22,6 +22,7 @@ public:
typedef QList<QPair<QString, QString>> Vars; typedef QList<QPair<QString, QString>> Vars;
ErrorCode rebootServer(const ServerCredentials &credentials);
ErrorCode removeAllContainers(const ServerCredentials &credentials); ErrorCode removeAllContainers(const ServerCredentials &credentials);
ErrorCode removeContainer(const ServerCredentials &credentials, DockerContainer container); ErrorCode removeContainer(const ServerCredentials &credentials, DockerContainer container);
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config, ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config,
+74 -59
View File
@@ -4,77 +4,92 @@
#include <QMetaEnum> #include <QMetaEnum>
#include <QObject> #include <QObject>
namespace amnezia { namespace amnezia
constexpr const qint16 qrMagicCode = 1984;
struct ServerCredentials
{ {
QString hostName;
QString userName;
QString secretData;
int port = 22;
bool isValid() const { return !hostName.isEmpty() && !userName.isEmpty() && !secretData.isEmpty() && port > 0; } constexpr const qint16 qrMagicCode = 1984;
};
enum ErrorCode struct ServerCredentials
{ {
// General error codes QString hostName;
NoError = 0, QString userName;
UnknownError, QString secretData;
InternalError, int port = 22;
NotImplementedError,
// Server errors bool isValid() const
ServerCheckFailed, {
ServerPortAlreadyAllocatedError, return !hostName.isEmpty() && !userName.isEmpty() && !secretData.isEmpty() && port > 0;
ServerContainerMissingError, }
ServerDockerFailedError, };
ServerCancelInstallation,
ServerUserNotInSudo,
ServerPacketManagerError,
// Ssh connection errors enum ErrorCode {
SshRequestDeniedError, SshInterruptedError, SshInternalError, // General error codes
SshPrivateKeyError, SshPrivateKeyFormatError, SshTimeoutError, NoError = 0,
UnknownError = 100,
InternalError = 101,
NotImplementedError = 102,
// Ssh sftp errors // Server errors
SshSftpEofError, SshSftpNoSuchFileError, SshSftpPermissionDeniedError, ServerCheckFailed = 200,
SshSftpFailureError, SshSftpBadMessageError, SshSftpNoConnectionError, ServerPortAlreadyAllocatedError = 201,
SshSftpConnectionLostError, SshSftpOpUnsupportedError, SshSftpInvalidHandleError, ServerContainerMissingError = 202,
SshSftpNoSuchPathError, SshSftpFileAlreadyExistsError, SshSftpWriteProtectError, ServerDockerFailedError = 203,
SshSftpNoMediaError, ServerCancelInstallation = 204,
ServerUserNotInSudo = 205,
ServerPacketManagerError = 206,
// Local errors // Ssh connection errors
OpenVpnConfigMissing, SshRequestDeniedError = 300,
OpenVpnManagementServerError, SshInterruptedError = 301,
ConfigMissing, SshInternalError = 302,
SshPrivateKeyError = 303,
SshPrivateKeyFormatError = 304,
SshTimeoutError = 305,
// Distro errors // Ssh sftp errors
OpenVpnExecutableMissing, SshSftpEofError = 400,
ShadowSocksExecutableMissing, SshSftpNoSuchFileError = 401,
CloakExecutableMissing, SshSftpPermissionDeniedError = 402,
AmneziaServiceConnectionFailed, SshSftpFailureError = 403,
ExecutableMissing, SshSftpBadMessageError = 404,
SshSftpNoConnectionError = 405,
SshSftpConnectionLostError = 406,
SshSftpOpUnsupportedError = 407,
SshSftpInvalidHandleError = 408,
SshSftpNoSuchPathError = 409,
SshSftpFileAlreadyExistsError = 410,
SshSftpWriteProtectError = 411,
SshSftpNoMediaError = 412,
// VPN errors // Local errors
OpenVpnAdaptersInUseError, OpenVpnConfigMissing = 500,
OpenVpnUnknownError, OpenVpnManagementServerError = 501,
OpenVpnTapAdapterError, ConfigMissing = 502,
AddressPoolError,
// 3rd party utils errors // Distro errors
OpenSslFailed, OpenVpnExecutableMissing = 600,
ShadowSocksExecutableCrashed, ShadowSocksExecutableMissing = 601,
CloakExecutableCrashed, CloakExecutableMissing = 602,
AmneziaServiceConnectionFailed = 603,
ExecutableMissing = 604,
// import and install errors // VPN errors
ImportInvalidConfigError, OpenVpnAdaptersInUseError = 700,
OpenVpnUnknownError = 701,
OpenVpnTapAdapterError = 702,
AddressPoolError = 703,
// Android errors // 3rd party utils errors
AndroidError OpenSslFailed = 800,
}; ShadowSocksExecutableCrashed = 801,
CloakExecutableCrashed = 802,
// import and install errors
ImportInvalidConfigError = 900,
// Android errors
AndroidError = 1000
};
} // namespace amnezia } // namespace amnezia
+46 -42
View File
@@ -2,70 +2,74 @@
using namespace amnezia; using namespace amnezia;
QString errorString(ErrorCode code){ QString errorString(ErrorCode code) {
QString errorMessage;
switch (code) { switch (code) {
// General error codes // General error codes
case(NoError): return QObject::tr("No error"); case(NoError): errorMessage = QObject::tr("No error"); break;
case(UnknownError): return QObject::tr("Unknown Error"); case(UnknownError): errorMessage = QObject::tr("Unknown Error"); break;
case(NotImplementedError): return QObject::tr("Function not implemented"); case(NotImplementedError): errorMessage = QObject::tr("Function not implemented"); break;
// Server errors // Server errors
case(ServerCheckFailed): return QObject::tr("Server check failed"); case(ServerCheckFailed): errorMessage = QObject::tr("Server check failed"); break;
case(ServerPortAlreadyAllocatedError): return QObject::tr("Server port already used. Check for another software"); case(ServerPortAlreadyAllocatedError): errorMessage = QObject::tr("Server port already used. Check for another software"); break;
case(ServerContainerMissingError): return QObject::tr("Server error: Docker container missing"); case(ServerContainerMissingError): errorMessage = QObject::tr("Server error: Docker container missing"); break;
case(ServerDockerFailedError): return QObject::tr("Server error: Docker failed"); case(ServerDockerFailedError): errorMessage = QObject::tr("Server error: Docker failed"); break;
case(ServerCancelInstallation): return QObject::tr("Installation canceled by user"); case(ServerCancelInstallation): errorMessage = QObject::tr("Installation canceled by user"); break;
case(ServerUserNotInSudo): return QObject::tr("The user does not have permission to use sudo"); case(ServerUserNotInSudo): errorMessage = QObject::tr("The user does not have permission to use sudo"); break;
// Libssh errors // Libssh errors
case(SshRequestDeniedError): return QObject::tr("Ssh request was denied"); case(SshRequestDeniedError): errorMessage = QObject::tr("Ssh request was denied"); break;
case(SshInterruptedError): return QObject::tr("Ssh request was interrupted"); case(SshInterruptedError): errorMessage = QObject::tr("Ssh request was interrupted"); break;
case(SshInternalError): return QObject::tr("Ssh internal error"); case(SshInternalError): errorMessage = QObject::tr("Ssh internal error"); break;
case(SshPrivateKeyError): return QObject::tr("Invalid private key or invalid passphrase entered"); case(SshPrivateKeyError): errorMessage = QObject::tr("Invalid private key or invalid passphrase entered"); break;
case(SshPrivateKeyFormatError): return QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types"); case(SshPrivateKeyFormatError): errorMessage = QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types"); break;
case(SshTimeoutError): return QObject::tr("Timeout connecting to server"); case(SshTimeoutError): errorMessage = QObject::tr("Timeout connecting to server"); break;
// Libssh sftp errors // Libssh sftp errors
case(SshSftpEofError): return QObject::tr("Sftp error: End-of-file encountered"); case(SshSftpEofError): errorMessage = QObject::tr("Sftp error: End-of-file encountered"); break;
case(SshSftpNoSuchFileError): return QObject::tr("Sftp error: File does not exist"); case(SshSftpNoSuchFileError): errorMessage = QObject::tr("Sftp error: File does not exist"); break;
case(SshSftpPermissionDeniedError): return QObject::tr("Sftp error: Permission denied"); case(SshSftpPermissionDeniedError): errorMessage = QObject::tr("Sftp error: Permission denied"); break;
case(SshSftpFailureError): return QObject::tr("Sftp error: Generic failure"); case(SshSftpFailureError): errorMessage = QObject::tr("Sftp error: Generic failure"); break;
case(SshSftpBadMessageError): return QObject::tr("Sftp error: Garbage received from server"); case(SshSftpBadMessageError): errorMessage = QObject::tr("Sftp error: Garbage received from server"); break;
case(SshSftpNoConnectionError): return QObject::tr("Sftp error: No connection has been set up"); case(SshSftpNoConnectionError): errorMessage = QObject::tr("Sftp error: No connection has been set up"); break;
case(SshSftpConnectionLostError): return QObject::tr("Sftp error: There was a connection, but we lost it"); case(SshSftpConnectionLostError): errorMessage = QObject::tr("Sftp error: There was a connection, but we lost it"); break;
case(SshSftpOpUnsupportedError): return QObject::tr("Sftp error: Operation not supported by libssh yet"); case(SshSftpOpUnsupportedError): errorMessage = QObject::tr("Sftp error: Operation not supported by libssh yet"); break;
case(SshSftpInvalidHandleError): return QObject::tr("Sftp error: Invalid file handle"); case(SshSftpInvalidHandleError): errorMessage = QObject::tr("Sftp error: Invalid file handle"); break;
case(SshSftpNoSuchPathError): return QObject::tr("Sftp error: No such file or directory path exists"); case(SshSftpNoSuchPathError): errorMessage = QObject::tr("Sftp error: No such file or directory path exists"); break;
case(SshSftpFileAlreadyExistsError): return QObject::tr("Sftp error: An attempt to create an already existing file or directory has been made"); case(SshSftpFileAlreadyExistsError): errorMessage = QObject::tr("Sftp error: An attempt to create an already existing file or directory has been made"); break;
case(SshSftpWriteProtectError): return QObject::tr("Sftp error: Write-protected filesystem"); case(SshSftpWriteProtectError): errorMessage = QObject::tr("Sftp error: Write-protected filesystem"); break;
case(SshSftpNoMediaError): return QObject::tr("Sftp error: No media was in remote drive"); case(SshSftpNoMediaError): errorMessage = QObject::tr("Sftp error: No media was in remote drive"); break;
// Local errors // Local errors
case (OpenVpnConfigMissing): return QObject::tr("OpenVPN config missing"); case (OpenVpnConfigMissing): errorMessage = QObject::tr("OpenVPN config missing"); break;
case (OpenVpnManagementServerError): return QObject::tr("OpenVPN management server error"); case (OpenVpnManagementServerError): errorMessage = QObject::tr("OpenVPN management server error"); break;
// Distro errors // Distro errors
case (OpenVpnExecutableMissing): return QObject::tr("OpenVPN executable missing"); case (OpenVpnExecutableMissing): errorMessage = QObject::tr("OpenVPN executable missing"); break;
case (ShadowSocksExecutableMissing): return QObject::tr("ShadowSocks (ss-local) executable missing"); case (ShadowSocksExecutableMissing): errorMessage = QObject::tr("ShadowSocks (ss-local) executable missing"); break;
case (CloakExecutableMissing): return QObject::tr("Cloak (ck-client) executable missing"); case (CloakExecutableMissing): errorMessage = QObject::tr("Cloak (ck-client) executable missing"); break;
case (AmneziaServiceConnectionFailed): return QObject::tr("Amnezia helper service error"); case (AmneziaServiceConnectionFailed): errorMessage = QObject::tr("Amnezia helper service error"); break;
case (OpenSslFailed): return QObject::tr("OpenSSL failed"); case (OpenSslFailed): errorMessage = QObject::tr("OpenSSL failed"); break;
// VPN errors // VPN errors
case (OpenVpnAdaptersInUseError): return QObject::tr("Can't connect: another VPN connection is active"); case (OpenVpnAdaptersInUseError): errorMessage = QObject::tr("Can't connect: another VPN connection is active"); break;
case (OpenVpnTapAdapterError): return QObject::tr("Can't setup OpenVPN TAP network adapter"); case (OpenVpnTapAdapterError): errorMessage = QObject::tr("Can't setup OpenVPN TAP network adapter"); break;
case (AddressPoolError): return QObject::tr("VPN pool error: no available addresses"); case (AddressPoolError): errorMessage = QObject::tr("VPN pool error: no available addresses"); break;
case (ImportInvalidConfigError): return QObject::tr("The config does not contain any containers and credentials for connecting to the server"); case (ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break;
// Android errors // Android errors
case (AndroidError): return QObject::tr("VPN connection error"); case (AndroidError): errorMessage = QObject::tr("VPN connection error"); break;
case(InternalError): case(InternalError):
default: default:
return QObject::tr("Internal error"); errorMessage = QObject::tr("Internal error"); break;
} }
return QObject::tr("ErrorCode: %1. ").arg(code) + errorMessage;
} }
QDebug operator<<(QDebug debug, const ErrorCode &e) QDebug operator<<(QDebug debug, const ErrorCode &e)
+2
View File
@@ -33,6 +33,7 @@ QString amnezia::scriptName(SharedScriptType type)
case SharedScriptType::check_connection: return QLatin1String("check_connection.sh"); case SharedScriptType::check_connection: return QLatin1String("check_connection.sh");
case SharedScriptType::check_server_is_busy: return QLatin1String("check_server_is_busy.sh"); case SharedScriptType::check_server_is_busy: return QLatin1String("check_server_is_busy.sh");
case SharedScriptType::check_user_in_sudo: return QLatin1String("check_user_in_sudo.sh"); case SharedScriptType::check_user_in_sudo: return QLatin1String("check_user_in_sudo.sh");
default: return QString();
} }
} }
@@ -46,6 +47,7 @@ QString amnezia::scriptName(ProtocolScriptType type)
case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn"); case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn");
case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf"); case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf");
case ProtocolScriptType::awg_template: return QLatin1String("template.conf"); case ProtocolScriptType::awg_template: return QLatin1String("template.conf");
default: return QString();
} }
} }
+24 -24
View File
@@ -17,41 +17,41 @@ public class Logger {
deinit {} deinit {}
func log(message: String) { func log(message: String) {
let suiteName = "group.org.amnezia.AmneziaVPN" // let suiteName = "group.org.amnezia.AmneziaVPN"
let logKey = "logMessages" // let logKey = "logMessages"
let sharedDefaults = UserDefaults(suiteName: suiteName) // let sharedDefaults = UserDefaults(suiteName: suiteName)
var logs = sharedDefaults?.array(forKey: logKey) as? [String] ?? [] // var logs = sharedDefaults?.array(forKey: logKey) as? [String] ?? []
logs.append(message) // logs.append(message)
sharedDefaults?.set(logs, forKey: logKey) // sharedDefaults?.set(logs, forKey: logKey)
} }
func writeLog(to targetFile: String) -> Bool { private func writeLog(to targetFile: String) -> Bool {
return true; return true;
} }
static func configureGlobal(tagged tag: String, withFilePath filePath: String?) { static func configureGlobal(tagged tag: String, withFilePath filePath: String?) {
if Logger.global != nil { // if Logger.global != nil {
return // return
} // }
//
Logger.global = Logger(tagged: tag) // Logger.global = Logger(tagged: tag)
//
var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown version" // var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown version"
//
if let appBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String { // if let appBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String {
appVersion += " (\(appBuild))" // appVersion += " (\(appBuild))"
} // }
//
Logger.global?.log(message: "App version: \(appVersion)") // Logger.global?.log(message: "App version: \(appVersion)")
} }
} }
func wg_log(_ type: OSLogType, staticMessage msg: StaticString) { func wg_log(_ type: OSLogType, staticMessage msg: StaticString) {
os_log(msg, log: OSLog.default, type: type) // os_log(msg, log: OSLog.default, type: type)
Logger.global?.log(message: "\(msg)") // Logger.global?.log(message: "\(msg)")
} }
func wg_log(_ type: OSLogType, message msg: String) { func wg_log(_ type: OSLogType, message msg: String) {
os_log("%{AMNEZIA}s", log: OSLog.default, type: type, msg) // os_log("%{AMNEZIA}s", log: OSLog.default, type: type, msg)
Logger.global?.log(message: msg) // Logger.global?.log(message: msg)
} }
@@ -0,0 +1,518 @@
// Copyright (c) 2023 Private Internet Access, Inc.
//
// This file is part of the Private Internet Access Desktop Client.
//
// The Private Internet Access Desktop Client is free software: you can
// redistribute it and/or modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// The Private Internet Access Desktop Client is distributed in the hope that
// it will be useful, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with the Private Internet Access Desktop Client. If not, see
// <https://www.gnu.org/licenses/>.
// Copyright (c) 2024 AmneziaVPN
// This file has been modified for AmneziaVPN
//
// This file is based on the work of the Private Internet Access Desktop Client.
// The original code of the Private Internet Access Desktop Client is copyrighted (c) 2023 Private Internet Access, Inc. and licensed under GPL3.
//
// The modified version of this file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this file. If not, see <https://www.gnu.org/licenses/>.
#include "linuxfirewall.h"
#include "logger.h"
#include <QProcess>
#define BRAND_CODE "amn"
namespace {
Logger logger("LinuxFirewall");
} // namespace
namespace
{
const QString kAnchorName{BRAND_CODE "vpn"};
const QString kPacketTag{"0x3211"};
const QString kCGroupId{"0x567"};
const QString enabledKeyTemplate = "enabled:%1:%2";
const QString disabledKeyTemplate = "disabled:%1:%2";
const QString kVpnGroupName = BRAND_CODE "vpn";
QHash<QString, LinuxFirewall::FilterCallbackFunc> anchorCallbacks;
}
QString LinuxFirewall::kRtableName = QStringLiteral("%1rt").arg(kAnchorName);
QString LinuxFirewall::kOutputChain = QStringLiteral("OUTPUT");
QString LinuxFirewall::kPostRoutingChain = QStringLiteral("POSTROUTING");
QString LinuxFirewall::kPreRoutingChain = QStringLiteral("PREROUTING");
QString LinuxFirewall::kRootChain = QStringLiteral("%1.anchors").arg(kAnchorName);
QString LinuxFirewall::kFilterTable = QStringLiteral("filter");
QString LinuxFirewall::kNatTable = QStringLiteral("nat");
QString LinuxFirewall::kRawTable = QStringLiteral("raw");
QString LinuxFirewall::kMangleTable = QStringLiteral("mangle");
static QString getCommand(LinuxFirewall::IPVersion ip)
{
return ip == LinuxFirewall::IPv6 ? QStringLiteral("ip6tables") : QStringLiteral("iptables");
}
int LinuxFirewall::createChain(LinuxFirewall::IPVersion ip, const QString& chain, const QString& tableName)
{
if (ip == Both)
{
int result4 = createChain(IPv4, chain, tableName);
int result6 = createChain(IPv6, chain, tableName);
return result4 ? result4 : result6;
}
const QString cmd = getCommand(ip);
return execute(QStringLiteral("%1 -N %2 -t %3 || %1 -F %2 -t %3").arg(cmd, chain, tableName));
}
int LinuxFirewall::deleteChain(LinuxFirewall::IPVersion ip, const QString& chain, const QString& tableName)
{
if (ip == Both)
{
int result4 = deleteChain(IPv4, chain, tableName);
int result6 = deleteChain(IPv6, chain, tableName);
return result4 ? result4 : result6;
}
const QString cmd = getCommand(ip);
return execute(QStringLiteral("if %1 -L %2 -n -t %3 > /dev/null 2> /dev/null ; then %1 -F %2 -t %3 && %1 -X %2 -t %3; fi").arg(cmd, chain, tableName));
}
int LinuxFirewall::linkChain(LinuxFirewall::IPVersion ip, const QString& chain, const QString& parent, bool mustBeFirst, const QString& tableName)
{
if (ip == Both)
{
int result4 = linkChain(IPv4, chain, parent, mustBeFirst, tableName);
int result6 = linkChain(IPv6, chain, parent, mustBeFirst, tableName);
return result4 ? result4 : result6;
}
const QString cmd = getCommand(ip);
if (mustBeFirst)
{
// This monster shell script does the following:
// 1. Check if a rule with the appropriate target exists at the top of the parent chain
// 2. If not, insert a jump rule at the top of the parent chain
// 3. Look for and delete a single rule with the designated target at an index > 1
// (we can't safely delete all rules at once since rule numbers change)
// TODO: occasionally this script results in warnings in logs "Bad rule (does a matching rule exist in the chain?)" - this happens when
// the e.g OUTPUT chain is empty but this script attempts to delete things from it anyway. It doesn't cause any problems, but we should still fix at some point..
return execute(QStringLiteral("if ! %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) == 1 && $2 == \"%3\" { found=1 } END { if(found==1) { exit 0 } else { exit 1 } }' ; then %1 -I %2 -j %3 -t %4 && %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) > 1 && $2 == \"%3\" { print $1; exit }' | xargs %1 -t %4 -D %2 ; fi").arg(cmd, parent, chain, tableName));
}
else
return execute(QStringLiteral("if ! %1 -C %2 -j %3 -t %4 2> /dev/null ; then %1 -A %2 -j %3 -t %4; fi").arg(cmd, parent, chain, tableName));
}
int LinuxFirewall::unlinkChain(LinuxFirewall::IPVersion ip, const QString& chain, const QString& parent, const QString& tableName)
{
if (ip == Both)
{
int result4 = unlinkChain(IPv4, chain, parent, tableName);
int result6 = unlinkChain(IPv6, chain, parent, tableName);
return result4 ? result4 : result6;
}
const QString cmd = getCommand(ip);
return execute(QStringLiteral("if %1 -C %2 -j %3 -t %4 2> /dev/null ; then %1 -D %2 -j %3 -t %4; fi").arg(cmd, parent, chain, tableName));
}
void LinuxFirewall::ensureRootAnchorPriority(LinuxFirewall::IPVersion ip)
{
linkChain(ip, kRootChain, kOutputChain, true);
}
void LinuxFirewall::installAnchor(LinuxFirewall::IPVersion ip, const QString& anchor, const QStringList& rules, const QString& tableName,
const FilterCallbackFunc& enableFunc, const FilterCallbackFunc& disableFunc)
{
if (ip == Both)
{
installAnchor(IPv4, anchor, rules, tableName, enableFunc, disableFunc);
installAnchor(IPv6, anchor, rules, tableName, enableFunc, disableFunc);
return;
}
const QString cmd = getCommand(ip);
const QString anchorChain = QStringLiteral("%1.a.%2").arg(kAnchorName, anchor);
const QString actualChain = QStringLiteral("%1.%2").arg(kAnchorName, anchor);
// Start by defining a placeholder chain, which stays locked into place
// in the root chain without being removed or recreated, ensuring the
// intended precedence order.
createChain(ip, anchorChain, tableName);
linkChain(ip, anchorChain, kRootChain, false, tableName);
if(enableFunc)
{
const QString key = enabledKeyTemplate.arg(tableName, anchor);
if(!anchorCallbacks.contains(key)) anchorCallbacks[key] = enableFunc;
}
if(disableFunc)
{
const QString key = disabledKeyTemplate.arg(tableName, anchor);
if(!anchorCallbacks.contains(key)) anchorCallbacks[key] = disableFunc;
}
// Create the actual rule chain, which we'll insert or remove from the
// placeholder anchor when needed.
createChain(ip, actualChain, tableName);
for (const QString& rule : rules)
execute(QStringLiteral("%1 -A %2 %3 -t %4").arg(cmd, actualChain, rule, tableName));
}
void LinuxFirewall::uninstallAnchor(LinuxFirewall::IPVersion ip, const QString& anchor, const QString& tableName)
{
if (ip == Both)
{
uninstallAnchor(IPv4, anchor, tableName);
uninstallAnchor(IPv6, anchor, tableName);
return;
}
const QString cmd = getCommand(ip);
const QString anchorChain = QStringLiteral("%1.a.%2").arg(kAnchorName, anchor);
const QString actualChain = QStringLiteral("%1.%2").arg(kAnchorName, anchor);
unlinkChain(ip, anchorChain, kRootChain, tableName);
deleteChain(ip, anchorChain, tableName);
deleteChain(ip, actualChain, tableName);
}
QStringList LinuxFirewall::getDNSRules(const QStringList& servers)
{
QStringList result;
for (const QString& server : servers)
{
result << QStringLiteral("-o amn0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o amn0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
}
return result;
}
QStringList LinuxFirewall::getAllowRule(const QStringList& servers)
{
QStringList result;
for (const QString& server : servers)
{
result << QStringLiteral("-d %1 -j ACCEPT").arg(server);
}
return result;
}
QStringList LinuxFirewall::getBlockRule(const QStringList& servers)
{
QStringList result;
for (const QString& server : servers)
{
result << QStringLiteral("-d %1 -j REJECT").arg(server);
}
return result;
}
void LinuxFirewall::install()
{
// Clean up any existing rules if they exist.
uninstall();
// Create a root filter chain to hold all our other anchors in order.
createChain(Both, kRootChain, kFilterTable);
// Create a root raw chain
createChain(Both, kRootChain, kRawTable);
// Create a root NAT chain
createChain(Both, kRootChain, kNatTable);
// Create a root Mangle chain
createChain(Both, kRootChain, kMangleTable);
// Install our filter rulesets in each corresponding anchor chain.
installAnchor(Both, QStringLiteral("000.allowLoopback"), {
QStringLiteral("-o lo+ -j ACCEPT"),
});
installAnchor(IPv4, QStringLiteral("320.allowDNS"), {});
installAnchor(Both, QStringLiteral("310.blockDNS"), {
QStringLiteral("-p udp --dport 53 -j REJECT"),
QStringLiteral("-p tcp --dport 53 -j REJECT"),
});
installAnchor(IPv4, QStringLiteral("300.allowLAN"), {
QStringLiteral("-d 10.0.0.0/8 -j ACCEPT"),
QStringLiteral("-d 169.254.0.0/16 -j ACCEPT"),
QStringLiteral("-d 172.16.0.0/12 -j ACCEPT"),
QStringLiteral("-d 192.168.0.0/16 -j ACCEPT"),
QStringLiteral("-d 224.0.0.0/4 -j ACCEPT"),
QStringLiteral("-d 255.255.255.255/32 -j ACCEPT"),
});
installAnchor(IPv6, QStringLiteral("300.allowLAN"), {
QStringLiteral("-d fc00::/7 -j ACCEPT"),
QStringLiteral("-d fe80::/10 -j ACCEPT"),
QStringLiteral("-d ff00::/8 -j ACCEPT"),
});
installAnchor(IPv4, QStringLiteral("290.allowDHCP"), {
QStringLiteral("-p udp -d 255.255.255.255 --sport 68 --dport 67 -j ACCEPT"),
});
installAnchor(IPv6, QStringLiteral("290.allowDHCP"), {
QStringLiteral("-p udp -d ff00::/8 --sport 546 --dport 547 -j ACCEPT"),
});
installAnchor(IPv6, QStringLiteral("250.blockIPv6"), {
QStringLiteral("! -o lo+ -j REJECT"),
});
installAnchor(Both, QStringLiteral("200.allowVPN"), {
QStringLiteral("-o amn0+ -j ACCEPT"),
QStringLiteral("-o tun0+ -j ACCEPT"),
});
installAnchor(IPv4, QStringLiteral("120.blockNets"), {});
installAnchor(IPv4, QStringLiteral("110.allowNets"), {});
installAnchor(Both, QStringLiteral("100.blockAll"), {
QStringLiteral("-j REJECT"),
});
// NAT rules
installAnchor(Both, QStringLiteral("100.transIp"), {
// Only need the original interface, not the IP.
// The interface should remain much more stable/unchangeable than the IP
// (IP can change when changing networks, but interface only changes if adding/removing NICs)
// this is just a stub rule - the real rule is set at run-time
// and updates dynamically (via replaceAnchor) when our interface changes
// it'll take this form: "-o <interface name> -j MASQUERADE"
QStringLiteral("-j MASQUERADE")
}, kNatTable);
// Mangle rules
installAnchor(Both, QStringLiteral("100.tagPkts"), {
QStringLiteral("-m cgroup --cgroup %1 -j MARK --set-mark %2").arg(kCGroupId, kPacketTag)
}, kMangleTable, setupTrafficSplitting, teardownTrafficSplitting);
// A rule to mitigate CVE-2019-14899 - drop packets addressed to the local
// VPN IP but that are not actually received on the VPN interface.
// See here: https://seclists.org/oss-sec/2019/q4/122
installAnchor(Both, QStringLiteral("100.vpnTunOnly"), {
// To be replaced at runtime
QStringLiteral("-j ACCEPT")
}, kRawTable);
// Insert our fitler root chain at the top of the OUTPUT chain.
linkChain(Both, kRootChain, kOutputChain, true, kFilterTable);
// Insert our NAT root chain at the top of the POSTROUTING chain.
linkChain(Both, kRootChain, kPostRoutingChain, true, kNatTable);
// Insert our Mangle root chain at the top of the OUTPUT chain.
linkChain(Both, kRootChain, kOutputChain, true, kMangleTable);
// Insert our Raw root chain at the top of the PREROUTING chain.
linkChain(Both, kRootChain, kPreRoutingChain, true, kRawTable);
setupTrafficSplitting();
}
void LinuxFirewall::uninstall()
{
// Filter chain
unlinkChain(Both, kRootChain, kOutputChain, kFilterTable);
deleteChain(Both, kRootChain, kFilterTable);
// Raw chain
unlinkChain(Both, kRootChain, kPreRoutingChain, kRawTable);
deleteChain(Both, kRootChain, kRawTable);
// NAT chain
unlinkChain(Both, kRootChain, kPostRoutingChain, kNatTable);
deleteChain(Both, kRootChain, kNatTable);
// Mangle chain
unlinkChain(Both, kRootChain, kOutputChain, kMangleTable);
deleteChain(Both, kRootChain, kMangleTable);
// Remove filter anchors
uninstallAnchor(Both, QStringLiteral("000.allowLoopback"));
uninstallAnchor(Both, QStringLiteral("400.allowPIA"));
uninstallAnchor(IPv4, QStringLiteral("320.allowDNS"));
uninstallAnchor(Both, QStringLiteral("310.blockDNS"));
uninstallAnchor(Both, QStringLiteral("300.allowLAN"));
uninstallAnchor(Both, QStringLiteral("290.allowDHCP"));
uninstallAnchor(IPv6, QStringLiteral("250.blockIPv6"));
uninstallAnchor(Both, QStringLiteral("200.allowVPN"));
uninstallAnchor(IPv4, QStringLiteral("120.blockNets"));
uninstallAnchor(IPv4, QStringLiteral("110.allowNets"));
uninstallAnchor(Both, QStringLiteral("100.blockAll"));
// Remove Nat anchors
uninstallAnchor(Both, QStringLiteral("100.transIp"), kNatTable);
// Remove Mangle anchors
uninstallAnchor(Both, QStringLiteral("100.tagPkts"), kMangleTable);
// Remove Raw anchors
uninstallAnchor(Both, QStringLiteral("100.vpnTunOnly"), kRawTable);
teardownTrafficSplitting();
logger.debug() << "LinuxFirewall::uninstall() complete";
}
bool LinuxFirewall::isInstalled()
{
return execute(QStringLiteral("iptables -C %1 -j %2 2> /dev/null").arg(kOutputChain, kRootChain)) == 0;
}
void LinuxFirewall::enableAnchor(LinuxFirewall::IPVersion ip, const QString &anchor, const QString& tableName)
{
if (ip == Both)
{
enableAnchor(IPv4, anchor, tableName);
enableAnchor(IPv6, anchor, tableName);
return;
}
const QString cmd = getCommand(ip);
const QString ipStr = ip == IPv6 ? QStringLiteral("(IPv6)") : QStringLiteral("(IPv4)");
execute(QStringLiteral("if %1 -C %5.a.%2 -j %5.%2 -t %4 2> /dev/null ; then echo '%2%3: ON' ; else echo '%2%3: OFF -> ON' ; %1 -A %5.a.%2 -j %5.%2 -t %4; fi").arg(cmd, anchor, ipStr, tableName, kAnchorName));
}
void LinuxFirewall::replaceAnchor(LinuxFirewall::IPVersion ip, const QString &anchor, const QString &newRule, const QString& tableName)
{
if (ip == Both)
{
replaceAnchor(IPv4, anchor, newRule, tableName);
replaceAnchor(IPv6, anchor, newRule, tableName);
return;
}
const QString cmd = getCommand(ip);
const QString ipStr = ip == IPv6 ? QStringLiteral("(IPv6)") : QStringLiteral("(IPv4)");
execute(QStringLiteral("%1 -R %7.%2 1 %3 -t %4 ; echo 'Replaced rule %7.%2 %5 with %6'").arg(cmd, anchor, newRule, tableName, ipStr, newRule, kAnchorName));
}
void LinuxFirewall::disableAnchor(LinuxFirewall::IPVersion ip, const QString &anchor, const QString& tableName)
{
if (ip == Both)
{
disableAnchor(IPv4, anchor, tableName);
disableAnchor(IPv6, anchor, tableName);
return;
}
const QString cmd = getCommand(ip);
const QString ipStr = ip == IPv6 ? QStringLiteral("(IPv6)") : QStringLiteral("(IPv4)");
execute(QStringLiteral("if ! %1 -C %5.a.%2 -j %5.%2 -t %4 2> /dev/null ; then echo '%2%3: OFF' ; else echo '%2%3: ON -> OFF' ; %1 -F %5.a.%2 -t %4; fi").arg(cmd, anchor, ipStr, tableName, kAnchorName));
}
bool LinuxFirewall::isAnchorEnabled(LinuxFirewall::IPVersion ip, const QString &anchor, const QString& tableName)
{
const QString cmd = getCommand(ip);
return execute(QStringLiteral("%1 -C %4.a.%2 -j %4.%2 -t %3 2> /dev/null").arg(cmd, anchor, tableName, kAnchorName)) == 0;
}
void LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPVersion ip, const QString &anchor, bool enabled, const QString &tableName)
{
if (enabled)
{
enableAnchor(ip, anchor, tableName);
const QString key = enabledKeyTemplate.arg(tableName, anchor);
if(anchorCallbacks.contains(key)) anchorCallbacks[key]();
}
else
{
disableAnchor(ip, anchor, tableName);
const QString key = disabledKeyTemplate.arg(tableName, anchor);
if(anchorCallbacks.contains(key)) anchorCallbacks[key]();
}
}
void LinuxFirewall::updateDNSServers(const QStringList& servers)
{
static QStringList existingServers {};
existingServers = servers;
execute(QStringLiteral("iptables -F %1.320.allowDNS").arg(kAnchorName));
for (const QString& rule : getDNSRules(servers))
execute(QStringLiteral("iptables -A %1.320.allowDNS %2").arg(kAnchorName, rule));
}
void LinuxFirewall::updateAllowNets(const QStringList& servers)
{
static QStringList existingServers {};
existingServers = servers;
execute(QStringLiteral("iptables -F %1.110.allowNets").arg(kAnchorName));
for (const QString& rule : getAllowRule(servers))
execute(QStringLiteral("iptables -A %1.110.allowNets %2").arg(kAnchorName, rule));
}
void LinuxFirewall::updateBlockNets(const QStringList& servers)
{
static QStringList existingServers {};
existingServers = servers;
execute(QStringLiteral("iptables -F %1.120.blockNets").arg(kAnchorName));
for (const QString& rule : getBlockRule(servers))
execute(QStringLiteral("iptables -A %1.120.blockNets %2").arg(kAnchorName, rule));
}
int waitForExitCode(QProcess& process)
{
if (!process.waitForFinished() || process.error() == QProcess::FailedToStart)
return -2;
else if (process.exitStatus() != QProcess::NormalExit)
return -1;
else
return process.exitCode();
}
int LinuxFirewall::execute(const QString &command, bool ignoreErrors)
{
QProcess p;
p.start(QStringLiteral("/bin/bash"), {QStringLiteral("-c"), command}, QProcess::ReadOnly);
p.closeWriteChannel();
int exitCode = waitForExitCode(p);
auto out = p.readAllStandardOutput().trimmed();
auto err = p.readAllStandardError().trimmed();
if ((exitCode != 0 || !err.isEmpty()) && !ignoreErrors)
logger.warning() << "(" << exitCode << ") $ " << command;
else if (false)
logger.debug() << "(" << exitCode << ") $ " << command;
if (!out.isEmpty())
logger.info() << out;
if (!err.isEmpty())
logger.warning() << err;
return exitCode;
}
void LinuxFirewall::setupTrafficSplitting()
{
auto cGroupDir = "/sys/fs/cgroup/net_cls/" BRAND_CODE "vpnexclusions/";
logger.info() << "Should be setting up cgroup in" << cGroupDir << "for traffic splitting";
execute(QStringLiteral("if [ ! -d %1 ] ; then mkdir %1 ; sleep 0.1 ; echo %2 > %1/net_cls.classid ; fi").arg(cGroupDir).arg(kCGroupId));
// Set a rule with priority 100 (lower priority than local but higher than main/default, 0 is highest priority)
execute(QStringLiteral("if ! ip rule list | grep -q %1 ; then ip rule add from all fwmark %1 lookup %2 pri 100 ; fi").arg(kPacketTag, kRtableName));
}
void LinuxFirewall::teardownTrafficSplitting()
{
logger.info() << "Tearing down cgroup and routing rules";
execute(QStringLiteral("if ip rule list | grep -q %1; then ip rule del from all fwmark %1 lookup %2 2> /dev/null ; fi").arg(kPacketTag, kRtableName));
execute(QStringLiteral("ip route flush table %1").arg(kRtableName));
execute(QStringLiteral("ip route flush cache"));
}
@@ -0,0 +1,107 @@
// Copyright (c) 2023 Private Internet Access, Inc.
//
// This file is part of the Private Internet Access Desktop Client.
//
// The Private Internet Access Desktop Client is free software: you can
// redistribute it and/or modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// The Private Internet Access Desktop Client is distributed in the hope that
// it will be useful, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with the Private Internet Access Desktop Client. If not, see
// <https://www.gnu.org/licenses/>.
// Copyright (c) 2024 AmneziaVPN
// This file has been modified for AmneziaVPN
//
// This file is based on the work of the Private Internet Access Desktop Client.
// The original code of the Private Internet Access Desktop Client is copyrighted (c) 2023 Private Internet Access, Inc. and licensed under GPL3.
//
// The modified version of this file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this file. If not, see <https://www.gnu.org/licenses/>.
#ifndef LINUXFIREWALL_H
#define LINUXFIREWALL_H
#include <QString>
#include <QStringList>
// Descriptor for a set of firewall rules to be appled.
//
struct FirewallParams
{
QStringList dnsServers;
QVector<QString> excludeApps; // Apps to exclude if VPN exemptions are enabled
QStringList allowAddrs;
QStringList blockAddrs;
// The follow flags indicate which general rulesets are needed. Note that
// this is after some sanity filtering, i.e. an allow rule may be listed
// as not needed if there were no block rules preceding it. The rulesets
// should be thought of as in last-match order.
bool blockAll; // Block all traffic by default
bool allowVPN; // Exempt traffic through VPN tunnel
bool allowDHCP; // Exempt DHCP traffic
bool blockIPv6; // Block all IPv6 traffic
bool allowLAN; // Exempt LAN traffic, including IPv6 LAN traffic
bool blockDNS; // Block all DNS traffic except specified DNS servers
bool allowPIA; // Exempt PIA executables
bool allowLoopback; // Exempt loopback traffic
bool allowHnsd; // Exempt Handshake DNS traffic
bool allowVpnExemptions; // Exempt specified traffic from the tunnel (route it over the physical uplink instead)
bool allowNets;
bool blockNets;
};
class LinuxFirewall
{
public:
enum IPVersion { IPv4, IPv6, Both };
// Table names
static QString kFilterTable, kNatTable, kMangleTable, kRtableName, kRawTable;
public:
using FilterCallbackFunc = std::function<void()>;
private:
static int createChain(IPVersion ip, const QString& chain, const QString& tableName = kFilterTable);
static int deleteChain(IPVersion ip, const QString& chain, const QString& tableName = kFilterTable);
static int linkChain(IPVersion ip, const QString& chain, const QString& parent, bool mustBeFirst = false, const QString& tableName = kFilterTable);
static int unlinkChain(IPVersion ip, const QString& chain, const QString& parent, const QString& tableName = kFilterTable);
static void installAnchor(IPVersion ip, const QString& anchor, const QStringList& rules, const QString& tableName = kFilterTable, const FilterCallbackFunc& enableFunc = {}, const FilterCallbackFunc& disableFunc = {});
static void uninstallAnchor(IPVersion ip, const QString& anchor, const QString& tableName = kFilterTable);
static QStringList getDNSRules(const QStringList& servers);
static QStringList getAllowRule(const QStringList& servers);
static QStringList getBlockRule(const QStringList& servers);
static void setupTrafficSplitting();
static void teardownTrafficSplitting();
static int execute(const QString& command, bool ignoreErrors = false);
private:
// Chain names
static QString kOutputChain, kRootChain, kPostRoutingChain, kPreRoutingChain;
public:
static void install();
static void uninstall();
static bool isInstalled();
static void ensureRootAnchorPriority(IPVersion ip = Both);
static void enableAnchor(IPVersion ip, const QString& anchor, const QString& tableName = kFilterTable);
static void disableAnchor(IPVersion ip, const QString& anchor, const QString& tableName = kFilterTable);
static bool isAnchorEnabled(IPVersion ip, const QString& anchor, const QString& tableName = kFilterTable);
static void setAnchorEnabled(IPVersion ip, const QString& anchor, bool enabled, const QString& tableName = kFilterTable);
static void replaceAnchor(LinuxFirewall::IPVersion ip, const QString &anchor, const QString &newRule, const QString& tableName);
static void updateDNSServers(const QStringList& servers);
static void updateAllowNets(const QStringList& servers);
static void updateBlockNets(const QStringList& servers);
};
#endif // LINUXFIREWALL_H
@@ -11,7 +11,9 @@
#include <QFile> #include <QFile>
#include <QLocalSocket> #include <QLocalSocket>
#include <QTimer> #include <QTimer>
#include <QThread>
#include "linuxfirewall.h"
#include "leakdetector.h" #include "leakdetector.h"
#include "logger.h" #include "logger.h"
@@ -116,7 +118,27 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
int err = uapiErrno(uapiCommand(message)); int err = uapiErrno(uapiCommand(message));
if (err != 0) { if (err != 0) {
logger.error() << "Interface configuration failed:" << strerror(err); logger.error() << "Interface configuration failed:" << strerror(err);
} else {
FirewallParams params { };
params.dnsServers.append(config.m_dnsServer);
if (config.m_allowedIPAddressRanges.at(0).toString() == "0.0.0.0/0"){
params.blockAll = true;
if (config.m_excludedAddresses.size()) {
params.allowNets = true;
foreach (auto net, config.m_excludedAddresses) {
params.allowAddrs.append(net.toUtf8());
}
}
} else {
params.blockNets = true;
foreach (auto net, config.m_allowedIPAddressRanges) {
params.blockAddrs.append(net.toString());
}
}
applyFirewallRules(params);
} }
return (err == 0); return (err == 0);
} }
@@ -140,6 +162,9 @@ bool WireguardUtilsLinux::deleteInterface() {
// Garbage collect. // Garbage collect.
QDir wgRuntimeDir(WG_RUNTIME_DIR); QDir wgRuntimeDir(WG_RUNTIME_DIR);
QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name")); QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name"));
// double-check + ensure our firewall is installed and enabled
LinuxFirewall::uninstall();
return true; return true;
} }
@@ -252,6 +277,31 @@ QList<WireguardUtils::PeerStatus> WireguardUtilsLinux::getPeerStatus() {
return peerList; return peerList;
} }
void WireguardUtilsLinux::applyFirewallRules(FirewallParams& params)
{
// double-check + ensure our firewall is installed and enabled
if (!LinuxFirewall::isInstalled()) LinuxFirewall::install();
// Note: rule precedence is handled inside IpTablesFirewall
LinuxFirewall::ensureRootAnchorPriority();
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), params.blockAll);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), params.allowNets);
LinuxFirewall::updateAllowNets(params.allowAddrs);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("120.blockNets"), params.blockNets);
LinuxFirewall::updateBlockNets(params.blockAddrs);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("200.allowVPN"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), true);
LinuxFirewall::updateDNSServers(params.dnsServers);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true);
}
bool WireguardUtilsLinux::updateRoutePrefix(const IPAddress& prefix) { bool WireguardUtilsLinux::updateRoutePrefix(const IPAddress& prefix) {
if (!m_rtmonitor) { if (!m_rtmonitor) {
return false; return false;
@@ -8,8 +8,11 @@
#include <QObject> #include <QObject>
#include <QProcess> #include <QProcess>
#include "daemon/wireguardutils.h" #include "daemon/wireguardutils.h"
#include "linuxroutemonitor.h" #include "linuxroutemonitor.h"
#include "linuxfirewall.h"
class WireguardUtilsLinux final : public WireguardUtils { class WireguardUtilsLinux final : public WireguardUtils {
Q_OBJECT Q_OBJECT
@@ -34,7 +37,7 @@ public:
bool addExclusionRoute(const IPAddress& prefix) override; bool addExclusionRoute(const IPAddress& prefix) override;
bool deleteExclusionRoute(const IPAddress& prefix) override; bool deleteExclusionRoute(const IPAddress& prefix) override;
void applyFirewallRules(FirewallParams& params);
signals: signals:
void backendFailure(); void backendFailure();
@@ -0,0 +1,199 @@
// Copyright (c) 2023 Private Internet Access, Inc.
//
// This file is part of the Private Internet Access Desktop Client.
//
// The Private Internet Access Desktop Client is free software: you can
// redistribute it and/or modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// The Private Internet Access Desktop Client is distributed in the hope that
// it will be useful, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with the Private Internet Access Desktop Client. If not, see
// <https://www.gnu.org/licenses/>.
// Copyright (c) 2024 AmneziaVPN
// This file has been modified for AmneziaVPN
//
// This file is based on the work of the Private Internet Access Desktop Client.
// The original code of the Private Internet Access Desktop Client is copyrighted (c) 2023 Private Internet Access, Inc. and licensed under GPL3.
//
// The modified version of this file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this file. If not, see <https://www.gnu.org/licenses/>.
#include "macosfirewall.h"
#include "logger.h"
#include <QProcess>
#include <QCoreApplication>
#define BRAND_IDENTIFIER "amn"
namespace {
Logger logger("MacOSFirewall");
} // namespace
#include "macosfirewall.h"
#define ResourceDir qApp->applicationDirPath() + "/pf"
#define DaemonDataDir qApp->applicationDirPath() + "/pf"
#include <QProcess>
static QString kRootAnchor = QStringLiteral(BRAND_IDENTIFIER);
static QByteArray kPfWarning = "pfctl: Use of -f option, could result in flushing of rules\npresent in the main ruleset added by the system at startup.\nSee /etc/pf.conf for further details.\n";
int waitForExitCode(QProcess& process)
{
if (!process.waitForFinished() || process.error() == QProcess::FailedToStart)
return -2;
else if (process.exitStatus() != QProcess::NormalExit)
return -1;
else
return process.exitCode();
}
int MacOSFirewall::execute(const QString& command, bool ignoreErrors)
{
QProcess p;
p.start(QStringLiteral("/bin/bash"), { QStringLiteral("-c"), command }, QProcess::ReadOnly);
p.closeWriteChannel();
int exitCode = waitForExitCode(p);
auto out = p.readAllStandardOutput().trimmed();
auto err = p.readAllStandardError().replace(kPfWarning, "").trimmed();
if ((exitCode != 0 || !err.isEmpty()) && !ignoreErrors)
logger.info() << "(" << exitCode << ") $ " << command;
else if (false)
logger.info() << "(" << exitCode << ") $ " << command;
if (!out.isEmpty()) logger.info() << out;
if (!err.isEmpty()) logger.info() << err;
return exitCode;
}
void MacOSFirewall::installRootAnchors()
{
logger.info() << "Installing PF root anchors";
// Append our NAT anchors by reading back and re-applying NAT rules only
auto insertNatAnchors = QStringLiteral(
"( "
R"(pfctl -sn | grep -v '%1/*'; )" // Translation rules (includes both nat and rdr, despite the modifier being 'nat')
R"(echo 'nat-anchor "%2/*"'; )" // PIA's translation anchors
R"(echo 'rdr-anchor "%3/*"'; )"
R"(echo 'load anchor "%4" from "%5/%6.conf"'; )" // Load the PIA anchors from file
") | pfctl -N -f -").arg(kRootAnchor, kRootAnchor, kRootAnchor, kRootAnchor, ResourceDir, kRootAnchor);
execute(insertNatAnchors);
// Append our filter anchor by reading back and re-applying filter rules
// only. pfctl -sr also includes scrub rules, but these will be ignored
// due to -R.
auto insertFilterAnchor = QStringLiteral(
"( "
R"(pfctl -sr | grep -v '%1/*'; )" // Filter rules (everything from pfctl -sr except 'scrub')
R"(echo 'anchor "%2/*"'; )" // PIA's filter anchors
R"(echo 'load anchor "%3" from "%4/%5.conf"'; )" // Load the PIA anchors from file
" ) | pfctl -R -f -").arg(kRootAnchor, kRootAnchor, kRootAnchor, ResourceDir, kRootAnchor);
execute(insertFilterAnchor);
}
void MacOSFirewall::install()
{
// remove hard-coded (legacy) pia anchor from /etc/pf.conf if it exists
execute(QStringLiteral("if grep -Fq '%1' /etc/pf.conf ; then echo \"`cat /etc/pf.conf | grep -vF '%1'`\" > /etc/pf.conf ; fi").arg(kRootAnchor));
// Clean up any existing rules if they exist.
uninstall();
timespec waitTime{0, 10'000'000};
::nanosleep(&waitTime, nullptr);
logger.info() << "Installing PF root anchor";
installRootAnchors();
execute(QStringLiteral("pfctl -E 2>&1 | grep -F 'Token : ' | cut -c9- > '%1/pf.token'").arg(DaemonDataDir));
}
void MacOSFirewall::uninstall()
{
logger.info() << "Uninstalling PF root anchor";
execute(QStringLiteral("pfctl -q -a '%1' -F all").arg(kRootAnchor));
execute(QStringLiteral("test -f '%1/pf.token' && pfctl -X `cat '%1/pf.token'` && rm '%1/pf.token'").arg(DaemonDataDir));
execute(QStringLiteral("test -f /etc/pf.conf && pfctl -F all -f /etc/pf.conf"));
}
bool MacOSFirewall::isInstalled()
{
return isPFEnabled() && isRootAnchorLoaded();
}
bool MacOSFirewall::isPFEnabled()
{
return 0 == execute(QStringLiteral("test -s '%1/pf.token' && pfctl -s References | grep -qFf '%1/pf.token'").arg(DaemonDataDir), true);
}
void MacOSFirewall::ensureRootAnchorPriority()
{
// We check whether our anchor appears last in the ruleset. If it does not, then remove it and re-add it last (this happens atomically).
// Appearing last ensures priority.
execute(QStringLiteral("if ! pfctl -sr | tail -1 | grep -qF '%1'; then echo -e \"$(pfctl -sr | grep -vF '%1')\\n\"'anchor \"%1\"' | pfctl -f - ; fi").arg(kRootAnchor));
}
bool MacOSFirewall::isRootAnchorLoaded()
{
// Our Root anchor is loaded if:
// 1. It is is included among the top-level anchors
// 2. It is not empty (i.e it contains sub-anchors)
return 0 == execute(QStringLiteral("pfctl -sr | grep -q '%1' && pfctl -q -a '%1' -s rules 2> /dev/null | grep -q .").arg(kRootAnchor), true);
}
void MacOSFirewall::enableAnchor(const QString& anchor)
{
execute(QStringLiteral("if pfctl -q -a '%1/%2' -s rules 2> /dev/null | grep -q . ; then echo '%2: ON' ; else echo '%2: OFF -> ON' ; pfctl -q -a '%1/%2' -F all -f '%3/%1.%2.conf' ; fi").arg(kRootAnchor, anchor, ResourceDir));
}
void MacOSFirewall::disableAnchor(const QString& anchor)
{
execute(QStringLiteral("if ! pfctl -q -a '%1/%2' -s rules 2> /dev/null | grep -q . ; then echo '%2: OFF' ; else echo '%2: ON -> OFF' ; pfctl -q -a '%1/%2' -F all ; fi").arg(kRootAnchor, anchor));
}
bool MacOSFirewall::isAnchorEnabled(const QString& anchor)
{
return 0 == execute(QStringLiteral("pfctl -q -a '%1/%2' -s rules 2> /dev/null | grep -q .").arg(kRootAnchor, anchor), true);
}
void MacOSFirewall::setAnchorEnabled(const QString& anchor, bool enabled)
{
if (enabled)
enableAnchor(anchor);
else
disableAnchor(anchor);
}
void MacOSFirewall::setAnchorTable(const QString& anchor, bool enabled, const QString& table, const QStringList& items)
{
if (enabled)
execute(QStringLiteral("pfctl -q -a '%1/%2' -t '%3' -T replace %4").arg(kRootAnchor, anchor, table, items.join(' ')));
else
execute(QStringLiteral("pfctl -q -a '%1/%2' -t '%3' -T kill").arg(kRootAnchor, anchor, table), true);
}
void MacOSFirewall::setAnchorWithRules(const QString& anchor, bool enabled, const QStringList &ruleList)
{
if (!enabled)
return (void)execute(QStringLiteral("pfctl -q -a '%1/%2' -F rules").arg(kRootAnchor, anchor), true);
else
return (void)execute(QStringLiteral("echo -e \"%1\" | pfctl -q -a '%2/%3' -f -").arg(ruleList.join('\n'), kRootAnchor, anchor), true);
}
@@ -0,0 +1,90 @@
// Copyright (c) 2023 Private Internet Access, Inc.
//
// This file is part of the Private Internet Access Desktop Client.
//
// The Private Internet Access Desktop Client is free software: you can
// redistribute it and/or modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// The Private Internet Access Desktop Client is distributed in the hope that
// it will be useful, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with the Private Internet Access Desktop Client. If not, see
// <https://www.gnu.org/licenses/>.
// Copyright (c) 2024 AmneziaVPN
// This file has been modified for AmneziaVPN
//
// This file is based on the work of the Private Internet Access Desktop Client.
// The original code of the Private Internet Access Desktop Client is copyrighted (c) 2023 Private Internet Access, Inc. and licensed under GPL3.
//
// The modified version of this file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this file. If not, see <https://www.gnu.org/licenses/>.
#ifndef MACOSFIREWALL_H
#define MACOSFIREWALL_H
#include <QString>
#include <QStringList>
// Descriptor for a set of firewall rules to be appled.
//
struct FirewallParams
{
QStringList dnsServers;
QVector<QString> excludeApps; // Apps to exclude if VPN exemptions are enabled
QStringList allowAddrs;
QStringList blockAddrs;
// The follow flags indicate which general rulesets are needed. Note that
// this is after some sanity filtering, i.e. an allow rule may be listed
// as not needed if there were no block rules preceding it. The rulesets
// should be thought of as in last-match order.
bool blockAll; // Block all traffic by default
bool blockNets;
bool allowNets;
bool allowVPN; // Exempt traffic through VPN tunnel
bool allowDHCP; // Exempt DHCP traffic
bool blockIPv6; // Block all IPv6 traffic
bool allowLAN; // Exempt LAN traffic, including IPv6 LAN traffic
bool blockDNS; // Block all DNS traffic except specified DNS servers
bool allowPIA; // Exempt PIA executables
bool allowLoopback; // Exempt loopback traffic
bool allowHnsd; // Exempt Handshake DNS traffic
bool allowVpnExemptions; // Exempt specified traffic from the tunnel (route it over the physical uplink instead)
};
class MacOSFirewall
{
private:
static int execute(const QString &command, bool ignoreErrors = false);
static bool isPFEnabled();
static bool isRootAnchorLoaded();
public:
static void install();
static void uninstall();
static bool isInstalled();
static void enableAnchor(const QString &anchor);
static void disableAnchor(const QString &anchor);
static bool isAnchorEnabled(const QString &anchor);
static void setAnchorEnabled(const QString &anchor, bool enable);
static void setAnchorTable(const QString &anchor, bool enabled, const QString &table, const QStringList &items);
static void setAnchorWithRules(const QString &anchor, bool enabled, const QStringList &rules);
static void ensureRootAnchorPriority();
static void installRootAnchors();
};
#endif // MACOSFIREWALL_H
@@ -114,9 +114,30 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
} }
int err = uapiErrno(uapiCommand(message)); int err = uapiErrno(uapiCommand(message));
if (err != 0) { if (err != 0) {
logger.error() << "Interface configuration failed:" << strerror(err); logger.error() << "Interface configuration failed:" << strerror(err);
} else {
FirewallParams params { };
params.dnsServers.append(config.m_dnsServer);
if (config.m_allowedIPAddressRanges.at(0).toString() == "0.0.0.0/0"){
params.blockAll = true;
if (config.m_excludedAddresses.size()) {
params.allowNets = true;
foreach (auto net, config.m_excludedAddresses) {
params.allowAddrs.append(net.toUtf8());
}
}
} else {
params.blockNets = true;
foreach (auto net, config.m_allowedIPAddressRanges) {
params.blockAddrs.append(net.toString());
}
}
applyFirewallRules(params);
} }
return (err == 0); return (err == 0);
} }
@@ -140,6 +161,10 @@ bool WireguardUtilsMacos::deleteInterface() {
// Garbage collect. // Garbage collect.
QDir wgRuntimeDir(WG_RUNTIME_DIR); QDir wgRuntimeDir(WG_RUNTIME_DIR);
QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name")); QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name"));
// double-check + ensure our firewall is installed and enabled
MacOSFirewall::uninstall();
return true; return true;
} }
@@ -302,6 +327,31 @@ bool WireguardUtilsMacos::addExclusionRoute(const IPAddress& prefix) {
return m_rtmonitor->addExclusionRoute(prefix); return m_rtmonitor->addExclusionRoute(prefix);
} }
void WireguardUtilsMacos::applyFirewallRules(FirewallParams& params)
{
// double-check + ensure our firewall is installed and enabled. This is necessary as
// other software may disable pfctl before re-enabling with their own rules (e.g other VPNs)
if (!MacOSFirewall::isInstalled()) MacOSFirewall::install();
MacOSFirewall::ensureRootAnchorPriority();
MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true);
MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), params.blockAll);
MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), params.allowNets);
MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), params.allowNets,
QStringLiteral("allownets"), params.allowAddrs);
MacOSFirewall::setAnchorEnabled(QStringLiteral("120.blockNets"), params.blockNets);
MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), params.blockNets,
QStringLiteral("blocknets"), params.blockAddrs);
MacOSFirewall::setAnchorEnabled(QStringLiteral("200.allowVPN"), true);
MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true);
MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), true);
MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), true);
MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), true);
MacOSFirewall::setAnchorTable(QStringLiteral("310.blockDNS"), true, QStringLiteral("dnsaddr"), params.dnsServers);
}
bool WireguardUtilsMacos::deleteExclusionRoute(const IPAddress& prefix) { bool WireguardUtilsMacos::deleteExclusionRoute(const IPAddress& prefix) {
if (!m_rtmonitor) { if (!m_rtmonitor) {
return false; return false;
@@ -10,6 +10,7 @@
#include "daemon/wireguardutils.h" #include "daemon/wireguardutils.h"
#include "macosroutemonitor.h" #include "macosroutemonitor.h"
#include "macosfirewall.h"
class WireguardUtilsMacos final : public WireguardUtils { class WireguardUtilsMacos final : public WireguardUtils {
Q_OBJECT Q_OBJECT
@@ -34,6 +35,7 @@ class WireguardUtilsMacos final : public WireguardUtils {
bool addExclusionRoute(const IPAddress& prefix) override; bool addExclusionRoute(const IPAddress& prefix) override;
bool deleteExclusionRoute(const IPAddress& prefix) override; bool deleteExclusionRoute(const IPAddress& prefix) override;
void applyFirewallRules(FirewallParams& params);
signals: signals:
void backendFailure(); void backendFailure();
@@ -66,7 +66,7 @@ ErrorCode OpenVpnOverCloakProtocol::start()
emit protocolError(amnezia::ErrorCode::CloakExecutableCrashed); emit protocolError(amnezia::ErrorCode::CloakExecutableCrashed);
stop(); stop();
} }
if (exitCode !=0 ){ if (exitCode !=0 ) {
emit protocolError(amnezia::ErrorCode::InternalError); emit protocolError(amnezia::ErrorCode::InternalError);
stop(); stop();
} }
+36 -10
View File
@@ -4,6 +4,7 @@
#include <QRandomGenerator> #include <QRandomGenerator>
#include <QTcpServer> #include <QTcpServer>
#include <QTcpSocket> #include <QTcpSocket>
#include <QNetworkInterface>
#include "logger.h" #include "logger.h"
#include "openvpnprotocol.h" #include "openvpnprotocol.h"
@@ -53,6 +54,11 @@ void OpenVpnProtocol::stop()
QThread::msleep(10); QThread::msleep(10);
m_managementServer.stop(); m_managementServer.stop();
} }
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
IpcClient::Interface()->disableKillSwitch();
#endif
setConnectionState(Vpn::ConnectionState::Disconnected); setConnectionState(Vpn::ConnectionState::Disconnected);
} }
@@ -85,13 +91,13 @@ void OpenVpnProtocol::killOpenVpnProcess()
void OpenVpnProtocol::readOpenVpnConfiguration(const QJsonObject &configuration) void OpenVpnProtocol::readOpenVpnConfiguration(const QJsonObject &configuration)
{ {
if (configuration.contains(ProtocolProps::key_proto_config_data(Proto::OpenVpn))) { if (configuration.contains(ProtocolProps::key_proto_config_data(Proto::OpenVpn))) {
m_configData = configuration;
QJsonObject jConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::OpenVpn)).toObject(); QJsonObject jConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::OpenVpn)).toObject();
m_configFile.open(); m_configFile.open();
m_configFile.write(jConfig.value(config_key::config).toString().toUtf8()); m_configFile.write(jConfig.value(config_key::config).toString().toUtf8());
m_configFile.close(); m_configFile.close();
m_configFileName = m_configFile.fileName(); m_configFileName = m_configFile.fileName();
qDebug().noquote() << QString("Set config data") << m_configFileName; qDebug().noquote() << QString("Set config data") << m_configFileName;
} }
} }
@@ -138,12 +144,18 @@ uint OpenVpnProtocol::selectMgmtPort()
void OpenVpnProtocol::updateRouteGateway(QString line) void OpenVpnProtocol::updateRouteGateway(QString line)
{ {
// TODO: fix for macos if (line.contains("net_route_v4_best_gw")) {
line = line.split("ROUTE_GATEWAY", Qt::SkipEmptyParts).at(1); QStringList params = line.split(" ");
if (!line.contains("/")) if (params.size() == 6) {
return; m_routeGateway = params.at(3);
m_routeGateway = line.split("/", Qt::SkipEmptyParts).first(); }
m_routeGateway.replace(" ", ""); } else {
line = line.split("ROUTE_GATEWAY", Qt::SkipEmptyParts).at(1);
if (!line.contains("/"))
return;
m_routeGateway = line.split("/", Qt::SkipEmptyParts).first();
m_routeGateway.replace(" ", "");
}
qDebug() << "Set VPN route gateway" << m_routeGateway; qDebug() << "Set VPN route gateway" << m_routeGateway;
} }
@@ -282,7 +294,7 @@ void OpenVpnProtocol::onReadyReadDataFromManagementServer()
} }
} }
if (line.contains("ROUTE_GATEWAY")) { if (line.contains("ROUTE_GATEWAY") || line.contains("net_route_v4_best_gw")) {
updateRouteGateway(line); updateRouteGateway(line);
} }
@@ -320,14 +332,28 @@ void OpenVpnProtocol::updateVpnGateway(const QString &line)
// line looks like // line looks like
// PUSH: Received control message: 'PUSH_REPLY,route 10.8.0.1,topology net30,ping 10,ping-restart // PUSH: Received control message: 'PUSH_REPLY,route 10.8.0.1,topology net30,ping 10,ping-restart
// 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-GCM' // 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-GCM'
QStringList params = line.split(","); QStringList params = line.split(",");
for (const QString &l : params) { for (const QString &l : params) {
if (l.contains("ifconfig")) { if (l.contains("ifconfig")) {
if (l.split(" ").size() == 3) { if (l.split(" ").size() == 3) {
m_vpnLocalAddress = l.split(" ").at(1); m_vpnLocalAddress = l.split(" ").at(1);
m_vpnGateway = l.split(" ").at(2); m_vpnGateway = l.split(" ").at(2);
#ifdef Q_OS_WIN
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
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
IpcClient::Interface()->enableKillSwitch(m_configData, 0);
#endif
qDebug() << QString("Set vpn local address %1, gw %2").arg(m_vpnLocalAddress).arg(vpnGateway()); qDebug() << QString("Set vpn local address %1, gw %2").arg(m_vpnLocalAddress).arg(vpnGateway());
} }
} }
+1
View File
@@ -44,6 +44,7 @@ private:
ManagementServer m_managementServer; ManagementServer m_managementServer;
QString m_configFileName; QString m_configFileName;
QJsonObject m_configData;
QTemporaryFile m_configFile; QTemporaryFile m_configFile;
uint selectMgmtPort(); uint selectMgmtPort();
-39
View File
@@ -13,9 +13,6 @@
WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *parent) WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *parent)
: VpnProtocol(configuration, parent) : VpnProtocol(configuration, parent)
{ {
m_configFile.setFileName(QDir::tempPath() + QDir::separator() + serviceName() + ".conf");
writeWireguardConfiguration(configuration);
m_impl.reset(new LocalSocketController()); m_impl.reset(new LocalSocketController());
connect(m_impl.get(), &ControllerImpl::connected, this, connect(m_impl.get(), &ControllerImpl::connected, this,
[this](const QString &pubkey, const QDateTime &connectionTimestamp) { [this](const QString &pubkey, const QDateTime &connectionTimestamp) {
@@ -50,45 +47,9 @@ ErrorCode WireguardProtocol::stopMzImpl()
return ErrorCode::NoError; return ErrorCode::NoError;
} }
void WireguardProtocol::writeWireguardConfiguration(const QJsonObject &configuration)
{
QJsonObject jConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::WireGuard)).toObject();
if (!m_configFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
qCritical() << "Failed to save wireguard config to" << m_configFile.fileName();
return;
}
m_configFile.write(jConfig.value(config_key::config).toString().toUtf8());
m_configFile.close();
m_configFileName = m_configFile.fileName();
m_isConfigLoaded = true;
qDebug().noquote() << QString("Set config data") << configPath();
qDebug().noquote() << QString("Set config data")
<< configuration.value(ProtocolProps::key_proto_config_data(Proto::WireGuard)).toString().toUtf8();
}
QString WireguardProtocol::configPath() const
{
return m_configFileName;
}
QString WireguardProtocol::serviceName() const
{
return "AmneziaVPN.WireGuard0";
}
ErrorCode WireguardProtocol::start() ErrorCode WireguardProtocol::start()
{ {
if (!m_isConfigLoaded) {
setLastError(ErrorCode::ConfigMissing);
return lastError();
}
return startMzImpl(); return startMzImpl();
} }
-9
View File
@@ -26,15 +26,6 @@ public:
ErrorCode stopMzImpl(); ErrorCode stopMzImpl();
private: private:
QString configPath() const;
void writeWireguardConfiguration(const QJsonObject &configuration);
QString serviceName() const;
private:
QString m_configFileName;
QFile m_configFile;
bool m_isConfigLoaded = false;
QScopedPointer<ControllerImpl> m_impl; QScopedPointer<ControllerImpl> m_impl;
}; };
+131 -91
View File
@@ -133,12 +133,12 @@
<context> <context>
<name>HomeContainersListView</name> <name>HomeContainersListView</name>
<message> <message>
<location filename="../ui/qml/Components/HomeContainersListView.qml" line="58"/> <location filename="../ui/qml/Components/HomeContainersListView.qml" line="76"/>
<source>Unable change protocol while there is an active connection</source> <source>Unable change protocol while there is an active connection</source>
<translation>امکان تغییر پروتکل در هنگام متصل بودن وجود ندارد</translation> <translation>امکان تغییر پروتکل در هنگام متصل بودن وجود ندارد</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/HomeContainersListView.qml" line="68"/> <location filename="../ui/qml/Components/HomeContainersListView.qml" line="85"/>
<source>The selected protocol is not supported on the current platform</source> <source>The selected protocol is not supported on the current platform</source>
<translation>پروتکل انتخاب شده بر روی این پلتفرم پشتیبانی نمیشود</translation> <translation>پروتکل انتخاب شده بر روی این پلتفرم پشتیبانی نمیشود</translation>
</message> </message>
@@ -150,7 +150,7 @@
<context> <context>
<name>ImportController</name> <name>ImportController</name>
<message> <message>
<location filename="../ui/controllers/importController.cpp" line="411"/> <location filename="../ui/controllers/importController.cpp" line="416"/>
<source>Scanned %1 of %2.</source> <source>Scanned %1 of %2.</source>
<translation>ارزیابی %1 از %2.</translation> <translation>ارزیابی %1 از %2.</translation>
</message> </message>
@@ -190,26 +190,31 @@ Already installed containers were found on the server. All installed containers
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="305"/> <location filename="../ui/controllers/installController.cpp" line="305"/>
<source>Server &apos;%1&apos; was rebooted</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/controllers/installController.cpp" line="314"/>
<source>Server &apos;%1&apos; was removed</source> <source>Server &apos;%1&apos; was removed</source>
<translation>سرور %1 حذف شد</translation> <translation>سرور %1 حذف شد</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="315"/> <location filename="../ui/controllers/installController.cpp" line="324"/>
<source>All containers from server &apos;%1&apos; have been removed</source> <source>All containers from server &apos;%1&apos; have been removed</source>
<translation>تمام کانتینترها از سرور %1 حذف شدند</translation> <translation>تمام کانتینترها از سرور %1 حذف شدند</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="332"/> <location filename="../ui/controllers/installController.cpp" line="341"/>
<source>%1 has been removed from the server &apos;%2&apos;</source> <source>%1 has been removed from the server &apos;%2&apos;</source>
<translation>%1 از سرور %2 حذف شد</translation> <translation>%1 از سرور %2 حذف شد</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="478"/> <location filename="../ui/controllers/installController.cpp" line="487"/>
<source>Please login as the user</source> <source>Please login as the user</source>
<translation>لطفا به عنوان کاربر وارد شوید</translation> <translation>لطفا به عنوان کاربر وارد شوید</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="506"/> <location filename="../ui/controllers/installController.cpp" line="515"/>
<source>Server added successfully</source> <source>Server added successfully</source>
<translation>سرور با موفقیت اضافه شد</translation> <translation>سرور با موفقیت اضافه شد</translation>
</message> </message>
@@ -277,17 +282,17 @@ Already installed containers were found on the server. All installed containers
<context> <context>
<name>PageHome</name> <name>PageHome</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="317"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="318"/>
<source>VPN protocol</source> <source>VPN protocol</source>
<translation>پروتکل ویپیان</translation> <translation>پروتکل ویپیان</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="361"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="362"/>
<source>Servers</source> <source>Servers</source>
<translation>سرورها</translation> <translation>سرورها</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="453"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="454"/>
<source>Unable change server while there is an active connection</source> <source>Unable change server while there is an active connection</source>
<translation>امکان تغییر سرور در هنگام متصل بودن وجود ندارد</translation> <translation>امکان تغییر سرور در هنگام متصل بودن وجود ندارد</translation>
</message> </message>
@@ -1220,57 +1225,62 @@ Already installed containers were found on the server. All installed containers
<context> <context>
<name>PageSettingsDns</name> <name>PageSettingsDns</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="45"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="35"/>
<source>Default server does not support custom dns</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="53"/>
<source>DNS servers</source> <source>DNS servers</source>
<translation>سرورهای DNS</translation> <translation>سرورهای DNS</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="50"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="58"/>
<source>If AmneziaDNS is not used or installed</source> <source>If AmneziaDNS is not used or installed</source>
<translation>اگر AmneziaDNS نصب نباشد یا استفاده نشود</translation> <translation>اگر AmneziaDNS نصب نباشد یا استفاده نشود</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="57"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="65"/>
<source>Primary DNS</source> <source>Primary DNS</source>
<translation>DNS اصلی</translation> <translation>DNS اصلی</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="69"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="77"/>
<source>Secondary DNS</source> <source>Secondary DNS</source>
<translation>DNS ثانویه</translation> <translation>DNS ثانویه</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="87"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="95"/>
<source>Restore default</source> <source>Restore default</source>
<translation>بازگشت به پیشفرض</translation> <translation>بازگشت به پیشفرض</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="90"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="98"/>
<source>Restore default DNS settings?</source> <source>Restore default DNS settings?</source>
<translation>بازگشت به تنظیمات پیشفرض DNS؟</translation> <translation>بازگشت به تنظیمات پیشفرض DNS؟</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="91"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="99"/>
<source>Continue</source> <source>Continue</source>
<translation>ادامه</translation> <translation>ادامه</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="92"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="100"/>
<source>Cancel</source> <source>Cancel</source>
<translation>کنسل</translation> <translation>کنسل</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="100"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="108"/>
<source>Settings have been reset</source> <source>Settings have been reset</source>
<translation>تنظیمات ریست شد</translation> <translation>تنظیمات ریست شد</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="112"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="120"/>
<source>Save</source> <source>Save</source>
<translation>ذخیره</translation> <translation>ذخیره</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="121"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="129"/>
<source>Settings saved</source> <source>Settings saved</source>
<translation>ذخیره تنظیمات</translation> <translation>ذخیره تنظیمات</translation>
</message> </message>
@@ -1288,52 +1298,52 @@ Already installed containers were found on the server. All installed containers
<translation>ذخیره گزارشات</translation> <translation>ذخیره گزارشات</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="86"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="87"/>
<source>Open folder with logs</source> <source>Open folder with logs</source>
<translation>باز کردن پوشه گزارشات</translation> <translation>باز کردن پوشه گزارشات</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="108"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="109"/>
<source>Save</source> <source>Save</source>
<translation>ذخیره</translation> <translation>ذخیره</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="109"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="110"/>
<source>Logs files (*.log)</source> <source>Logs files (*.log)</source>
<translation>Logs files (*.log)</translation> <translation>Logs files (*.log)</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="118"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="119"/>
<source>Logs file saved</source> <source>Logs file saved</source>
<translation>فایل گزارشات ذخیره شد</translation> <translation>فایل گزارشات ذخیره شد</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="127"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="128"/>
<source>Save logs to file</source> <source>Save logs to file</source>
<translation>ذخیره گزارشات در فایل</translation> <translation>ذخیره گزارشات در فایل</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="145"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="146"/>
<source>Clear logs?</source> <source>Clear logs?</source>
<translation>پاک کردن گزارشات؟</translation> <translation>پاک کردن گزارشات؟</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="146"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="147"/>
<source>Continue</source> <source>Continue</source>
<translation>ادامه</translation> <translation>ادامه</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="147"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="148"/>
<source>Cancel</source> <source>Cancel</source>
<translation>کنسل</translation> <translation>کنسل</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="154"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="155"/>
<source>Logs have been cleaned up</source> <source>Logs have been cleaned up</source>
<translation>گزارشات پاک شدند</translation> <translation>گزارشات پاک شدند</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="167"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="168"/>
<source>Clear logs</source> <source>Clear logs</source>
<translation>پاک کردن گزارشات</translation> <translation>پاک کردن گزارشات</translation>
</message> </message>
@@ -1346,17 +1356,17 @@ Already installed containers were found on the server. All installed containers
<translation>تمام کانتینرهای نصب شده به نرمافزار اضافه شدند</translation> <translation>تمام کانتینرهای نصب شده به نرمافزار اضافه شدند</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="87"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="91"/>
<source>Clear Amnezia cache</source> <source>Clear Amnezia cache</source>
<translation>پاک کردن حافظه داخلی Amnezia</translation> <translation>پاک کردن حافظه داخلی Amnezia</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="88"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="92"/>
<source>May be needed when changing other settings</source> <source>May be needed when changing other settings</source>
<translation>وقتی تنظیمات دیگر را تغییر دهید ممکن است نیاز باشد</translation> <translation>وقتی تنظیمات دیگر را تغییر دهید ممکن است نیاز باشد</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="91"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="95"/>
<source>Clear cached profiles?</source> <source>Clear cached profiles?</source>
<translation>پاک کردن پروفایل ذخیره شده؟</translation> <translation>پاک کردن پروفایل ذخیره شده؟</translation>
</message> </message>
@@ -1371,56 +1381,81 @@ Already installed containers were found on the server. All installed containers
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="93"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="97"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="140"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="145"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="171"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="177"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="208"/>
<source>Continue</source> <source>Continue</source>
<translation>ادامه</translation> <translation>ادامه</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="94"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="98"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="141"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="146"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="172"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="178"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="209"/>
<source>Cancel</source> <source>Cancel</source>
<translation>کنسل</translation> <translation>کنسل</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="117"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="121"/>
<source>Check the server for previously installed Amnezia services</source> <source>Check the server for previously installed Amnezia services</source>
<translation>چک کردن سرویسهای نصب شده Amnezia بر روی سرور</translation> <translation>چک کردن سرویسهای نصب شده Amnezia بر روی سرور</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="118"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="122"/>
<source>Add them to the application if they were not displayed</source> <source>Add them to the application if they were not displayed</source>
<translation>اضافه کردن آنها به نرمافزار اگر نمایش داده نشدهاند</translation> <translation>اضافه کردن آنها به نرمافزار اگر نمایش داده نشدهاند</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="134"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="139"/>
<source>Reboot server</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="143"/>
<source>Do you want to reboot the server?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="144"/>
<source>??????????????????????????????</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="175"/>
<source>Do you want to remove the server?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="206"/>
<source>Do you want to clear server from Amnezia software?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="171"/>
<source>Remove server from application</source> <source>Remove server from application</source>
<translation>حذف کردن سرور از نرمافزار</translation> <translation>حذف کردن سرور از نرمافزار</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="138"/>
<source>Remove server?</source> <source>Remove server?</source>
<translation>حذف سرور؟</translation> <translation type="vanished">حذف سرور؟</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="139"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="176"/>
<source>All installed AmneziaVPN services will still remain on the server.</source> <source>All installed AmneziaVPN services will still remain on the server.</source>
<translation>تمام سرویسهای نصبشده Amnezia همچنان بر روی سرور باقی خواهند ماند.</translation> <translation>تمام سرویسهای نصبشده Amnezia همچنان بر روی سرور باقی خواهند ماند.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="165"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="202"/>
<source>Clear server from Amnezia software</source> <source>Clear server from Amnezia software</source>
<translation>پاک کردن سرور از نرمافزار Amnezia</translation> <translation>پاک کردن سرور از نرمافزار Amnezia</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="169"/>
<source>Clear server from Amnezia software?</source> <source>Clear server from Amnezia software?</source>
<translation>سرور از نرمافزار Amnezia پاک شود؟</translation> <translation type="vanished">سرور از نرمافزار Amnezia پاک شود؟</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="170"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="207"/>
<source>All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.</source> <source>All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.</source>
<translation>تمام کانتینرها از سرور پاک شوند، به این معنی که تمام فایلهای پیکربندی، کلیدها و مجوزها حذف خواهند شد.</translation> <translation>تمام کانتینرها از سرور پاک شوند، به این معنی که تمام فایلهای پیکربندی، کلیدها و مجوزها حذف خواهند شد.</translation>
</message> </message>
@@ -1501,90 +1536,95 @@ Already installed containers were found on the server. All installed containers
<context> <context>
<name>PageSettingsSplitTunneling</name> <name>PageSettingsSplitTunneling</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="53"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="29"/>
<source>Default server does not support split tunneling function</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="59"/>
<source>Addresses from the list should be accessed via VPN</source> <source>Addresses from the list should be accessed via VPN</source>
<translation>دسترسی به آدرسهای لیست از طریق ویپیان</translation> <translation>دسترسی به آدرسهای لیست از طریق ویپیان</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="58"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="64"/>
<source>Addresses from the list should not be accessed via VPN</source> <source>Addresses from the list should not be accessed via VPN</source>
<translation>دسترسی به آدرسهای لیست بدون ویپیان</translation> <translation>دسترسی به آدرسهای لیست بدون ویپیان</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="90"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="96"/>
<source>Split tunneling</source> <source>Split tunneling</source>
<translation>جداسازی ترافیک</translation> <translation>جداسازی ترافیک</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="121"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="127"/>
<source>Mode</source> <source>Mode</source>
<translation>حالت</translation> <translation>حالت</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="199"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="205"/>
<source>Remove </source> <source>Remove </source>
<translation>حذف </translation> <translation>حذف </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="200"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="206"/>
<source>Continue</source> <source>Continue</source>
<translation>ادامه</translation> <translation>ادامه</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="201"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="207"/>
<source>Cancel</source> <source>Cancel</source>
<translation>کنسل</translation> <translation>کنسل</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="248"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="254"/>
<source>Site or IP</source> <source>Site or IP</source>
<translation>سایت یا آیپی</translation> <translation>سایت یا آیپی</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="292"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="298"/>
<source>Import/Export Sites</source> <source>Import/Export Sites</source>
<translation>بارگذاری / خروجیگرفتن از سایتها</translation> <translation>بارگذاری / خروجیگرفتن از سایتها</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="298"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="304"/>
<source>Import</source> <source>Import</source>
<translation>بارگذاری</translation> <translation>بارگذاری</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="310"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="316"/>
<source>Save site list</source> <source>Save site list</source>
<translation>ذخیره لیست سایتها</translation> <translation>ذخیره لیست سایتها</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="317"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="323"/>
<source>Save sites</source> <source>Save sites</source>
<translation>ذخیره سایتها</translation> <translation>ذخیره سایتها</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="318"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="324"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="385"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="391"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="400"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="406"/>
<source>Sites files (*.json)</source> <source>Sites files (*.json)</source>
<translation>Sites files (*.json)</translation> <translation>Sites files (*.json)</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="375"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="381"/>
<source>Import a list of sites</source> <source>Import a list of sites</source>
<translation>بارگذاری لیست سایتها</translation> <translation>بارگذاری لیست سایتها</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="381"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="387"/>
<source>Replace site list</source> <source>Replace site list</source>
<translation>جایگزین کردن لیست سایت</translation> <translation>جایگزین کردن لیست سایت</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="384"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="390"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="399"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="405"/>
<source>Open sites file</source> <source>Open sites file</source>
<translation>باز کردن فایل سایتها</translation> <translation>باز کردن فایل سایتها</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="396"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="402"/>
<source>Add imported sites to existing ones</source> <source>Add imported sites to existing ones</source>
<translation>اضافه کردن سایتهای بارگذاری شده به سایتهای موجود</translation> <translation>اضافه کردن سایتهای بارگذاری شده به سایتهای موجود</translation>
</message> </message>
@@ -1711,22 +1751,22 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation>سطح کنترل اینترنت در منطقه شما چگونه است؟</translation> <translation>سطح کنترل اینترنت در منطقه شما چگونه است؟</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="137"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="139"/>
<source>Set up a VPN yourself</source> <source>Set up a VPN yourself</source>
<translation>یک ویپیان برای خودتان بسازید</translation> <translation>یک ویپیان برای خودتان بسازید</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="138"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="140"/>
<source>I want to choose a VPN protocol</source> <source>I want to choose a VPN protocol</source>
<translation>میخواهم پروتکل ویپیان را انتخاب کنم</translation> <translation>میخواهم پروتکل ویپیان را انتخاب کنم</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="157"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="159"/>
<source>Continue</source> <source>Continue</source>
<translation>ادامه</translation> <translation>ادامه</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="197"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="199"/>
<source>Set up later</source> <source>Set up later</source>
<translation>بعدا تنظیم شود</translation> <translation>بعدا تنظیم شود</translation>
</message> </message>
@@ -1734,7 +1774,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>PageSetupWizardInstalling</name> <name>PageSetupWizardInstalling</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="60"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="61"/>
<source>The server has already been added to the application</source> <source>The server has already been added to the application</source>
<translation>سرور در حال حاضر به نرمافزار اضافه شده است</translation> <translation>سرور در حال حاضر به نرمافزار اضافه شده است</translation>
</message> </message>
@@ -1747,33 +1787,33 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="vanished">занят установкой других протоколов или сервисов. Установка Amnesia </translation> <translation type="vanished">занят установкой других протоколов или сервисов. Установка Amnesia </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="66"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="67"/>
<source>Amnezia has detected that your server is currently </source> <source>Amnezia has detected that your server is currently </source>
<translation>برنامه Amnezia تشخیص داده است که سرور در حال حاضر </translation> <translation>برنامه Amnezia تشخیص داده است که سرور در حال حاضر </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="67"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="68"/>
<source>busy installing other software. Amnezia installation </source> <source>busy installing other software. Amnezia installation </source>
<translation>مشغول نصب نرمافزار دیگری است. نصب Amnezia </translation> <translation>مشغول نصب نرمافزار دیگری است. نصب Amnezia </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="68"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="69"/>
<source>will pause until the server finishes installing other software</source> <source>will pause until the server finishes installing other software</source>
<translation>متوقف شده تا زمانی که سرور نصب نرمافزار دیگر را تمام کند</translation> <translation>متوقف شده تا زمانی که سرور نصب نرمافزار دیگر را تمام کند</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="126"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="127"/>
<source>Installing</source> <source>Installing</source>
<translation>در حال نصب</translation> <translation>در حال نصب</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="165"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="166"/>
<source>Cancel installation</source> <source>Cancel installation</source>
<translation>لغو عملیات نصب</translation> <translation>لغو عملیات نصب</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="21"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="21"/>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="72"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="73"/>
<source>Usually it takes no more than 5 minutes</source> <source>Usually it takes no more than 5 minutes</source>
<translation>معمولا بیش از 5 دقیقه طول نمیکشد</translation> <translation>معمولا بیش از 5 دقیقه طول نمیکشد</translation>
</message> </message>
@@ -1896,27 +1936,27 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>PageSetupWizardViewConfig</name> <name>PageSetupWizardViewConfig</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="63"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="64"/>
<source>New connection</source> <source>New connection</source>
<translation>ارتباط جدید</translation> <translation>ارتباط جدید</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="90"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="91"/>
<source>Do not use connection code from public sources. It could be created to intercept your data.</source> <source>Do not use connection code from public sources. It could be created to intercept your data.</source>
<translation>از کد اتصالی که در منابع عمومی هست استفاده نکنید. ممکن است برای شنود اطلاعات شما ایجاد شده باشد.</translation> <translation>از کد اتصالی که در منابع عمومی هست استفاده نکنید. ممکن است برای شنود اطلاعات شما ایجاد شده باشد.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="105"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="106"/>
<source>Collapse content</source> <source>Collapse content</source>
<translation>جمع کردن محتوا</translation> <translation>جمع کردن محتوا</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="105"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="106"/>
<source>Show content</source> <source>Show content</source>
<translation>نمایش محتوا</translation> <translation>نمایش محتوا</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="148"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="149"/>
<source>Connect</source> <source>Connect</source>
<translation>اتصال</translation> <translation>اتصال</translation>
</message> </message>
@@ -2958,22 +2998,22 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<context> <context>
<name>SettingsController</name> <name>SettingsController</name>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="26"/> <location filename="../ui/controllers/settingsController.cpp" line="27"/>
<source>Software version</source> <source>Software version</source>
<translation>نسخه نرمافزار</translation> <translation>نسخه نرمافزار</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="139"/> <location filename="../ui/controllers/settingsController.cpp" line="148"/>
<source>All settings have been reset to default values</source> <source>All settings have been reset to default values</source>
<translation>تمام تنظیمات به مقادیر پیش فرض ریست شد</translation> <translation>تمام تنظیمات به مقادیر پیش فرض ریست شد</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="145"/> <location filename="../ui/controllers/settingsController.cpp" line="154"/>
<source>Cached profiles cleared</source> <source>Cached profiles cleared</source>
<translation>پروفایل ذخیره شده پاک شد</translation> <translation>پروفایل ذخیره شده پاک شد</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="123"/> <location filename="../ui/controllers/settingsController.cpp" line="132"/>
<source>Backup file is corrupted</source> <source>Backup file is corrupted</source>
<translation>فایل بکآپ خراب شده است</translation> <translation>فایل بکآپ خراب شده است</translation>
</message> </message>
@@ -3105,7 +3145,7 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<context> <context>
<name>VpnConnection</name> <name>VpnConnection</name>
<message> <message>
<location filename="../vpnconnection.cpp" line="432"/> <location filename="../vpnconnection.cpp" line="438"/>
<source>Mbps</source> <source>Mbps</source>
<translation>Mbps</translation> <translation>Mbps</translation>
</message> </message>
+131 -91
View File
@@ -132,12 +132,12 @@
<context> <context>
<name>HomeContainersListView</name> <name>HomeContainersListView</name>
<message> <message>
<location filename="../ui/qml/Components/HomeContainersListView.qml" line="58"/> <location filename="../ui/qml/Components/HomeContainersListView.qml" line="76"/>
<source>Unable change protocol while there is an active connection</source> <source>Unable change protocol while there is an active connection</source>
<translation>Невозможно изменить протокол при активном соединении</translation> <translation>Невозможно изменить протокол при активном соединении</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/HomeContainersListView.qml" line="68"/> <location filename="../ui/qml/Components/HomeContainersListView.qml" line="85"/>
<source>The selected protocol is not supported on the current platform</source> <source>The selected protocol is not supported on the current platform</source>
<translation>Выбранный протокол не поддерживается на данном устройстве</translation> <translation>Выбранный протокол не поддерживается на данном устройстве</translation>
</message> </message>
@@ -149,7 +149,7 @@
<context> <context>
<name>ImportController</name> <name>ImportController</name>
<message> <message>
<location filename="../ui/controllers/importController.cpp" line="411"/> <location filename="../ui/controllers/importController.cpp" line="416"/>
<source>Scanned %1 of %2.</source> <source>Scanned %1 of %2.</source>
<translation>Отсканировано %1 из%2.</translation> <translation>Отсканировано %1 из%2.</translation>
</message> </message>
@@ -188,26 +188,31 @@ Already installed containers were found on the server. All installed containers
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="305"/> <location filename="../ui/controllers/installController.cpp" line="305"/>
<source>Server &apos;%1&apos; was rebooted</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/controllers/installController.cpp" line="314"/>
<source>Server &apos;%1&apos; was removed</source> <source>Server &apos;%1&apos; was removed</source>
<translation>Сервер &apos;%1&apos; был удален</translation> <translation>Сервер &apos;%1&apos; был удален</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="315"/> <location filename="../ui/controllers/installController.cpp" line="324"/>
<source>All containers from server &apos;%1&apos; have been removed</source> <source>All containers from server &apos;%1&apos; have been removed</source>
<translation>Все протоколы и сервисы были удалены с сервера &apos;%1&apos;</translation> <translation>Все протоколы и сервисы были удалены с сервера &apos;%1&apos;</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="332"/> <location filename="../ui/controllers/installController.cpp" line="341"/>
<source>%1 has been removed from the server &apos;%2&apos;</source> <source>%1 has been removed from the server &apos;%2&apos;</source>
<translation>%1 был удален с сервера &apos;%2&apos;</translation> <translation>%1 был удален с сервера &apos;%2&apos;</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="478"/> <location filename="../ui/controllers/installController.cpp" line="487"/>
<source>Please login as the user</source> <source>Please login as the user</source>
<translation>Пожалуйста, войдите в систему от имени пользователя</translation> <translation>Пожалуйста, войдите в систему от имени пользователя</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="506"/> <location filename="../ui/controllers/installController.cpp" line="515"/>
<source>Server added successfully</source> <source>Server added successfully</source>
<translation>Сервер успешно добавлен</translation> <translation>Сервер успешно добавлен</translation>
</message> </message>
@@ -275,17 +280,17 @@ Already installed containers were found on the server. All installed containers
<context> <context>
<name>PageHome</name> <name>PageHome</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="317"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="318"/>
<source>VPN protocol</source> <source>VPN protocol</source>
<translation>VPN протокол</translation> <translation>VPN протокол</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="361"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="362"/>
<source>Servers</source> <source>Servers</source>
<translation>Серверы</translation> <translation>Серверы</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="453"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="454"/>
<source>Unable change server while there is an active connection</source> <source>Unable change server while there is an active connection</source>
<translation>Невозможно изменить сервер при активном соединении</translation> <translation>Невозможно изменить сервер при активном соединении</translation>
</message> </message>
@@ -1218,57 +1223,62 @@ Already installed containers were found on the server. All installed containers
<context> <context>
<name>PageSettingsDns</name> <name>PageSettingsDns</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="45"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="35"/>
<source>Default server does not support custom dns</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="53"/>
<source>DNS servers</source> <source>DNS servers</source>
<translation>DNS сервер</translation> <translation>DNS сервер</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="50"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="58"/>
<source>If AmneziaDNS is not used or installed</source> <source>If AmneziaDNS is not used or installed</source>
<translation>Эти адреса будут использоваться, если не включен или не установлен AmneziaDNS</translation> <translation>Эти адреса будут использоваться, если не включен или не установлен AmneziaDNS</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="57"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="65"/>
<source>Primary DNS</source> <source>Primary DNS</source>
<translation>Первичный DNS</translation> <translation>Первичный DNS</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="69"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="77"/>
<source>Secondary DNS</source> <source>Secondary DNS</source>
<translation>Вторичный DNS</translation> <translation>Вторичный DNS</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="87"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="95"/>
<source>Restore default</source> <source>Restore default</source>
<translation>Восстановить по умолчанию</translation> <translation>Восстановить по умолчанию</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="90"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="98"/>
<source>Restore default DNS settings?</source> <source>Restore default DNS settings?</source>
<translation>Восстановить настройки DNS по умолчанию?</translation> <translation>Восстановить настройки DNS по умолчанию?</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="91"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="99"/>
<source>Continue</source> <source>Continue</source>
<translation>Продолжить</translation> <translation>Продолжить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="92"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="100"/>
<source>Cancel</source> <source>Cancel</source>
<translation>Отменить</translation> <translation>Отменить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="100"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="108"/>
<source>Settings have been reset</source> <source>Settings have been reset</source>
<translation>Настройки сброшены</translation> <translation>Настройки сброшены</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="112"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="120"/>
<source>Save</source> <source>Save</source>
<translation>Сохранить</translation> <translation>Сохранить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="121"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="129"/>
<source>Settings saved</source> <source>Settings saved</source>
<translation>Сохранить настройки</translation> <translation>Сохранить настройки</translation>
</message> </message>
@@ -1286,52 +1296,52 @@ Already installed containers were found on the server. All installed containers
<translation>Сохранять логи</translation> <translation>Сохранять логи</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="86"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="87"/>
<source>Open folder with logs</source> <source>Open folder with logs</source>
<translation>Открыть папку с логами</translation> <translation>Открыть папку с логами</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="108"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="109"/>
<source>Save</source> <source>Save</source>
<translation>Сохранить</translation> <translation>Сохранить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="109"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="110"/>
<source>Logs files (*.log)</source> <source>Logs files (*.log)</source>
<translation>Logs files (*.log)</translation> <translation>Logs files (*.log)</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="118"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="119"/>
<source>Logs file saved</source> <source>Logs file saved</source>
<translation>Файл с логами сохранен</translation> <translation>Файл с логами сохранен</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="127"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="128"/>
<source>Save logs to file</source> <source>Save logs to file</source>
<translation>Сохранить логи в файл</translation> <translation>Сохранить логи в файл</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="145"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="146"/>
<source>Clear logs?</source> <source>Clear logs?</source>
<translation>Очистить логи?</translation> <translation>Очистить логи?</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="146"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="147"/>
<source>Continue</source> <source>Continue</source>
<translation>Продолжить</translation> <translation>Продолжить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="147"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="148"/>
<source>Cancel</source> <source>Cancel</source>
<translation>Отменить</translation> <translation>Отменить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="154"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="155"/>
<source>Logs have been cleaned up</source> <source>Logs have been cleaned up</source>
<translation>Логи удалены</translation> <translation>Логи удалены</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="167"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="168"/>
<source>Clear logs</source> <source>Clear logs</source>
<translation>Удалить логи</translation> <translation>Удалить логи</translation>
</message> </message>
@@ -1344,17 +1354,17 @@ Already installed containers were found on the server. All installed containers
<translation>Все установленные протоколы и сервисы были добавлены в приложение</translation> <translation>Все установленные протоколы и сервисы были добавлены в приложение</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="87"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="91"/>
<source>Clear Amnezia cache</source> <source>Clear Amnezia cache</source>
<translation>Очистить кэш Amnezia</translation> <translation>Очистить кэш Amnezia</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="88"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="92"/>
<source>May be needed when changing other settings</source> <source>May be needed when changing other settings</source>
<translation>Может понадобиться при изменении других настроек</translation> <translation>Может понадобиться при изменении других настроек</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="91"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="95"/>
<source>Clear cached profiles?</source> <source>Clear cached profiles?</source>
<translation>Удалить кэш Amnezia?</translation> <translation>Удалить кэш Amnezia?</translation>
</message> </message>
@@ -1369,56 +1379,81 @@ Already installed containers were found on the server. All installed containers
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="93"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="97"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="140"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="145"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="171"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="177"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="208"/>
<source>Continue</source> <source>Continue</source>
<translation>Продолжить</translation> <translation>Продолжить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="94"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="98"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="141"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="146"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="172"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="178"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="209"/>
<source>Cancel</source> <source>Cancel</source>
<translation>Отменить</translation> <translation>Отменить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="117"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="121"/>
<source>Check the server for previously installed Amnezia services</source> <source>Check the server for previously installed Amnezia services</source>
<translation>Проверить сервер на наличие ранее установленных сервисов Amnezia</translation> <translation>Проверить сервер на наличие ранее установленных сервисов Amnezia</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="118"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="122"/>
<source>Add them to the application if they were not displayed</source> <source>Add them to the application if they were not displayed</source>
<translation>Добавить их в приложение, если они не были отображены</translation> <translation>Добавить их в приложение, если они не были отображены</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="134"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="139"/>
<source>Reboot server</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="143"/>
<source>Do you want to reboot the server?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="144"/>
<source>??????????????????????????????</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="175"/>
<source>Do you want to remove the server?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="206"/>
<source>Do you want to clear server from Amnezia software?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="171"/>
<source>Remove server from application</source> <source>Remove server from application</source>
<translation>Удалить сервер из приложения</translation> <translation>Удалить сервер из приложения</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="138"/>
<source>Remove server?</source> <source>Remove server?</source>
<translation>Удалить сервер?</translation> <translation type="vanished">Удалить сервер?</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="139"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="176"/>
<source>All installed AmneziaVPN services will still remain on the server.</source> <source>All installed AmneziaVPN services will still remain on the server.</source>
<translation>Все установленные сервисы и протоколы Amnezia всё ещё останутся на сервере.</translation> <translation>Все установленные сервисы и протоколы Amnezia всё ещё останутся на сервере.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="165"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="202"/>
<source>Clear server from Amnezia software</source> <source>Clear server from Amnezia software</source>
<translation>Очистить сервер от протоколов и сервисов Amnezia</translation> <translation>Очистить сервер от протоколов и сервисов Amnezia</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="169"/>
<source>Clear server from Amnezia software?</source> <source>Clear server from Amnezia software?</source>
<translation>Удалить все сервисы и протоколы Amnezia с сервера?</translation> <translation type="vanished">Удалить все сервисы и протоколы Amnezia с сервера?</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="170"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="207"/>
<source>All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.</source> <source>All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.</source>
<translation>На сервере будут удалены все данные, связанные с Amnezia: протоколы, сервисы, конфигурационные файлы, ключи и сертификаты.</translation> <translation>На сервере будут удалены все данные, связанные с Amnezia: протоколы, сервисы, конфигурационные файлы, ключи и сертификаты.</translation>
</message> </message>
@@ -1499,90 +1534,95 @@ Already installed containers were found on the server. All installed containers
<context> <context>
<name>PageSettingsSplitTunneling</name> <name>PageSettingsSplitTunneling</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="53"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="29"/>
<source>Default server does not support split tunneling function</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="59"/>
<source>Addresses from the list should be accessed via VPN</source> <source>Addresses from the list should be accessed via VPN</source>
<translation>Только адреса из списка должны открываться через VPN</translation> <translation>Только адреса из списка должны открываться через VPN</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="58"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="64"/>
<source>Addresses from the list should not be accessed via VPN</source> <source>Addresses from the list should not be accessed via VPN</source>
<translation>Адреса из списка не должны открываться через VPN</translation> <translation>Адреса из списка не должны открываться через VPN</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="90"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="96"/>
<source>Split tunneling</source> <source>Split tunneling</source>
<translation>Раздельное VPN-туннелирование</translation> <translation>Раздельное VPN-туннелирование</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="121"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="127"/>
<source>Mode</source> <source>Mode</source>
<translation>Режим</translation> <translation>Режим</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="199"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="205"/>
<source>Remove </source> <source>Remove </source>
<translation>Удалить </translation> <translation>Удалить </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="200"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="206"/>
<source>Continue</source> <source>Continue</source>
<translation>Продолжить</translation> <translation>Продолжить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="201"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="207"/>
<source>Cancel</source> <source>Cancel</source>
<translation>Отменить</translation> <translation>Отменить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="248"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="254"/>
<source>Site or IP</source> <source>Site or IP</source>
<translation>Сайт или IP</translation> <translation>Сайт или IP</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="292"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="298"/>
<source>Import/Export Sites</source> <source>Import/Export Sites</source>
<translation>Импорт/экспорт Сайтов</translation> <translation>Импорт/экспорт Сайтов</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="298"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="304"/>
<source>Import</source> <source>Import</source>
<translation>Импорт</translation> <translation>Импорт</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="310"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="316"/>
<source>Save site list</source> <source>Save site list</source>
<translation>Сохранить список сайтов</translation> <translation>Сохранить список сайтов</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="317"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="323"/>
<source>Save sites</source> <source>Save sites</source>
<translation>Сохранить</translation> <translation>Сохранить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="318"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="324"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="385"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="391"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="400"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="406"/>
<source>Sites files (*.json)</source> <source>Sites files (*.json)</source>
<translation>Sites files (*.json)</translation> <translation>Sites files (*.json)</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="375"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="381"/>
<source>Import a list of sites</source> <source>Import a list of sites</source>
<translation>Импортировать список с сайтами</translation> <translation>Импортировать список с сайтами</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="381"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="387"/>
<source>Replace site list</source> <source>Replace site list</source>
<translation>Заменить список сайтов</translation> <translation>Заменить список сайтов</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="384"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="390"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="399"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="405"/>
<source>Open sites file</source> <source>Open sites file</source>
<translation>Открыть список с сайтами</translation> <translation>Открыть список с сайтами</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="396"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="402"/>
<source>Add imported sites to existing ones</source> <source>Add imported sites to existing ones</source>
<translation>Добавить импортированные сайты к существующим</translation> <translation>Добавить импортированные сайты к существующим</translation>
</message> </message>
@@ -1709,22 +1749,22 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation>Какой уровень контроля интернета в вашем регионе?</translation> <translation>Какой уровень контроля интернета в вашем регионе?</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="137"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="139"/>
<source>Set up a VPN yourself</source> <source>Set up a VPN yourself</source>
<translation>Настроить VPN самостоятельно</translation> <translation>Настроить VPN самостоятельно</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="138"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="140"/>
<source>I want to choose a VPN protocol</source> <source>I want to choose a VPN protocol</source>
<translation>Выбрать VPN-протокол</translation> <translation>Выбрать VPN-протокол</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="157"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="159"/>
<source>Continue</source> <source>Continue</source>
<translation>Продолжить</translation> <translation>Продолжить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="197"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="199"/>
<source>Set up later</source> <source>Set up later</source>
<translation>Настроить позднее</translation> <translation>Настроить позднее</translation>
</message> </message>
@@ -1732,7 +1772,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>PageSetupWizardInstalling</name> <name>PageSetupWizardInstalling</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="60"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="61"/>
<source>The server has already been added to the application</source> <source>The server has already been added to the application</source>
<translation>Сервер уже был добавлен в приложение</translation> <translation>Сервер уже был добавлен в приложение</translation>
</message> </message>
@@ -1745,33 +1785,33 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="vanished">занят установкой других протоколов или сервисов. Установка Amnesia </translation> <translation type="vanished">занят установкой других протоколов или сервисов. Установка Amnesia </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="66"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="67"/>
<source>Amnezia has detected that your server is currently </source> <source>Amnezia has detected that your server is currently </source>
<translation>Amnezia обнаружила, что ваш сервер в настоящее время </translation> <translation>Amnezia обнаружила, что ваш сервер в настоящее время </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="67"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="68"/>
<source>busy installing other software. Amnezia installation </source> <source>busy installing other software. Amnezia installation </source>
<translation>занят установкой другого программного обеспечения. Установка Amnezia </translation> <translation>занят установкой другого программного обеспечения. Установка Amnezia </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="68"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="69"/>
<source>will pause until the server finishes installing other software</source> <source>will pause until the server finishes installing other software</source>
<translation>будет приостановлена до тех пор, пока сервер не завершит установку</translation> <translation>будет приостановлена до тех пор, пока сервер не завершит установку</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="126"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="127"/>
<source>Installing</source> <source>Installing</source>
<translation>Установка</translation> <translation>Установка</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="165"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="166"/>
<source>Cancel installation</source> <source>Cancel installation</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="21"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="21"/>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="72"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="73"/>
<source>Usually it takes no more than 5 minutes</source> <source>Usually it takes no more than 5 minutes</source>
<translation>Обычно это занимает не более 5 минут</translation> <translation>Обычно это занимает не более 5 минут</translation>
</message> </message>
@@ -1894,27 +1934,27 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>PageSetupWizardViewConfig</name> <name>PageSetupWizardViewConfig</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="63"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="64"/>
<source>New connection</source> <source>New connection</source>
<translation>Новое соединение</translation> <translation>Новое соединение</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="90"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="91"/>
<source>Do not use connection code from public sources. It could be created to intercept your data.</source> <source>Do not use connection code from public sources. It could be created to intercept your data.</source>
<translation>Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные.</translation> <translation>Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="105"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="106"/>
<source>Collapse content</source> <source>Collapse content</source>
<translation>Свернуть</translation> <translation>Свернуть</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="105"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="106"/>
<source>Show content</source> <source>Show content</source>
<translation>Показать содержимое ключа</translation> <translation>Показать содержимое ключа</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="148"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="149"/>
<source>Connect</source> <source>Connect</source>
<translation>Подключиться</translation> <translation>Подключиться</translation>
</message> </message>
@@ -2925,22 +2965,22 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<context> <context>
<name>SettingsController</name> <name>SettingsController</name>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="26"/> <location filename="../ui/controllers/settingsController.cpp" line="27"/>
<source>Software version</source> <source>Software version</source>
<translation>Версия ПО</translation> <translation>Версия ПО</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="139"/> <location filename="../ui/controllers/settingsController.cpp" line="148"/>
<source>All settings have been reset to default values</source> <source>All settings have been reset to default values</source>
<translation>Все настройки были сброшены к значению &quot;По умолчанию&quot;</translation> <translation>Все настройки были сброшены к значению &quot;По умолчанию&quot;</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="145"/> <location filename="../ui/controllers/settingsController.cpp" line="154"/>
<source>Cached profiles cleared</source> <source>Cached profiles cleared</source>
<translation>Кэш профиля очищен</translation> <translation>Кэш профиля очищен</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="123"/> <location filename="../ui/controllers/settingsController.cpp" line="132"/>
<source>Backup file is corrupted</source> <source>Backup file is corrupted</source>
<translation>Backup файл поврежден</translation> <translation>Backup файл поврежден</translation>
</message> </message>
@@ -3072,7 +3112,7 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<context> <context>
<name>VpnConnection</name> <name>VpnConnection</name>
<message> <message>
<location filename="../vpnconnection.cpp" line="432"/> <location filename="../vpnconnection.cpp" line="438"/>
<source>Mbps</source> <source>Mbps</source>
<translation>Mbps</translation> <translation>Mbps</translation>
</message> </message>
+131 -91
View File
@@ -135,12 +135,12 @@
<context> <context>
<name>HomeContainersListView</name> <name>HomeContainersListView</name>
<message> <message>
<location filename="../ui/qml/Components/HomeContainersListView.qml" line="58"/> <location filename="../ui/qml/Components/HomeContainersListView.qml" line="76"/>
<source>Unable change protocol while there is an active connection</source> <source>Unable change protocol while there is an active connection</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/HomeContainersListView.qml" line="68"/> <location filename="../ui/qml/Components/HomeContainersListView.qml" line="85"/>
<source>The selected protocol is not supported on the current platform</source> <source>The selected protocol is not supported on the current platform</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -152,7 +152,7 @@
<context> <context>
<name>ImportController</name> <name>ImportController</name>
<message> <message>
<location filename="../ui/controllers/importController.cpp" line="411"/> <location filename="../ui/controllers/importController.cpp" line="416"/>
<source>Scanned %1 of %2.</source> <source>Scanned %1 of %2.</source>
<translation> %1 of %2.</translation> <translation> %1 of %2.</translation>
</message> </message>
@@ -199,16 +199,21 @@ Already installed containers were found on the server. All installed containers
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="305"/> <location filename="../ui/controllers/installController.cpp" line="305"/>
<source>Server &apos;%1&apos; was rebooted</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/controllers/installController.cpp" line="314"/>
<source>Server &apos;%1&apos; was removed</source> <source>Server &apos;%1&apos; was removed</source>
<translation> &apos;%1&apos;</translation> <translation> &apos;%1&apos;</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="315"/> <location filename="../ui/controllers/installController.cpp" line="324"/>
<source>All containers from server &apos;%1&apos; have been removed</source> <source>All containers from server &apos;%1&apos; have been removed</source>
<translation> &apos;%1&apos; </translation> <translation> &apos;%1&apos; </translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="332"/> <location filename="../ui/controllers/installController.cpp" line="341"/>
<source>%1 has been removed from the server &apos;%2&apos;</source> <source>%1 has been removed from the server &apos;%2&apos;</source>
<translation>%1 &apos;%2&apos; </translation> <translation>%1 &apos;%2&apos; </translation>
</message> </message>
@@ -229,12 +234,12 @@ Already installed containers were found on the server. All installed containers
<translation type="obsolete"> </translation> <translation type="obsolete"> </translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="478"/> <location filename="../ui/controllers/installController.cpp" line="487"/>
<source>Please login as the user</source> <source>Please login as the user</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="506"/> <location filename="../ui/controllers/installController.cpp" line="515"/>
<source>Server added successfully</source> <source>Server added successfully</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -302,17 +307,17 @@ Already installed containers were found on the server. All installed containers
<context> <context>
<name>PageHome</name> <name>PageHome</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="317"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="318"/>
<source>VPN protocol</source> <source>VPN protocol</source>
<translation>VPN协议</translation> <translation>VPN协议</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="361"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="362"/>
<source>Servers</source> <source>Servers</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="453"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="454"/>
<source>Unable change server while there is an active connection</source> <source>Unable change server while there is an active connection</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -1297,57 +1302,62 @@ And if you don&apos;t like the app, all the more support it - the donation will
<context> <context>
<name>PageSettingsDns</name> <name>PageSettingsDns</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="45"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="35"/>
<source>Default server does not support custom dns</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="53"/>
<source>DNS servers</source> <source>DNS servers</source>
<translation>DNS服务器</translation> <translation>DNS服务器</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="50"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="58"/>
<source>If AmneziaDNS is not used or installed</source> <source>If AmneziaDNS is not used or installed</source>
<translation>使AmneziaDNS</translation> <translation>使AmneziaDNS</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="57"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="65"/>
<source>Primary DNS</source> <source>Primary DNS</source>
<translation> DNS</translation> <translation> DNS</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="69"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="77"/>
<source>Secondary DNS</source> <source>Secondary DNS</source>
<translation> DNS</translation> <translation> DNS</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="87"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="95"/>
<source>Restore default</source> <source>Restore default</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="90"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="98"/>
<source>Restore default DNS settings?</source> <source>Restore default DNS settings?</source>
<translation>DNS配置</translation> <translation>DNS配置</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="91"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="99"/>
<source>Continue</source> <source>Continue</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="92"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="100"/>
<source>Cancel</source> <source>Cancel</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="100"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="108"/>
<source>Settings have been reset</source> <source>Settings have been reset</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="112"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="120"/>
<source>Save</source> <source>Save</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="121"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="129"/>
<source>Settings saved</source> <source>Settings saved</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -1365,52 +1375,52 @@ And if you don&apos;t like the app, all the more support it - the donation will
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="86"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="87"/>
<source>Open folder with logs</source> <source>Open folder with logs</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="108"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="109"/>
<source>Save</source> <source>Save</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="109"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="110"/>
<source>Logs files (*.log)</source> <source>Logs files (*.log)</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="118"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="119"/>
<source>Logs file saved</source> <source>Logs file saved</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="127"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="128"/>
<source>Save logs to file</source> <source>Save logs to file</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="145"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="146"/>
<source>Clear logs?</source> <source>Clear logs?</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="146"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="147"/>
<source>Continue</source> <source>Continue</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="147"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="148"/>
<source>Cancel</source> <source>Cancel</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="154"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="155"/>
<source>Logs have been cleaned up</source> <source>Logs have been cleaned up</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="167"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="168"/>
<source>Clear logs</source> <source>Clear logs</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -1428,76 +1438,101 @@ And if you don&apos;t like the app, all the more support it - the donation will
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="87"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="91"/>
<source>Clear Amnezia cache</source> <source>Clear Amnezia cache</source>
<translation> Amnezia </translation> <translation> Amnezia </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="88"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="92"/>
<source>May be needed when changing other settings</source> <source>May be needed when changing other settings</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="91"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="95"/>
<source>Clear cached profiles?</source> <source>Clear cached profiles?</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="143"/>
<source>Do you want to reboot the server?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="144"/>
<source>??????????????????????????????</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="175"/>
<source>Do you want to remove the server?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="206"/>
<source>Do you want to clear server from Amnezia software?</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="92"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="92"/>
<source></source> <source></source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="93"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="97"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="140"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="145"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="171"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="177"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="208"/>
<source>Continue</source> <source>Continue</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="94"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="98"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="141"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="146"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="172"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="178"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="209"/>
<source>Cancel</source> <source>Cancel</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="117"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="121"/>
<source>Check the server for previously installed Amnezia services</source> <source>Check the server for previously installed Amnezia services</source>
<translation> Amnezia </translation> <translation> Amnezia </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="118"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="122"/>
<source>Add them to the application if they were not displayed</source> <source>Add them to the application if they were not displayed</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="134"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="139"/>
<source>Reboot server</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="171"/>
<source>Remove server from application</source> <source>Remove server from application</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="138"/>
<source>Remove server?</source> <source>Remove server?</source>
<translation>?</translation> <translation type="vanished">?</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="139"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="176"/>
<source>All installed AmneziaVPN services will still remain on the server.</source> <source>All installed AmneziaVPN services will still remain on the server.</source>
<translation> AmneziaVPN </translation> <translation> AmneziaVPN </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="165"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="202"/>
<source>Clear server from Amnezia software</source> <source>Clear server from Amnezia software</source>
<translation>Amnezia中服务器信息</translation> <translation>Amnezia中服务器信息</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="169"/>
<source>Clear server from Amnezia software?</source> <source>Clear server from Amnezia software?</source>
<translation>Amnezia中服务器信息</translation> <translation type="vanished">Amnezia中服务器信息</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="170"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="207"/>
<source>All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.</source> <source>All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -1598,90 +1633,95 @@ And if you don&apos;t like the app, all the more support it - the donation will
<translation type="obsolete">VPN分流</translation> <translation type="obsolete">VPN分流</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="53"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="29"/>
<source>Default server does not support split tunneling function</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="59"/>
<source>Addresses from the list should be accessed via VPN</source> <source>Addresses from the list should be accessed via VPN</source>
<translation>使VPN访问</translation> <translation>使VPN访问</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="58"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="64"/>
<source>Addresses from the list should not be accessed via VPN</source> <source>Addresses from the list should not be accessed via VPN</source>
<translation>使VPN访问</translation> <translation>使VPN访问</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="90"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="96"/>
<source>Split tunneling</source> <source>Split tunneling</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="121"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="127"/>
<source>Mode</source> <source>Mode</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="199"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="205"/>
<source>Remove </source> <source>Remove </source>
<translation> </translation> <translation> </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="200"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="206"/>
<source>Continue</source> <source>Continue</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="201"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="207"/>
<source>Cancel</source> <source>Cancel</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="248"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="254"/>
<source>Site or IP</source> <source>Site or IP</source>
<translation>IP地址</translation> <translation>IP地址</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="292"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="298"/>
<source>Import/Export Sites</source> <source>Import/Export Sites</source>
<translation>/</translation> <translation>/</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="298"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="304"/>
<source>Import</source> <source>Import</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="310"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="316"/>
<source>Save site list</source> <source>Save site list</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="317"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="323"/>
<source>Save sites</source> <source>Save sites</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="318"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="324"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="385"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="391"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="400"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="406"/>
<source>Sites files (*.json)</source> <source>Sites files (*.json)</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="375"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="381"/>
<source>Import a list of sites</source> <source>Import a list of sites</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="381"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="387"/>
<source>Replace site list</source> <source>Replace site list</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="384"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="390"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="399"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="405"/>
<source>Open sites file</source> <source>Open sites file</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="396"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="402"/>
<source>Add imported sites to existing ones</source> <source>Add imported sites to existing ones</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -1808,22 +1848,22 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="137"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="139"/>
<source>Set up a VPN yourself</source> <source>Set up a VPN yourself</source>
<translation>VPN</translation> <translation>VPN</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="138"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="140"/>
<source>I want to choose a VPN protocol</source> <source>I want to choose a VPN protocol</source>
<translation>VPN协议</translation> <translation>VPN协议</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="157"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="159"/>
<source>Continue</source> <source>Continue</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="197"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="199"/>
<source>Set up later</source> <source>Set up later</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -1832,27 +1872,27 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<name>PageSetupWizardInstalling</name> <name>PageSetupWizardInstalling</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="21"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="21"/>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="72"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="73"/>
<source>Usually it takes no more than 5 minutes</source> <source>Usually it takes no more than 5 minutes</source>
<translation>5</translation> <translation>5</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="60"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="61"/>
<source>The server has already been added to the application</source> <source>The server has already been added to the application</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="66"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="67"/>
<source>Amnezia has detected that your server is currently </source> <source>Amnezia has detected that your server is currently </source>
<translation>Amnezia </translation> <translation>Amnezia </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="67"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="68"/>
<source>busy installing other software. Amnezia installation </source> <source>busy installing other software. Amnezia installation </source>
<translation>Amnezia安装</translation> <translation>Amnezia安装</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="165"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="166"/>
<source>Cancel installation</source> <source>Cancel installation</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@@ -1865,12 +1905,12 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="vanished">Amnezia安装</translation> <translation type="vanished">Amnezia安装</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="68"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="69"/>
<source>will pause until the server finishes installing other software</source> <source>will pause until the server finishes installing other software</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="126"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="127"/>
<source>Installing</source> <source>Installing</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -1993,27 +2033,27 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>PageSetupWizardViewConfig</name> <name>PageSetupWizardViewConfig</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="63"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="64"/>
<source>New connection</source> <source>New connection</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="90"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="91"/>
<source>Do not use connection code from public sources. It could be created to intercept your data.</source> <source>Do not use connection code from public sources. It could be created to intercept your data.</source>
<translation>使</translation> <translation>使</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="105"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="106"/>
<source>Collapse content</source> <source>Collapse content</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="105"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="106"/>
<source>Show content</source> <source>Show content</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="148"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="149"/>
<source>Connect</source> <source>Connect</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -3069,22 +3109,22 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<context> <context>
<name>SettingsController</name> <name>SettingsController</name>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="26"/> <location filename="../ui/controllers/settingsController.cpp" line="27"/>
<source>Software version</source> <source>Software version</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="123"/> <location filename="../ui/controllers/settingsController.cpp" line="132"/>
<source>Backup file is corrupted</source> <source>Backup file is corrupted</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="139"/> <location filename="../ui/controllers/settingsController.cpp" line="148"/>
<source>All settings have been reset to default values</source> <source>All settings have been reset to default values</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="145"/> <location filename="../ui/controllers/settingsController.cpp" line="154"/>
<source>Cached profiles cleared</source> <source>Cached profiles cleared</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -3220,7 +3260,7 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<context> <context>
<name>VpnConnection</name> <name>VpnConnection</name>
<message> <message>
<location filename="../vpnconnection.cpp" line="432"/> <location filename="../vpnconnection.cpp" line="438"/>
<source>Mbps</source> <source>Mbps</source>
<translation></translation> <translation></translation>
</message> </message>
+33 -26
View File
@@ -5,12 +5,14 @@
#include <QNetworkReply> #include <QNetworkReply>
#include "configurators/openvpn_configurator.h" #include "configurators/openvpn_configurator.h"
#include "configurators/wireguard_configurator.h"
namespace namespace
{ {
namespace configKey namespace configKey
{ {
constexpr char cloak[] = "cloak"; constexpr char cloak[] = "cloak";
constexpr char awg[] = "awg";
constexpr char apiEdnpoint[] = "api_endpoint"; constexpr char apiEdnpoint[] = "api_endpoint";
constexpr char accessToken[] = "api_key"; constexpr char accessToken[] = "api_key";
@@ -26,33 +28,42 @@ ApiController::ApiController(const QSharedPointer<ServersModel> &serversModel,
{ {
} }
QString ApiController::genPublicKey(const QString &protocol) void ApiController::processCloudConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, QString &config)
{
if (protocol == configKey::cloak) {
return ".";
}
return QString();
}
QString ApiController::genCertificateRequest(const QString &protocol)
{
if (protocol == configKey::cloak) {
m_certRequest = OpenVpnConfigurator::createCertRequest();
return m_certRequest.request;
}
return QString();
}
void ApiController::processCloudConfig(const QString &protocol, QString &config)
{ {
if (protocol == configKey::cloak) { if (protocol == configKey::cloak) {
config.replace("<key>", "<key>\n"); config.replace("<key>", "<key>\n");
config.replace("$OPENVPN_PRIV_KEY", m_certRequest.privKey); config.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey);
return; return;
} else if (protocol == configKey::awg) {
config.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey);
} }
return; return;
} }
ApiController::ApiPayloadData ApiController::generateApiPayloadData(const QString &protocol)
{
ApiController::ApiPayloadData apiPayload;
if (protocol == configKey::cloak) {
apiPayload.certRequest = OpenVpnConfigurator::createCertRequest();
} else if (protocol == configKey::awg) {
auto connData = WireguardConfigurator::genClientKeys();
apiPayload.wireGuardClientPubKey = connData.clientPubKey;
apiPayload.wireGuardClientPrivKey = connData.clientPrivKey;
}
return apiPayload;
}
QJsonObject ApiController::fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData)
{
QJsonObject obj;
if (protocol == configKey::cloak) {
obj[configKey::certificate] = apiPayloadData.certRequest.request;
} else if (protocol == configKey::awg) {
obj[configKey::publicKey] = apiPayloadData.wireGuardClientPubKey;
}
return obj;
}
bool ApiController::updateServerConfigFromApi() bool ApiController::updateServerConfigFromApi()
{ {
auto serverConfig = m_serversModel->getDefaultServerConfig(); auto serverConfig = m_serversModel->getDefaultServerConfig();
@@ -71,13 +82,9 @@ bool ApiController::updateServerConfigFromApi()
QString protocol = serverConfig.value(configKey::protocol).toString(); QString protocol = serverConfig.value(configKey::protocol).toString();
QJsonObject obj; auto apiPayloadData = generateApiPayloadData(protocol);
obj[configKey::publicKey] = genPublicKey(protocol); QByteArray requestBody = QJsonDocument(fillApiPayload(protocol, apiPayloadData)).toJson();
obj[configKey::certificate] = genCertificateRequest(protocol);
QByteArray requestBody = QJsonDocument(obj).toJson();
qDebug() << requestBody;
QScopedPointer<QNetworkReply> reply; QScopedPointer<QNetworkReply> reply;
reply.reset(manager.post(request, requestBody)); reply.reset(manager.post(request, requestBody));
@@ -100,7 +107,7 @@ bool ApiController::updateServerConfigFromApi()
} }
QString configStr = ba; QString configStr = ba;
processCloudConfig(protocol, configStr); processCloudConfig(protocol, apiPayloadData, configStr);
QJsonObject cloudConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); QJsonObject cloudConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
+9 -5
View File
@@ -22,15 +22,19 @@ signals:
void errorOccurred(const QString &errorMessage); void errorOccurred(const QString &errorMessage);
private: private:
QString genPublicKey(const QString &protocol); struct ApiPayloadData {
QString genCertificateRequest(const QString &protocol); OpenVpnConfigurator::ConnectionData certRequest;
void processCloudConfig(const QString &protocol, QString &config); QString wireGuardClientPrivKey;
QString wireGuardClientPubKey;
};
ApiPayloadData generateApiPayloadData(const QString &protocol);
QJsonObject fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData);
void processCloudConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, QString &config);
QSharedPointer<ServersModel> m_serversModel; QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<ContainersModel> m_containersModel; QSharedPointer<ContainersModel> m_containersModel;
OpenVpnConfigurator::ConnectionData m_certRequest;
}; };
#endif // APICONTROLLER_H #endif // APICONTROLLER_H
+2 -1
View File
@@ -327,7 +327,8 @@ void ExportController::updateClientManagementModel(const DockerContainer contain
void ExportController::revokeConfig(const int row, const DockerContainer container, ServerCredentials credentials) void ExportController::revokeConfig(const int row, const DockerContainer container, ServerCredentials credentials)
{ {
ErrorCode errorCode = m_clientManagementModel->revokeClient(row, container, credentials); ErrorCode errorCode = m_clientManagementModel->revokeClient(row, container, credentials,
m_serversModel->getCurrentlyProcessedServerIndex());
if (errorCode != ErrorCode::NoError) { if (errorCode != ErrorCode::NoError) {
emit exportErrorOccurred(errorString(errorCode)); emit exportErrorOccurred(errorString(errorCode));
} }
@@ -296,6 +296,15 @@ void InstallController::updateContainer(QJsonObject config)
emit installationErrorOccurred(errorString(errorCode)); emit installationErrorOccurred(errorString(errorCode));
} }
void InstallController::rebootCurrentlyProcessedServer()
{
int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex();
QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString();
m_serversModel->rebootServer();
emit rebootCurrentlyProcessedServerFinished(tr("Server '%1' was rebooted").arg(serverName));
}
void InstallController::removeCurrentlyProcessedServer() void InstallController::removeCurrentlyProcessedServer()
{ {
int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex();
@@ -31,6 +31,7 @@ public slots:
void updateContainer(QJsonObject config); void updateContainer(QJsonObject config);
void removeCurrentlyProcessedServer(); void removeCurrentlyProcessedServer();
void rebootCurrentlyProcessedServer();
void removeAllContainers(); void removeAllContainers();
void removeCurrentlyProcessedContainer(); void removeCurrentlyProcessedContainer();
@@ -53,6 +54,7 @@ signals:
void scanServerFinished(bool isInstalledContainerFound); void scanServerFinished(bool isInstalledContainerFound);
void rebootCurrentlyProcessedServerFinished(const QString &finishedMessage);
void removeCurrentlyProcessedServerFinished(const QString &finishedMessage); void removeCurrentlyProcessedServerFinished(const QString &finishedMessage);
void removeAllContainersFinished(const QString &finishedMessage); void removeAllContainersFinished(const QString &finishedMessage);
void removeCurrentlyProcessedContainerFinished(const QString &finishedMessage); void removeCurrentlyProcessedContainerFinished(const QString &finishedMessage);
+18 -16
View File
@@ -296,30 +296,36 @@ ErrorCode ClientManagementModel::renameClient(const int row, const QString &clie
} }
ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContainer container, ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContainer container,
ServerCredentials credentials) ServerCredentials credentials, const int serverIndex)
{ {
ErrorCode errorCode = ErrorCode::NoError; ErrorCode errorCode = ErrorCode::NoError;
auto client = m_clientsTable.at(row).toObject();
QString clientId = client.value(configKey::clientId).toString();
if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks
|| container == DockerContainer::Cloak) { || container == DockerContainer::Cloak) {
errorCode = revokeOpenVpn(row, container, credentials); errorCode = revokeOpenVpn(row, container, credentials, serverIndex);
} else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) { } else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) {
errorCode = revokeWireGuard(row, container, credentials); errorCode = revokeWireGuard(row, container, credentials);
} }
if (errorCode == ErrorCode::NoError) { if (errorCode == ErrorCode::NoError) {
auto client = m_clientsTable.at(row).toObject(); const auto server = m_settings->server(serverIndex);
QString clientId = client.value(configKey::clientId).toString();
const auto server = m_settings->defaultServer();
QJsonArray containers = server.value(config_key::containers).toArray(); QJsonArray containers = server.value(config_key::containers).toArray();
for (auto i = 0; i < containers.size(); i++) { for (auto i = 0; i < containers.size(); i++) {
auto containerConfig = containers.at(i).toObject(); auto containerConfig = containers.at(i).toObject();
auto containerType = ContainerProps::containerFromString(containerConfig.value(config_key::container).toString()); auto containerType = ContainerProps::containerFromString(containerConfig.value(config_key::container).toString());
auto protocolConfig = containerConfig.value(ContainerProps::containerTypeToString(containerType)).toObject(); if (containerType == container) {
QJsonObject protocolConfig;
if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) {
protocolConfig = containerConfig.value(ContainerProps::containerTypeToString(DockerContainer::OpenVpn)).toObject();
} else {
protocolConfig = containerConfig.value(ContainerProps::containerTypeToString(containerType)).toObject();
}
if (protocolConfig.value(config_key::last_config).toString().contains(clientId)) { if (protocolConfig.value(config_key::last_config).toString().contains(clientId)) {
emit adminConfigRevoked(container); emit adminConfigRevoked(container);
}
} }
} }
} }
@@ -328,7 +334,7 @@ ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContain
} }
ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContainer container, ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContainer container,
ServerCredentials credentials) ServerCredentials credentials, const int serverIndex)
{ {
auto client = m_clientsTable.at(row).toObject(); auto client = m_clientsTable.at(row).toObject();
QString clientId = client.value(configKey::clientId).toString(); QString clientId = client.value(configKey::clientId).toString();
@@ -337,6 +343,7 @@ ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContai
"cd /opt/amnezia/openvpn ;\\" "cd /opt/amnezia/openvpn ;\\"
"easyrsa revoke %1 ;\\" "easyrsa revoke %1 ;\\"
"easyrsa gen-crl ;\\" "easyrsa gen-crl ;\\"
"chmod 666 pki/crl.pem ;\\"
"cp pki/crl.pem .'") "cp pki/crl.pem .'")
.arg(clientId); .arg(clientId);
@@ -356,12 +363,7 @@ ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContai
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable");
if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn));
|| container == DockerContainer::Cloak) {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn));
} else {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container));
}
error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) { if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload the clientsTable file to the server"; logger.error() << "Failed to upload the clientsTable file to the server";
+2 -2
View File
@@ -28,7 +28,7 @@ public slots:
ServerCredentials credentials); ServerCredentials credentials);
ErrorCode renameClient(const int row, const QString &userName, const DockerContainer container, ErrorCode renameClient(const int row, const QString &userName, const DockerContainer container,
ServerCredentials credentials, bool addTimeStamp = false); ServerCredentials credentials, bool addTimeStamp = false);
ErrorCode revokeClient(const int index, const DockerContainer container, ServerCredentials credentials); ErrorCode revokeClient(const int index, const DockerContainer container, ServerCredentials credentials, const int serverIndex);
protected: protected:
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
@@ -41,7 +41,7 @@ private:
void migration(const QByteArray &clientsTableString); void migration(const QByteArray &clientsTableString);
ErrorCode revokeOpenVpn(const int row, const DockerContainer container, ServerCredentials credentials); ErrorCode revokeOpenVpn(const int row, const DockerContainer container, ServerCredentials credentials, const int serverIndex);
ErrorCode revokeWireGuard(const int row, const DockerContainer container, ServerCredentials credentials); ErrorCode revokeWireGuard(const int row, const DockerContainer container, ServerCredentials credentials);
ErrorCode getOpenVpnClients(ServerController &serverController, DockerContainer container, ServerCredentials credentials, int &count); ErrorCode getOpenVpnClients(ServerController &serverController, DockerContainer container, ServerCredentials credentials, int &count);
@@ -1,4 +1,4 @@
#include "ikev2ConfigModel.h".h " #include "ikev2ConfigModel.h"
#include "protocols/protocols_defs.h" #include "protocols/protocols_defs.h"
+9
View File
@@ -435,6 +435,15 @@ ErrorCode ServersModel::removeAllContainers()
return errorCode; return errorCode;
} }
ErrorCode ServersModel::rebootServer()
{
ServerController serverController(m_settings);
auto credentials = m_settings->serverCredentials(m_currentlyProcessedServerIndex);
ErrorCode errorCode = serverController.rebootServer(credentials);
return errorCode;
}
ErrorCode ServersModel::removeContainer(const int containerIndex) ErrorCode ServersModel::removeContainer(const int containerIndex)
{ {
ServerController serverController(m_settings); ServerController serverController(m_settings);
+1
View File
@@ -88,6 +88,7 @@ public slots:
ErrorCode removeContainer(const int containerIndex); ErrorCode removeContainer(const int containerIndex);
ErrorCode removeAllContainers(); ErrorCode removeAllContainers();
ErrorCode rebootServer();
void setDefaultContainer(const int containerIndex); void setDefaultContainer(const int containerIndex);
DockerContainer getDefaultContainer(); DockerContainer getDefaultContainer();
@@ -81,7 +81,7 @@ PageType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 32 Layout.topMargin: 32
headerText: qsTr("VPN Addresses Subnet") headerText: qsTr("VPN address subnet")
textFieldText: subnetAddress textFieldText: subnetAddress
textField.onEditingFinished: { textField.onEditingFinished: {
@@ -91,7 +91,7 @@ PageType {
onLinkActivated: Qt.openUrlExternally(link) onLinkActivated: Qt.openUrlExternally(link)
textFormat: Text.RichText textFormat: Text.RichText
text: qsTr("Use <a href=\"https://www.torproject.org/download/\" style=\"color: #FBB26A;\">Tor Browser</a> to open this url.") text: qsTr("Use <a href=\"https://www.torproject.org/download/\" style=\"color: #FBB26A;\">Tor Browser</a> to open this URL.")
} }
ParagraphTextType { ParagraphTextType {
@@ -100,7 +100,7 @@ PageType {
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
text: qsTr("After installation it takes several minutes while your onion site will become available in the Tor Network.") text: qsTr("After creating your onion site, it takes a few minutes for the Tor network to make it available for use.")
} }
ParagraphTextType { ParagraphTextType {
+1 -1
View File
@@ -53,7 +53,7 @@ PageType {
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
text: qsTr("Support the project with a donation") text: qsTr("Support Amnezia")
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
} }
@@ -83,7 +83,7 @@ PageType {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("DNS servers") text: qsTr("DNS servers")
descriptionText: qsTr("If AmneziaDNS is not used or installed") descriptionText: qsTr("When AmneziaDNS is not used or installed")
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() { clickedFunction: function() {
@@ -117,7 +117,7 @@ PageType {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("App-based split tunneling") text: qsTr("App-based split tunneling")
descriptionText: qsTr("Allows you to use the VPN only for certain applications") descriptionText: qsTr("Allows you to use the VPN only for certain Apps")
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() { clickedFunction: function() {
@@ -38,6 +38,10 @@ PageType {
PageController.showNotificationMessage(finishedMessage) PageController.showNotificationMessage(finishedMessage)
} }
function onRebootCurrentlyProcessedServerFinished(finishedMessage) {
PageController.showNotificationMessage(finishedMessage)
}
function onRemoveAllContainersFinished(finishedMessage) { function onRemoveAllContainersFinished(finishedMessage) {
PageController.closePage() // close deInstalling page PageController.closePage() // close deInstalling page
PageController.showNotificationMessage(finishedMessage) PageController.showNotificationMessage(finishedMessage)
@@ -128,6 +132,39 @@ PageType {
visible: content.isServerWithWriteAccess visible: content.isServerWithWriteAccess
} }
LabelWithButtonType {
visible: content.isServerWithWriteAccess
Layout.fillWidth: true
text: qsTr("Reboot server")
textColor: "#EB5757"
clickedFunction: function() {
questionDrawer.headerText = qsTr("Do you want to reboot the server?")
questionDrawer.descriptionText = qsTr("The reboot process may take approximately 30 seconds. Are you sure you wish to proceed?")
questionDrawer.yesButtonText = qsTr("Continue")
questionDrawer.noButtonText = qsTr("Cancel")
questionDrawer.yesButtonFunction = function() {
questionDrawer.visible = false
PageController.showBusyIndicator(true)
if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
ConnectionController.closeConnection()
}
InstallController.rebootCurrentlyProcessedServer()
PageController.showBusyIndicator(false)
}
questionDrawer.noButtonFunction = function() {
questionDrawer.visible = false
}
questionDrawer.visible = true
}
}
DividerType {
visible: content.isServerWithWriteAccess
}
LabelWithButtonType { LabelWithButtonType {
Layout.fillWidth: true Layout.fillWidth: true
@@ -135,7 +172,7 @@ PageType {
textColor: "#EB5757" textColor: "#EB5757"
clickedFunction: function() { clickedFunction: function() {
questionDrawer.headerText = qsTr("Remove server?") questionDrawer.headerText = qsTr("Do you want to remove the server from application?")
questionDrawer.descriptionText = qsTr("All installed AmneziaVPN services will still remain on the server.") questionDrawer.descriptionText = qsTr("All installed AmneziaVPN services will still remain on the server.")
questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.yesButtonText = qsTr("Continue")
questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.noButtonText = qsTr("Cancel")
@@ -166,7 +203,7 @@ PageType {
textColor: "#EB5757" textColor: "#EB5757"
clickedFunction: function() { clickedFunction: function() {
questionDrawer.headerText = qsTr("Clear server from Amnezia software?") questionDrawer.headerText = qsTr("Do you want to clear server from Amnezia software?")
questionDrawer.descriptionText = qsTr("All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.") questionDrawer.descriptionText = qsTr("All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.")
questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.yesButtonText = qsTr("Continue")
questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.noButtonText = qsTr("Cancel")
@@ -56,7 +56,7 @@ PageType {
QtObject { QtObject {
id: onlyForwardSites id: onlyForwardSites
property string name: qsTr("Addresses from the list should be accessed via VPN") property string name: qsTr("Only the sites listed here will be accessed through the VPN")
property int type: routeMode.onlyForwardSites property int type: routeMode.onlyForwardSites
} }
QtObject { QtObject {
@@ -251,7 +251,7 @@ PageType {
TextFieldWithHeaderType { TextFieldWithHeaderType {
Layout.fillWidth: true Layout.fillWidth: true
textFieldPlaceholderText: qsTr("Site or IP") textFieldPlaceholderText: qsTr("website or IP")
buttonImageSource: "qrc:/images/controls/plus.svg" buttonImageSource: "qrc:/images/controls/plus.svg"
clickedFunc: function() { clickedFunc: function() {
@@ -295,7 +295,7 @@ PageType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: 16 Layout.margins: 16
headerText: qsTr("Import/Export Sites") headerText: qsTr("Import / Export Sites")
} }
LabelWithButtonType { LabelWithButtonType {
@@ -73,7 +73,7 @@ PageType {
property bool hidePassword: true property bool hidePassword: true
Layout.fillWidth: true Layout.fillWidth: true
headerText: qsTr("Password / SSH private key") headerText: qsTr("Password or SSH private key")
textField.echoMode: hidePassword ? TextInput.Password : TextInput.Normal textField.echoMode: hidePassword ? TextInput.Password : TextInput.Normal
buttonImageSource: textFieldText !== "" ? (hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") buttonImageSource: textFieldText !== "" ? (hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg")
: "" : ""
+20 -1
View File
@@ -388,6 +388,25 @@ void VpnConnection::createProtocolConnections()
void VpnConnection::appendSplitTunnelingConfig() void VpnConnection::appendSplitTunnelingConfig()
{ {
if (m_vpnConfiguration.value(config_key::configVersion).toInt()) {
auto protocolName = m_vpnConfiguration.value(config_key::vpnproto).toString();
if (protocolName == ProtocolProps::protoToString(Proto::Awg)) {
auto configData = m_vpnConfiguration.value(protocolName + "_config_data").toObject();
QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(configData.value("allowed_ips").toString().split(","));
QJsonArray defaultAllowedIP = QJsonArray::fromStringList(QString("0.0.0.0/0, ::/0").split(","));
if (allowedIpsJsonArray != defaultAllowedIP) {
allowedIpsJsonArray.append(m_vpnConfiguration.value(config_key::dns1).toString());
allowedIpsJsonArray.append(m_vpnConfiguration.value(config_key::dns2).toString());
m_vpnConfiguration.insert(config_key::splitTunnelType, Settings::RouteMode::VpnOnlyForwardSites);
m_vpnConfiguration.insert(config_key::splitTunnelSites, allowedIpsJsonArray);
return;
}
}
}
auto routeMode = m_settings->routeMode(); auto routeMode = m_settings->routeMode();
auto sites = m_settings->getVpnIps(routeMode); auto sites = m_settings->getVpnIps(routeMode);
@@ -397,7 +416,7 @@ void VpnConnection::appendSplitTunnelingConfig()
} }
// Allow traffic to Amezia DNS // Allow traffic to Amezia DNS
if (routeMode == Settings::VpnOnlyForwardSites){ if (routeMode == Settings::VpnOnlyForwardSites) {
sitesJsonArray.append(m_vpnConfiguration.value(config_key::dns1).toString()); sitesJsonArray.append(m_vpnConfiguration.value(config_key::dns1).toString());
sitesJsonArray.append(m_vpnConfiguration.value(config_key::dns2).toString()); sitesJsonArray.append(m_vpnConfiguration.value(config_key::dns2).toString());
} }
@@ -0,0 +1,3 @@
# Always allow at least loopback/localhost traffic
set skip on lo0
pass quick on lo0 flags any
@@ -0,0 +1,3 @@
# Block all traffic by default (can be overridden by later rules)
block out all flags any no state
@@ -0,0 +1,2 @@
table <allownets> {}
pass out to <allownets> flags any no state
@@ -0,0 +1,2 @@
table <blocknets> {}
block out to <blocknets> flags any no state
@@ -0,0 +1,2 @@
# Rules are set at runtime
@@ -0,0 +1,9 @@
# Exempt the tunnel interface(s) used by the VPN connection
utunInterfaces = "{ \
utun0, utun1, utun2, utun3, utun4, utun5, utun6, utun7, utun8, utun9, utun10, \
utun11, utun12, utun13, utun14, utun15, utun16, utun17, utun18, utun19, utun20, \
utun21, utun22, utun23, utun24, utun25, utun26, utun27, utun28, utun29, utun30 \
}"
pass out on $utunInterfaces flags any no state
@@ -0,0 +1,2 @@
# Block all outgoing IPv6 traffic (even over the VPN)
block return out inet6 flags any no state
@@ -0,0 +1,5 @@
# Allow DHCP
pass out inet proto udp from port 68 to 255.255.255.255 port 67 no state
# Allow DHCPv6
pass out inet6 proto udp from port 546 to ff00::/8 port 547 no state
@@ -0,0 +1,3 @@
# Allow LAN IP ranges
table <lanips> { 10.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.168.0.0/16, 224.0.0.0/4, 255.255.255.255/32, fc00::/7, fe80::/10, ff00::/8 }
pass out to <lanips> flags any no state
@@ -0,0 +1,7 @@
# Block all DNS traffic
block return out proto { tcp, udp } to port 53 flags any no state
# Allow our DNS servers
table <dnsaddr> {}
pass out proto { tcp, udp } to <dnsaddr> port 53 flags any no state
@@ -0,0 +1,14 @@
utunInterfaces = "{ \
utun0, utun1, utun2, utun3, utun4, utun5, utun6, utun7, utun8, utun9, utun10, \
utun11, utun12, utun13, utun14, utun15, utun16, utun17, utun18, utun19, utun20, \
utun21, utun22, utun23, utun24, utun25, utun26, utun27, utun28, utun29, utun30 \
}"
hnsdGroup=amnhnsd
# Block everything from handshake group
# Without this initial block hnsd traffic could possibly travel outside the tunnel (we don't trust the routing table)
block return out group $hnsdGroup flags any no state
# Next, poke a hole in this block but only for traffic on the tunnel (port 13038 is the handshake control port)
pass out on $utunInterfaces proto { tcp, udp } to port { 53, 13038 } group $hnsdGroup flags any no state
@@ -0,0 +1,2 @@
# Allow traffic by privileged group (used by daemon)
pass out proto { tcp, udp } group { amnvpn } flags any no state
+16
View File
@@ -0,0 +1,16 @@
# This root anchor file establishes multiple sub-anchors which can be
# individually turned on or off; they have a numeric prefix in order to
# produce a well-defined alphabetical order.
anchor "000.allowLoopback"
anchor "100.blockAll"
anchor "110.allowNets"
anchor "120.blockNets"
anchor "150.allowExcludedApps"
anchor "200.allowVPN"
anchor "250.blockIPv6"
anchor "290.allowDHCP"
anchor "300.allowLAN"
anchor "310.blockDNS"
anchor "350.allowHnsd"
anchor "400.allowPIA"
+5 -3
View File
@@ -1,5 +1,7 @@
#include <QtCore> #include <QtCore>
#include <QString> #include <QString>
#include <QJsonObject>
#include "../client/daemon/interfaceconfig.h"
class IpcInterface class IpcInterface
{ {
@@ -19,8 +21,8 @@ class IpcInterface
SLOT( void cleanUp() ); SLOT( void cleanUp() );
SLOT( void setLogsEnabled(bool enabled) ); SLOT( void setLogsEnabled(bool enabled) );
SLOT( bool copyWireguardConfig(const QString &sourcePath) ); SLOT( bool disableKillSwitch() );
SLOT( bool isWireguardRunning() ); SLOT( bool enablePeerTraffic( const QJsonObject &configStr) );
SLOT( bool isWireguardConfigExists(const QString &configPath) ); SLOT( bool enableKillSwitch( const QJsonObject &excludeAddr, int vpnAdapterIndex) );
}; };
+156 -48
View File
@@ -8,8 +8,18 @@
#include "router.h" #include "router.h"
#include "logger.h" #include "logger.h"
#include "../client/protocols/protocols_defs.h"
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#include "tapcontroller_win.h" #include "tapcontroller_win.h"
#include "../client/platforms/windows/daemon/windowsfirewall.h"
#endif
#ifdef Q_OS_LINUX
#include "../client/platforms/linux/daemon/linuxfirewall.h"
#endif
#ifdef Q_OS_MACOS
#include "../client/platforms/macos/daemon/macosfirewall.h"
#endif #endif
IpcServer::IpcServer(QObject *parent): IpcServer::IpcServer(QObject *parent):
@@ -22,15 +32,14 @@ int IpcServer::createPrivilegedProcess()
qDebug() << "IpcServer::createPrivilegedProcess"; qDebug() << "IpcServer::createPrivilegedProcess";
#endif #endif
#ifdef Q_OS_WIN
WindowsFirewall::instance()->init();
#endif
m_localpid++; m_localpid++;
ProcessDescriptor pd(this); ProcessDescriptor pd(this);
// pd.serverNode->setHostUrl(QUrl(amnezia::getIpcProcessUrl(m_localpid)));
// pd.serverNode->enableRemoting(pd.ipcProcess.data());
//pd.localServer = QSharedPointer<QLocalServer>(new QLocalServer(this));
pd.localServer->setSocketOptions(QLocalServer::WorldAccessOption); pd.localServer->setSocketOptions(QLocalServer::WorldAccessOption);
if (!pd.localServer->listen(amnezia::getIpcProcessUrl(m_localpid))) { if (!pd.localServer->listen(amnezia::getIpcProcessUrl(m_localpid))) {
@@ -165,61 +174,160 @@ void IpcServer::setLogsEnabled(bool enabled)
} }
} }
bool IpcServer::copyWireguardConfig(const QString &sourcePath)
bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIndex)
{ {
#ifdef MZ_DEBUG #ifdef Q_OS_WIN
qDebug() << "IpcServer::copyWireguardConfig"; return WindowsFirewall::instance()->enableKillSwitch(vpnAdapterIndex);
#endif #endif
#ifdef Q_OS_LINUX #if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
const QString wireguardConfigPath = "/etc/wireguard/wg99.conf"; int splitTunnelType = configStr.value("splitTunnelType").toInt();
if (QFile::exists(wireguardConfigPath)) QJsonArray splitTunnelSites = configStr.value("splitTunnelSites").toArray();
bool blockAll = 0;
bool allowNets = 0;
bool blockNets = 0;
QStringList allownets;
QStringList blocknets;
if (splitTunnelType == 0)
{ {
QFile::remove(wireguardConfigPath); blockAll = true;
allowNets = true;
allownets.append(configStr.value(amnezia::config_key::hostName).toString());
} else if (splitTunnelType == 1)
{
blockNets = true;
for (auto v : splitTunnelSites) {
blocknets.append(v.toString());
}
} else if (splitTunnelType == 2) {
blockAll = true;
allowNets = true;
allownets.append(configStr.value(amnezia::config_key::hostName).toString());
for (auto v : splitTunnelSites) {
allownets.append(v.toString());
}
} }
if (!QFile::copy(sourcePath, wireguardConfigPath)) {
qDebug() << "WireguardProtocol::WireguardProtocol error occurred while copying wireguard config:";
return false;
}
return true;
#else
return false;
#endif
}
bool IpcServer::isWireguardRunning()
{
#ifdef MZ_DEBUG
qDebug() << "IpcServer::isWireguardRunning";
#endif #endif
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
QProcess checkWireguardStatusProcess; // double-check + ensure our firewall is installed and enabled
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true);
connect(&checkWireguardStatusProcess, &QProcess::errorOccurred, this, [](QProcess::ProcessError error) { LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), blockAll);
qDebug() << "WireguardProtocol::WireguardProtocol error occurred while checking wireguard status: " << error; LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), allowNets);
}); LinuxFirewall::updateAllowNets(allownets);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("120.blockNets"), blockAll);
checkWireguardStatusProcess.setProgram("/bin/wg"); LinuxFirewall::updateBlockNets(blocknets);
checkWireguardStatusProcess.setArguments(QStringList{"show"}); LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("200.allowVPN"), true);
checkWireguardStatusProcess.start(); LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true);
checkWireguardStatusProcess.waitForFinished(10000); LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true);
QString output = checkWireguardStatusProcess.readAllStandardOutput(); LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true);
if (!output.isEmpty()) { QStringList dnsServers;
return true; dnsServers.append(configStr.value(amnezia::config_key::dns1).toString());
} dnsServers.append(configStr.value(amnezia::config_key::dns2).toString());
return false; dnsServers.append("127.0.0.1");
#else dnsServers.append("127.0.0.53");
return false; LinuxFirewall::updateDNSServers(dnsServers);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true);
#endif #endif
#ifdef Q_OS_MACOS
// double-check + ensure our firewall is installed and enabled. This is necessary as
// other software may disable pfctl before re-enabling with their own rules (e.g other VPNs)
if (!MacOSFirewall::isInstalled()) MacOSFirewall::install();
MacOSFirewall::ensureRootAnchorPriority();
MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true);
MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), blockAll);
MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), allowNets);
MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), allowNets,
QStringLiteral("allownets"), allownets);
MacOSFirewall::setAnchorEnabled(QStringLiteral("120.blockNets"), blockNets);
MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), blockNets,
QStringLiteral("blocknets"), blocknets);
MacOSFirewall::setAnchorEnabled(QStringLiteral("200.allowVPN"), true);
MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true);
MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), true);
MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), true);
QStringList dnsServers;
dnsServers.append(configStr.value(amnezia::config_key::dns1).toString());
dnsServers.append(configStr.value(amnezia::config_key::dns2).toString());
MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), true);
MacOSFirewall::setAnchorTable(QStringLiteral("310.blockDNS"), true, QStringLiteral("dnsaddr"), dnsServers);
#endif
return true;
} }
bool IpcServer::isWireguardConfigExists(const QString &configPath) bool IpcServer::disableKillSwitch()
{ {
#ifdef MZ_DEBUG #ifdef Q_OS_WIN
qDebug() << "IpcServer::isWireguardConfigExists"; return WindowsFirewall::instance()->disableKillSwitch();
#endif #endif
return QFileInfo::exists(configPath); #ifdef Q_OS_LINUX
LinuxFirewall::uninstall();
#endif
#ifdef Q_OS_MACOS
MacOSFirewall::uninstall();
#endif
return true;
}
bool IpcServer::enablePeerTraffic(const QJsonObject &configStr)
{
#ifdef Q_OS_WIN
InterfaceConfig config;
config.m_dnsServer = configStr.value(amnezia::config_key::dns1).toString();
config.m_serverPublicKey = "openvpn";
config.m_serverIpv4Gateway = configStr.value("vpnGateway").toString();
int splitTunnelType = configStr.value("splitTunnelType").toInt();
QJsonArray splitTunnelSites = configStr.value("splitTunnelSites").toArray();
qDebug() << "splitTunnelType " << splitTunnelType << "splitTunnelSites " << splitTunnelSites;
QStringList AllowedIPAddesses;
// Use APP split tunnel
if (splitTunnelType == 0 || splitTunnelType == 2) {
config.m_allowedIPAddressRanges.append(
IPAddress(QHostAddress("0.0.0.0"), 0));
config.m_allowedIPAddressRanges.append(
IPAddress(QHostAddress("::"), 0));
}
if (splitTunnelType == 1) {
for (auto v : splitTunnelSites) {
QString ipRange = v.toString();
qDebug() << "ipRange " << ipRange;
if (ipRange.split('/').size() > 1){
config.m_allowedIPAddressRanges.append(
IPAddress(QHostAddress(ipRange.split('/')[0]), atoi(ipRange.split('/')[1].toLocal8Bit())));
} else {
config.m_allowedIPAddressRanges.append(
IPAddress(QHostAddress(ipRange), 32));
}
}
}
config.m_excludedAddresses.append(configStr.value(amnezia::config_key::hostName).toString());
if (splitTunnelType == 2) {
for (auto v : splitTunnelSites) {
QString ipRange = v.toString();
config.m_excludedAddresses.append(ipRange);
}
}
return WindowsFirewall::instance()->enablePeerTraffic(config);
#endif
return true;
} }
+5 -3
View File
@@ -4,6 +4,8 @@
#include <QLocalServer> #include <QLocalServer>
#include <QObject> #include <QObject>
#include <QRemoteObjectNode> #include <QRemoteObjectNode>
#include <QJsonObject>
#include "../client/daemon/interfaceconfig.h"
#include "ipc.h" #include "ipc.h"
#include "ipcserverprocess.h" #include "ipcserverprocess.h"
@@ -25,9 +27,9 @@ public:
virtual QStringList getTapList() override; virtual QStringList getTapList() override;
virtual void cleanUp() override; virtual void cleanUp() override;
virtual void setLogsEnabled(bool enabled) override; virtual void setLogsEnabled(bool enabled) override;
virtual bool copyWireguardConfig(const QString &sourcePath) override; virtual bool enablePeerTraffic(const QJsonObject &configStr) override;
virtual bool isWireguardRunning() override; virtual bool enableKillSwitch(const QJsonObject &excludeAddr, int vpnAdapterIndex) override;
virtual bool isWireguardConfigExists(const QString &configPath) override; virtual bool disableKillSwitch() override;
private: private:
int m_localpid = 0; int m_localpid = 0;
+4
View File
@@ -182,6 +182,7 @@ if(APPLE)
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/macosdaemon.h ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/macosdaemon.h
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/macosroutemonitor.h ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/macosroutemonitor.h
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/wireguardutilsmacos.h ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/wireguardutilsmacos.h
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/macosfirewall.h
) )
set(SOURCES ${SOURCES} set(SOURCES ${SOURCES}
@@ -195,6 +196,7 @@ if(APPLE)
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/macosdaemon.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/macosdaemon.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/macosroutemonitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/macosroutemonitor.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/wireguardutilsmacos.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/wireguardutilsmacos.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/macosfirewall.cpp
) )
endif() endif()
@@ -211,6 +213,7 @@ if(LINUX)
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/dnsutilslinux.h ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/dnsutilslinux.h
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/wireguardutilslinux.h ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/wireguardutilslinux.h
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxroutemonitor.h ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxroutemonitor.h
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxfirewall.h
) )
set(SOURCES ${SOURCES} set(SOURCES ${SOURCES}
@@ -223,6 +226,7 @@ if(LINUX)
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxdaemon.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxdaemon.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/wireguardutilslinux.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/wireguardutilslinux.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxroutemonitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxroutemonitor.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxfirewall.cpp
) )
endif() endif()