Compare commits

..

3 Commits

Author SHA1 Message Date
vladimir.kuznetsov 4911aa5eaa Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into HEAD 2024-01-10 20:51:34 +07:00
vladimir.kuznetsov f18dbc1e15 remove unnecessary mutexes 2024-01-10 20:50:35 +07:00
vladimir.kuznetsov 9753c3feb6 Now value and setValue of secureQSettings are always called in the main thread 2024-01-06 10:48:29 +07:00
137 changed files with 2024 additions and 4085 deletions
+14 -32
View File
@@ -48,18 +48,14 @@ jobs:
export QIF_BIN_DIR=${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin export QIF_BIN_DIR=${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin
bash deploy/build_linux.sh bash deploy/build_linux.sh
- name: 'Pack installer'
run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin
- name: 'Upload installer artifact' - name: 'Upload installer artifact'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: AmneziaVPN_Linux_installer.tar name: AmneziaVPN_Linux_installer
path: deploy/AmneziaVPN_Linux_Installer.tar path: deploy/AmneziaVPN_Linux_Installer
retention-days: 7 retention-days: 7
- name: 'Upload unpacked artifact' - name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: AmneziaVPN_Linux_unpacked name: AmneziaVPN_Linux_unpacked
path: deploy/AppDir path: deploy/AppDir
@@ -114,14 +110,13 @@ jobs:
call deploy\\build_windows.bat call deploy\\build_windows.bat
- name: 'Upload installer artifact' - name: 'Upload installer artifact'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: AmneziaVPN_Windows_installer name: AmneziaVPN_Windows_installer
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.exe path: AmneziaVPN_x${{ env.BUILD_ARCH }}.exe
retention-days: 7 retention-days: 7
- name: 'Upload unpacked artifact' - name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: AmneziaVPN_Windows_unpacked name: AmneziaVPN_Windows_unpacked
path: deploy\\build_${{ env.BUILD_ARCH }}\\client\\Release path: deploy\\build_${{ env.BUILD_ARCH }}\\client\\Release
@@ -205,7 +200,7 @@ jobs:
IOS_NE_PROVISIONING_PROFILE: ${{ secrets.IOS_NE_PROVISIONING_PROFILE }} IOS_NE_PROVISIONING_PROFILE: ${{ secrets.IOS_NE_PROVISIONING_PROFILE }}
# - name: 'Upload appstore .ipa and dSYMs to artifacts' # - name: 'Upload appstore .ipa and dSYMs to artifacts'
# uses: actions/upload-artifact@v4 # uses: actions/upload-artifact@v3
# with: # with:
# name: app-store ipa & dsyms # name: app-store ipa & dsyms
# path: | # path: |
@@ -260,14 +255,13 @@ jobs:
bash deploy/build_macos.sh bash deploy/build_macos.sh
- name: 'Upload installer artifact' - name: 'Upload installer artifact'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: AmneziaVPN_MacOS_installer name: AmneziaVPN_MacOS_installer
path: AmneziaVPN.dmg path: AmneziaVPN.dmg
retention-days: 7 retention-days: 7
- name: 'Upload unpacked artifact' - name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: AmneziaVPN_MacOS_unpacked name: AmneziaVPN_MacOS_unpacked
path: deploy/build/client/AmneziaVPN.app path: deploy/build/client/AmneziaVPN.app
@@ -381,44 +375,32 @@ jobs:
ANDROID_KEYSTORE_KEY_ALIAS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_ALIAS }} ANDROID_KEYSTORE_KEY_ALIAS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_ALIAS }}
ANDROID_KEYSTORE_KEY_PASS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_PASS }} ANDROID_KEYSTORE_KEY_PASS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_PASS }}
shell: bash shell: bash
run: ./deploy/build_android.sh --aab --apk all --build-platform ${{ env.ANDROID_BUILD_PLATFORM }} run: ./deploy/build_android.sh --apk all --build-platform ${{ env.ANDROID_BUILD_PLATFORM }}
- name: 'Upload x86_64 apk' - name: 'Upload x86_64 apk'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: AmneziaVPN-android-x86_64 name: AmneziaVPN-android-x86_64
path: deploy/build/AmneziaVPN-x86_64-release.apk path: deploy/build/AmneziaVPN-x86_64-release.apk
compression-level: 0
retention-days: 7 retention-days: 7
- name: 'Upload x86 apk' - name: 'Upload x86 apk'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: AmneziaVPN-android-x86 name: AmneziaVPN-android-x86
path: deploy/build/AmneziaVPN-x86-release.apk path: deploy/build/AmneziaVPN-x86-release.apk
compression-level: 0
retention-days: 7 retention-days: 7
- name: 'Upload arm64-v8a apk' - name: 'Upload arm64-v8a apk'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: AmneziaVPN-android-arm64-v8a name: AmneziaVPN-android-arm64-v8a
path: deploy/build/AmneziaVPN-arm64-v8a-release.apk path: deploy/build/AmneziaVPN-arm64-v8a-release.apk
compression-level: 0
retention-days: 7 retention-days: 7
- name: 'Upload armeabi-v7a apk' - name: 'Upload armeabi-v7a apk'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: AmneziaVPN-android-armeabi-v7a name: AmneziaVPN-android-armeabi-v7a
path: deploy/build/AmneziaVPN-armeabi-v7a-release.apk path: deploy/build/AmneziaVPN-armeabi-v7a-release.apk
compression-level: 0
retention-days: 7
- name: 'Upload aab'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN-android
path: deploy/build/AmneziaVPN-release.aab
compression-level: 0
retention-days: 7 retention-days: 7
+3 -3
View File
@@ -22,6 +22,6 @@
[submodule "client/3rd-prebuilt"] [submodule "client/3rd-prebuilt"]
path = client/3rd-prebuilt path = client/3rd-prebuilt
url = https://github.com/amnezia-vpn/3rd-prebuilt url = https://github.com/amnezia-vpn/3rd-prebuilt
[submodule "client/3rd/amneziawg-apple"] [submodule "client/3rd/awg-apple"]
path = client/3rd/amneziawg-apple path = client/3rd/awg-apple
url = https://github.com/amnezia-vpn/amneziawg-apple url = https://github.com/amnezia-vpn/awg-apple
+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.3.0.0 project(${PROJECT} VERSION 4.1.0.1
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 44) set(APP_ANDROID_VERSION_CODE 39)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux") set(MZ_PLATFORM_NAME "linux")
+4 -12
View File
@@ -18,6 +18,7 @@ Amnezia is an open-source VPN client, with a key feature that enables you to dep
[https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
[https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English) [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
[https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Telegram support channel (Russian) [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Telegram support channel (Russian)
[https://signal.group/...](https://signal.group/#CjQKIB2gUf8QH_IXnOJMGQWMDjYz9cNfmRQipGWLFiIgc4MwEhAKBONrSiWHvoUFbbD0xwdh) - Signal channel
## Tech ## Tech
@@ -35,7 +36,7 @@ AmneziaVPN uses a number of open source projects to work:
Make sure to pull all submodules after checking out the repo. Make sure to pull all submodules after checking out the repo.
```bash ```bash
git submodule update --init --recursive git submodule update --init
``` ```
## Development ## Development
@@ -49,15 +50,7 @@ Look deploy folder for build scripts.
1. First, make sure you have [XCode](https://developer.apple.com/xcode/) installed, at least version 14 or higher. 1. First, make sure you have [XCode](https://developer.apple.com/xcode/) installed, at least version 14 or higher.
2. We use QT to generate the XCode project. we need QT version 6.6.1. Install QT for macos in [here](https://doc.qt.io/qt-6/macos.html) or [QT Online Installer](https://www.qt.io/download-open-source). Required modules: 2. We use QT to generate the XCode project. we need QT version 6.4. Install QT for macos in [here](https://doc.qt.io/qt-6/macos.html)
- macOS
- iOS
- Qt 5 Compatibility Module
- Qt Shader Tools
- Additional Libraries:
- Qt Image Formats
- Qt Multimedia
- Qt Remote Objects
3. Install cmake is require. We recommend cmake version 3.25. You can install cmake in [here](https://cmake.org/download/) 3. Install cmake is require. We recommend cmake version 3.25. You can install cmake in [here](https://cmake.org/download/)
@@ -73,11 +66,10 @@ gomobile init
5. Build project 5. Build project
```bash ```bash
export QT_BIN_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/ios/bin" export QT_BIN_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/ios/bin"
export QT_MACOS_ROOT_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/macos"
export QT_IOS_BIN=$QT_BIN_DIR export QT_IOS_BIN=$QT_BIN_DIR
export PATH=$PATH:~/go/bin export PATH=$PATH:~/go/bin
mkdir build-ios mkdir build-ios
$QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR $QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_BIN_DIR
``` ```
Replace PATH-TO-QT-FOLDER and QT-VERSION to your environment Replace PATH-TO-QT-FOLDER and QT-VERSION to your environment
Vendored Submodule
+1
Submodule client/3rd/awg-apple added at 233eda6760
+14 -23
View File
@@ -91,13 +91,6 @@ void AmneziaApplication::init()
initControllers(); initControllers();
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
if(!AndroidController::initLogging()) {
qFatal("Android logging initialization failed");
}
AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs());
connect(m_settings.get(), &Settings::saveLogsChanged,
AndroidController::instance(), &AndroidController::setSaveLogs);
connect(AndroidController::instance(), &AndroidController::initConnectionState, this, connect(AndroidController::instance(), &AndroidController::initConnectionState, this,
[this](Vpn::ConnectionState state) { [this](Vpn::ConnectionState state) {
m_connectionController->onConnectionStateChanged(state); m_connectionController->onConnectionStateChanged(state);
@@ -105,7 +98,10 @@ void AmneziaApplication::init()
m_vpnConnection->restoreConnection(); m_vpnConnection->restoreConnection();
}); });
if (!AndroidController::instance()->initialize()) { if (!AndroidController::instance()->initialize()) {
qFatal("Android controller initialization failed"); qCritical() << QString("Init failed");
if (m_vpnConnection)
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Error);
return;
} }
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) { connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) {
@@ -147,13 +143,11 @@ void AmneziaApplication::init()
m_engine->load(url); m_engine->load(url);
m_systemController->setQmlRoot(m_engine->rootObjects().value(0)); m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
#ifndef Q_OS_ANDROID
if (m_settings->isSaveLogs()) { if (m_settings->isSaveLogs()) {
if (!Logger::init()) { if (!Logger::init()) {
qWarning() << "Initialization of debug subsystem failed"; qWarning() << "Initialization of debug subsystem failed";
} }
} }
#endif
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
if (m_parser.isSet("a")) if (m_parser.isSet("a"))
@@ -174,19 +168,16 @@ void AmneziaApplication::init()
} }
#endif #endif
// Android TextArea clipboard workaround // Android TextField clipboard workaround
// Text from TextArea always has "text/html" mime-type: // https://bugreports.qt.io/browse/QTBUG-113461
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1865
// Next, html is created for this mime-type:
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1885
// And this html goes to the Androids clipboard, i.e. text from TextArea is always copied as richText:
// /qt/6.6.1/Src/qtbase/src/plugins/platforms/android/androidjniclipboard.cpp:46
// So we catch all the copies to the clipboard and clear them from "text/html"
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
connect(QGuiApplication::clipboard(), &QClipboard::dataChanged, []() { QObject::connect(qApp, &QGuiApplication::applicationStateChanged, [](Qt::ApplicationState state) {
auto clipboard = QGuiApplication::clipboard(); if (state == Qt::ApplicationActive) {
if (clipboard->mimeData()->hasHtml()) { if (qApp->clipboard()->mimeData()->formats().contains("text/html")) {
clipboard->setText(clipboard->text()); QTextDocument doc;
doc.setHtml(qApp->clipboard()->mimeData()->html());
qApp->clipboard()->setText(doc.toPlainText());
}
} }
}); });
#endif #endif
@@ -370,7 +361,7 @@ void AmneziaApplication::initControllers()
m_settings, m_configurator)); m_settings, m_configurator));
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get()); m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_settings)); m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_settings));
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) { if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); }); QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });
+4 -9
View File
@@ -1,8 +1,6 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- Leave package attribute for androiddeployqt --> <!-- Leave package attribute for androiddeployqt -->
<manifest <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.amnezia.vpn" package="org.amnezia.vpn"
android:versionName="-- %%INSERT_VERSION_NAME%% --" android:versionName="-- %%INSERT_VERSION_NAME%% --"
android:versionCode="-- %%INSERT_VERSION_CODE%% --" android:versionCode="-- %%INSERT_VERSION_CODE%% --"
@@ -24,6 +22,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- Enable when VPN-per-app mode will be implemented --> <!-- Enable when VPN-per-app mode will be implemented -->
<!-- <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/> --> <!-- <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/> -->
@@ -33,17 +32,13 @@
android:label="-- %%INSERT_APP_NAME%% --" android:label="-- %%INSERT_APP_NAME%% --"
android:icon="@mipmap/icon" android:icon="@mipmap/icon"
android:roundIcon="@mipmap/icon_round" android:roundIcon="@mipmap/icon_round"
android:theme="@style/NoActionBar" android:theme="@style/NoActionBar">
android:fullBackupContent="@xml/backup_content"
android:dataExtractionRules="@xml/data_extraction_rules"
tools:targetApi="s">
<activity <activity
android:name=".AmneziaActivity" android:name=".AmneziaActivity"
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc" |fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
android:launchMode="singleInstance" android:launchMode="singleInstance"
android:windowSoftInputMode="adjustResize"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
@@ -152,7 +147,7 @@
android:authorities="org.amnezia.vpn.qtprovider" android:authorities="org.amnezia.vpn.qtprovider"
android:exported="false" android:exported="false"
android:grantUriPermissions="true"> android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" /> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" />
</provider> </provider>
</application> </application>
</manifest> </manifest>
@@ -99,7 +99,7 @@ class AwgConfig private constructor(
fun setH3(h3: Long) = apply { this.h3 = h3 } fun setH3(h3: Long) = apply { this.h3 = h3 }
fun setH4(h4: Long) = apply { this.h4 = h4 } fun setH4(h4: Long) = apply { this.h4 = h4 }
override fun build(): AwgConfig = configBuild().run { AwgConfig(this@Builder) } override fun build(): AwgConfig = AwgConfig(this)
} }
companion object { companion object {
+1
View File
@@ -108,6 +108,7 @@ dependencies {
implementation(project(":cloak")) implementation(project(":cloak"))
implementation(libs.androidx.core) implementation(libs.androidx.core)
implementation(libs.androidx.activity) implementation(libs.androidx.activity)
implementation(libs.androidx.security.crypto)
implementation(libs.kotlinx.coroutines) implementation(libs.kotlinx.coroutines)
implementation(libs.bundles.androidx.camera) implementation(libs.bundles.androidx.camera)
implementation(libs.google.mlkit) implementation(libs.google.mlkit)
@@ -3,9 +3,6 @@ package org.amnezia.vpn.protocol.cloak
import android.util.Base64 import android.util.Base64
import net.openvpn.ovpn3.ClientAPI_Config import net.openvpn.ovpn3.ClientAPI_Config
import org.amnezia.vpn.protocol.openvpn.OpenVpn import org.amnezia.vpn.protocol.openvpn.OpenVpn
import org.amnezia.vpn.protocol.openvpn.OpenVpnConfig
import org.amnezia.vpn.util.net.InetNetwork
import org.amnezia.vpn.util.net.parseInetAddress
import org.json.JSONObject import org.json.JSONObject
/** /**
@@ -54,13 +51,6 @@ class Cloak : OpenVpn() {
return openVpnConfig return openVpnConfig
} }
override fun configPluggableTransport(configBuilder: OpenVpnConfig.Builder, config: JSONObject) {
// exclude remote server ip from vpn routes
val remoteServer = config.getString("hostName")
val remoteServerAddress = InetNetwork(parseInetAddress(remoteServer))
configBuilder.excludeRoute(remoteServerAddress)
}
private fun checkCloakJson(cloakConfigJson: JSONObject): JSONObject { private fun checkCloakJson(cloakConfigJson: JSONObject): JSONObject {
cloakConfigJson.put("NumConn", 1) cloakConfigJson.put("NumConn", 1)
cloakConfigJson.put("ProxyMethod", "openvpn") cloakConfigJson.put("ProxyMethod", "openvpn")
@@ -2,6 +2,7 @@ package org.amnezia.vpn.protocol.openvpn
import android.content.Context import android.content.Context
import android.net.VpnService.Builder import android.net.VpnService.Builder
import android.os.Build
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@@ -13,6 +14,7 @@ import org.amnezia.vpn.protocol.ProtocolState
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
import org.amnezia.vpn.protocol.Statistics import org.amnezia.vpn.protocol.Statistics
import org.amnezia.vpn.protocol.VpnStartException import org.amnezia.vpn.protocol.VpnStartException
import org.amnezia.vpn.util.net.InetNetwork
import org.amnezia.vpn.util.net.getLocalNetworks import org.amnezia.vpn.util.net.getLocalNetworks
import org.json.JSONObject import org.json.JSONObject
@@ -77,8 +79,16 @@ open class OpenVpn : Protocol() {
if (evalConfig.error) { if (evalConfig.error) {
throw BadConfigException("OpenVPN config parse error: ${evalConfig.message}") throw BadConfigException("OpenVPN config parse error: ${evalConfig.message}")
} }
configPluggableTransport(configBuilder, config) configBuilder.apply {
configBuilder.configSplitTunneling(config) // fix for split tunneling
// The exclude split tunneling OpenVpn configuration does not contain a default route.
// It is required for split tunneling in newer versions of Android.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
addRoute(InetNetwork("0.0.0.0", 0))
addRoute(InetNetwork("::", 0))
}
configSplitTunneling(config)
}
scope.launch { scope.launch {
val status = client.connect() val status = client.connect()
@@ -112,8 +122,6 @@ open class OpenVpn : Protocol() {
return openVpnConfig return openVpnConfig
} }
protected open fun configPluggableTransport(configBuilder: OpenVpnConfig.Builder, config: JSONObject) {}
private fun makeEstablish(vpnBuilder: Builder): (OpenVpnConfig.Builder) -> Int = { configBuilder -> private fun makeEstablish(vpnBuilder: Builder): (OpenVpnConfig.Builder) -> Int = { configBuilder ->
val openVpnConfig = configBuilder.build() val openVpnConfig = configBuilder.build()
buildVpnInterface(openVpnConfig, vpnBuilder) buildVpnInterface(openVpnConfig, vpnBuilder)
@@ -52,7 +52,7 @@ class OpenVpnClient(
// Callback to construct a new tun builder // Callback to construct a new tun builder
// Should be called first. // Should be called first.
override fun tun_builder_new(): Boolean { override fun tun_builder_new(): Boolean {
Log.d(TAG, "tun_builder_new") Log.v(TAG, "tun_builder_new")
configBuilder.clearAddresses() configBuilder.clearAddresses()
return true return true
} }
@@ -60,7 +60,7 @@ class OpenVpnClient(
// Callback to set MTU of the VPN interface // Callback to set MTU of the VPN interface
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
override fun tun_builder_set_mtu(mtu: Int): Boolean { override fun tun_builder_set_mtu(mtu: Int): Boolean {
Log.d(TAG, "tun_builder_set_mtu: $mtu") Log.v(TAG, "tun_builder_set_mtu: $mtu")
configBuilder.setMtu(mtu) configBuilder.setMtu(mtu)
return true return true
} }
@@ -71,7 +71,7 @@ class OpenVpnClient(
address: String, prefix_length: Int, address: String, prefix_length: Int,
gateway: String, ipv6: Boolean, net30: Boolean gateway: String, ipv6: Boolean, net30: Boolean
): Boolean { ): Boolean {
Log.d(TAG, "tun_builder_add_address: $address, $prefix_length, $gateway, $ipv6, $net30") Log.v(TAG, "tun_builder_add_address: $address, $prefix_length, $gateway, $ipv6, $net30")
configBuilder.addAddress(InetNetwork(address, prefix_length)) configBuilder.addAddress(InetNetwork(address, prefix_length))
return true return true
} }
@@ -80,7 +80,7 @@ class OpenVpnClient(
// May be called more than once per tun_builder session // May be called more than once per tun_builder session
// metric is optional and should be ignored if < 0 // metric is optional and should be ignored if < 0
override fun tun_builder_add_route(address: String, prefix_length: Int, metric: Int, ipv6: Boolean): Boolean { override fun tun_builder_add_route(address: String, prefix_length: Int, metric: Int, ipv6: Boolean): Boolean {
Log.d(TAG, "tun_builder_add_route: $address, $prefix_length, $metric, $ipv6") Log.v(TAG, "tun_builder_add_route: $address, $prefix_length, $metric, $ipv6")
if (address == "remote_host") return false if (address == "remote_host") return false
configBuilder.addRoute(InetNetwork(address, prefix_length)) configBuilder.addRoute(InetNetwork(address, prefix_length))
return true return true
@@ -90,8 +90,10 @@ class OpenVpnClient(
// May be called more than once per tun_builder session // May be called more than once per tun_builder session
// metric is optional and should be ignored if < 0 // metric is optional and should be ignored if < 0
override fun tun_builder_exclude_route(address: String, prefix_length: Int, metric: Int, ipv6: Boolean): Boolean { override fun tun_builder_exclude_route(address: String, prefix_length: Int, metric: Int, ipv6: Boolean): Boolean {
Log.d(TAG, "tun_builder_exclude_route: $address, $prefix_length, $metric, $ipv6") Log.v(TAG, "tun_builder_exclude_route: $address, $prefix_length, $metric, $ipv6")
configBuilder.excludeRoute(InetNetwork(address, prefix_length)) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
configBuilder.excludeRoute(InetNetwork(address, prefix_length))
}
return true return true
} }
@@ -102,7 +104,7 @@ class OpenVpnClient(
// domain should be routed. // domain should be routed.
// Guaranteed to be called after tun_builder_reroute_gw. // Guaranteed to be called after tun_builder_reroute_gw.
override fun tun_builder_add_dns_server(address: String, ipv6: Boolean): Boolean { override fun tun_builder_add_dns_server(address: String, ipv6: Boolean): Boolean {
Log.d(TAG, "tun_builder_add_dns_server: $address, $ipv6") Log.v(TAG, "tun_builder_add_dns_server: $address, $ipv6")
configBuilder.addDnsServer(parseInetAddress(address)) configBuilder.addDnsServer(parseInetAddress(address))
return true return true
} }
@@ -117,28 +119,28 @@ class OpenVpnClient(
// ignored for that family // ignored for that family
// See also Android's VPNService.Builder.allowFamily method // See also Android's VPNService.Builder.allowFamily method
/* override fun tun_builder_set_allow_family(af: Int, allow: Boolean): Boolean { /* override fun tun_builder_set_allow_family(af: Int, allow: Boolean): Boolean {
Log.d(TAG, "tun_builder_set_allow_family: $af, $allow") Log.v(TAG, "tun_builder_set_allow_family: $af, $allow")
return true return true
} */ } */
// Callback to set address of remote server // Callback to set address of remote server
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
override fun tun_builder_set_remote_address(address: String, ipv6: Boolean): Boolean { override fun tun_builder_set_remote_address(address: String, ipv6: Boolean): Boolean {
Log.d(TAG, "tun_builder_set_remote_address: $address, $ipv6") Log.v(TAG, "tun_builder_set_remote_address: $address, $ipv6")
return true return true
} }
// Optional callback that indicates OSI layer, should be 2 or 3. // Optional callback that indicates OSI layer, should be 2 or 3.
// Defaults to 3. // Defaults to 3.
override fun tun_builder_set_layer(layer: Int): Boolean { override fun tun_builder_set_layer(layer: Int): Boolean {
Log.d(TAG, "tun_builder_set_layer: $layer") Log.v(TAG, "tun_builder_set_layer: $layer")
return layer == 3 return layer == 3
} }
// Callback to set the session name // Callback to set the session name
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
override fun tun_builder_set_session_name(name: String): Boolean { override fun tun_builder_set_session_name(name: String): Boolean {
Log.d(TAG, "tun_builder_set_session_name: $name") Log.v(TAG, "tun_builder_set_session_name: $name")
return true return true
} }
@@ -147,7 +149,7 @@ class OpenVpnClient(
// if the tunnel could not be established. // if the tunnel could not be established.
// Always called last after tun_builder session has been configured. // Always called last after tun_builder session has been configured.
override fun tun_builder_establish(): Int { override fun tun_builder_establish(): Int {
Log.d(TAG, "tun_builder_establish") Log.v(TAG, "tun_builder_establish")
return establish(configBuilder) return establish(configBuilder)
} }
@@ -157,7 +159,7 @@ class OpenVpnClient(
// flags are defined in RGWFlags (rgwflags.hpp). // flags are defined in RGWFlags (rgwflags.hpp).
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
override fun tun_builder_reroute_gw(ipv4: Boolean, ipv6: Boolean, flags: Long): Boolean { override fun tun_builder_reroute_gw(ipv4: Boolean, ipv6: Boolean, flags: Long): Boolean {
Log.d(TAG, "tun_builder_reroute_gw: $ipv4, $ipv6, $flags") Log.v(TAG, "tun_builder_reroute_gw: $ipv4, $ipv6, $flags")
if ((flags and EMULATED_EXCLUDE_ROUTES.toLong()) != 0L) return true if ((flags and EMULATED_EXCLUDE_ROUTES.toLong()) != 0L) return true
if (ipv4) { if (ipv4) {
configBuilder.addRoute(InetNetwork("0.0.0.0", 0)) configBuilder.addRoute(InetNetwork("0.0.0.0", 0))
@@ -174,7 +176,7 @@ class OpenVpnClient(
// reroute_dns parameter. // reroute_dns parameter.
// Guaranteed to be called after tun_builder_reroute_gw. // Guaranteed to be called after tun_builder_reroute_gw.
override fun tun_builder_add_search_domain(domain: String): Boolean { override fun tun_builder_add_search_domain(domain: String): Boolean {
Log.d(TAG, "tun_builder_add_search_domain: $domain") Log.v(TAG, "tun_builder_add_search_domain: $domain")
configBuilder.setSearchDomain(domain) configBuilder.setSearchDomain(domain)
return true return true
} }
@@ -182,7 +184,7 @@ class OpenVpnClient(
// Callback to set the HTTP proxy // Callback to set the HTTP proxy
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
override fun tun_builder_set_proxy_http(host: String, port: Int): Boolean { override fun tun_builder_set_proxy_http(host: String, port: Int): Boolean {
Log.d(TAG, "tun_builder_set_proxy_http: $host, $port") Log.v(TAG, "tun_builder_set_proxy_http: $host, $port")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
try { try {
configBuilder.setHttpProxy(ProxyInfo.buildDirectProxy(host, port)) configBuilder.setHttpProxy(ProxyInfo.buildDirectProxy(host, port))
@@ -197,7 +199,7 @@ class OpenVpnClient(
// Callback to set the HTTPS proxy // Callback to set the HTTPS proxy
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
override fun tun_builder_set_proxy_https(host: String, port: Int): Boolean { override fun tun_builder_set_proxy_https(host: String, port: Int): Boolean {
Log.d(TAG, "tun_builder_set_proxy_https: $host, $port") Log.v(TAG, "tun_builder_set_proxy_https: $host, $port")
return false return false
} }
@@ -206,7 +208,7 @@ class OpenVpnClient(
// to exclude them from the VPN network are generated // to exclude them from the VPN network are generated
// This should be a list of CIDR networks (e.g. 192.168.0.0/24) // This should be a list of CIDR networks (e.g. 192.168.0.0/24)
override fun tun_builder_get_local_networks(ipv6: Boolean): ClientAPI_StringVec { override fun tun_builder_get_local_networks(ipv6: Boolean): ClientAPI_StringVec {
Log.d(TAG, "tun_builder_get_local_networks: $ipv6") Log.v(TAG, "tun_builder_get_local_networks: $ipv6")
val networks = ClientAPI_StringVec() val networks = ClientAPI_StringVec()
for (address in getLocalNetworks(ipv6)) { for (address in getLocalNetworks(ipv6)) {
networks.add(address.toString()) networks.add(address.toString())
@@ -220,21 +222,21 @@ class OpenVpnClient(
// tun_builder_reroute_gw. Route metric is ignored // tun_builder_reroute_gw. Route metric is ignored
// if < 0. // if < 0.
/* override fun tun_builder_set_route_metric_default(metric: Int): Boolean { /* override fun tun_builder_set_route_metric_default(metric: Int): Boolean {
Log.d(TAG, "tun_builder_set_route_metric_default: $metric") Log.v(TAG, "tun_builder_set_route_metric_default: $metric")
return super.tun_builder_set_route_metric_default(metric) return super.tun_builder_set_route_metric_default(metric)
} */ } */
// Callback to add a host which should bypass the proxy // Callback to add a host which should bypass the proxy
// May be called more than once per tun_builder session // May be called more than once per tun_builder session
/* override fun tun_builder_add_proxy_bypass(bypass_host: String): Boolean { /* override fun tun_builder_add_proxy_bypass(bypass_host: String): Boolean {
Log.d(TAG, "tun_builder_add_proxy_bypass: $bypass_host") Log.v(TAG, "tun_builder_add_proxy_bypass: $bypass_host")
return super.tun_builder_add_proxy_bypass(bypass_host) return super.tun_builder_add_proxy_bypass(bypass_host)
} */ } */
// Callback to set the proxy "Auto Config URL" // Callback to set the proxy "Auto Config URL"
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
/* override fun tun_builder_set_proxy_auto_config_url(url: String): Boolean { /* override fun tun_builder_set_proxy_auto_config_url(url: String): Boolean {
Log.d(TAG, "tun_builder_set_proxy_auto_config_url: $url") Log.v(TAG, "tun_builder_set_proxy_auto_config_url: $url")
return super.tun_builder_set_proxy_auto_config_url(url) return super.tun_builder_set_proxy_auto_config_url(url)
} */ } */
@@ -243,7 +245,7 @@ class OpenVpnClient(
// May be called more than once per tun_builder session. // May be called more than once per tun_builder session.
// Guaranteed to be called after tun_builder_reroute_gw. // Guaranteed to be called after tun_builder_reroute_gw.
/* override fun tun_builder_add_wins_server(address: String): Boolean { /* override fun tun_builder_add_wins_server(address: String): Boolean {
Log.d(TAG, "tun_builder_add_wins_server: $address") Log.v(TAG, "tun_builder_add_wins_server: $address")
return super.tun_builder_add_wins_server(address) return super.tun_builder_add_wins_server(address)
} */ } */
@@ -252,7 +254,7 @@ class OpenVpnClient(
// set the "Connection-specific DNS Suffix" property on // set the "Connection-specific DNS Suffix" property on
// the TAP driver. // the TAP driver.
/* override fun tun_builder_set_adapter_domain_suffix(name: String): Boolean { /* override fun tun_builder_set_adapter_domain_suffix(name: String): Boolean {
Log.d(TAG, "tun_builder_set_adapter_domain_suffix: $name") Log.v(TAG, "tun_builder_set_adapter_domain_suffix: $name")
return super.tun_builder_set_adapter_domain_suffix(name) return super.tun_builder_set_adapter_domain_suffix(name)
} */ } */
@@ -264,13 +266,13 @@ class OpenVpnClient(
// tun_builder_establish_lite() will be called. Otherwise, // tun_builder_establish_lite() will be called. Otherwise,
// tun_builder_establish() will be called. // tun_builder_establish() will be called.
/* override fun tun_builder_persist(): Boolean { /* override fun tun_builder_persist(): Boolean {
Log.d(TAG, "tun_builder_persist") Log.v(TAG, "tun_builder_persist")
return super.tun_builder_persist() return super.tun_builder_persist()
} */ } */
// Indicates a reconnection with persisted tun state. // Indicates a reconnection with persisted tun state.
/* override fun tun_builder_establish_lite() { /* override fun tun_builder_establish_lite() {
Log.d(TAG, "tun_builder_establish_lite") Log.v(TAG, "tun_builder_establish_lite")
super.tun_builder_establish_lite() super.tun_builder_establish_lite()
} */ } */
@@ -278,7 +280,7 @@ class OpenVpnClient(
// If disconnect == true, then the teardown is occurring // If disconnect == true, then the teardown is occurring
// prior to final disconnect. // prior to final disconnect.
/* override fun tun_builder_teardown(disconnect: Boolean) { /* override fun tun_builder_teardown(disconnect: Boolean) {
Log.d(TAG, "tun_builder_teardown: $disconnect") Log.v(TAG, "tun_builder_teardown: $disconnect")
super.tun_builder_teardown(disconnect) super.tun_builder_teardown(disconnect)
} */ } */
@@ -288,7 +290,7 @@ class OpenVpnClient(
// Parse OpenVPN configuration file. // Parse OpenVPN configuration file.
override fun eval_config(arg0: ClientAPI_Config): ClientAPI_EvalConfig { override fun eval_config(arg0: ClientAPI_Config): ClientAPI_EvalConfig {
Log.d(TAG, "eval_config") Log.v(TAG, "eval_config")
return super.eval_config(arg0) return super.eval_config(arg0)
} }
@@ -297,7 +299,7 @@ class OpenVpnClient(
// to event() and log() functions. Make sure to call eval_config() // to event() and log() functions. Make sure to call eval_config()
// and possibly provide_creds() as well before this function. // and possibly provide_creds() as well before this function.
override fun connect(): ClientAPI_Status { override fun connect(): ClientAPI_Status {
Log.d(TAG, "connect") Log.v(TAG, "connect")
return super.connect() return super.connect()
} }
@@ -305,7 +307,7 @@ class OpenVpnClient(
// Will be called from the thread executing connect(). // Will be called from the thread executing connect().
// The remote and ipv6 are the remote host this socket will connect to // The remote and ipv6 are the remote host this socket will connect to
override fun socket_protect(socket: Int, remote: String, ipv6: Boolean): Boolean { override fun socket_protect(socket: Int, remote: String, ipv6: Boolean): Boolean {
Log.d(TAG, "socket_protect: $socket, $remote, $ipv6") Log.v(TAG, "socket_protect: $socket, $remote, $ipv6")
return protect(socket) return protect(socket)
} }
@@ -313,7 +315,7 @@ class OpenVpnClient(
// May be called asynchronously from a different thread // May be called asynchronously from a different thread
// when connect() is running. // when connect() is running.
override fun stop() { override fun stop() {
Log.d(TAG, "stop") Log.v(TAG, "stop")
super.stop() super.stop()
} }
@@ -321,21 +323,21 @@ class OpenVpnClient(
// when network is down. May be called from a different thread // when network is down. May be called from a different thread
// when connect() is running. // when connect() is running.
override fun pause(reason: String) { override fun pause(reason: String) {
Log.d(TAG, "pause: $reason") Log.v(TAG, "pause: $reason")
super.pause(reason) super.pause(reason)
} }
// Resume the client after it has been paused. May be called from a // Resume the client after it has been paused. May be called from a
// different thread when connect() is running. // different thread when connect() is running.
override fun resume() { override fun resume() {
Log.d(TAG, "resume") Log.v(TAG, "resume")
super.resume() super.resume()
} }
// Do a disconnect/reconnect cycle n seconds from now. May be called // Do a disconnect/reconnect cycle n seconds from now. May be called
// from a different thread when connect() is running. // from a different thread when connect() is running.
override fun reconnect(seconds: Int) { override fun reconnect(seconds: Int) {
Log.d(TAG, "reconnect: $seconds") Log.v(TAG, "reconnect")
super.reconnect(seconds) super.reconnect(seconds)
} }
@@ -344,14 +346,14 @@ class OpenVpnClient(
// CONNECTION_TIMEOUT event. If true, the core will enter a PAUSE // CONNECTION_TIMEOUT event. If true, the core will enter a PAUSE
// state. // state.
override fun pause_on_connection_timeout(): Boolean { override fun pause_on_connection_timeout(): Boolean {
Log.d(TAG, "pause_on_connection_timeout") Log.v(TAG, "pause_on_connection_timeout")
return false return false
} }
// Return information about the most recent connection. Should be called // Return information about the most recent connection. Should be called
// after an event of type "CONNECTED". // after an event of type "CONNECTED".
/* override fun connection_info(): ClientAPI_ConnectionInfo { /* override fun connection_info(): ClientAPI_ConnectionInfo {
Log.d(TAG, "connection_info") Log.v(TAG, "connection_info")
return super.connection_info() return super.connection_info()
} */ } */
@@ -364,7 +366,7 @@ class OpenVpnClient(
override fun event(event: ClientAPI_Event) { override fun event(event: ClientAPI_Event) {
val name = event.name val name = event.name
val info = event.info val info = event.info
Log.d(TAG, "OpenVpn event: $name: $info") Log.v(TAG, "OpenVpn event: $name: $info")
when (name) { when (name) {
"COMPRESSION_ENABLED", "WARN" -> Log.w(TAG, "$name: $info") "COMPRESSION_ENABLED", "WARN" -> Log.w(TAG, "$name: $info")
"CONNECTED" -> state.value = CONNECTED "CONNECTED" -> state.value = CONNECTED
@@ -396,31 +398,31 @@ class OpenVpnClient(
// return transport stats only // return transport stats only
override fun transport_stats(): ClientAPI_TransportStats { override fun transport_stats(): ClientAPI_TransportStats {
Log.d(TAG, "transport_stats") Log.v(TAG, "transport_stats")
return super.transport_stats() return super.transport_stats()
} }
// return a stats value, index should be >= 0 and < stats_n() // return a stats value, index should be >= 0 and < stats_n()
/* override fun stats_value(index: Int): Long { /* override fun stats_value(index: Int): Long {
Log.d(TAG, "stats_value: $index") Log.v(TAG, "stats_value: $index")
return super.stats_value(index) return super.stats_value(index)
} */ } */
// return all stats in a bundle // return all stats in a bundle
/* override fun stats_bundle(): ClientAPI_LLVector { /* override fun stats_bundle(): ClientAPI_LLVector {
Log.d(TAG, "stats_bundle") Log.v(TAG, "stats_bundle")
return super.stats_bundle() return super.stats_bundle()
} */ } */
// return tun stats only // return tun stats only
/* override fun tun_stats(): ClientAPI_InterfaceStats { /* override fun tun_stats(): ClientAPI_InterfaceStats {
Log.d(TAG, "tun_stats") Log.v(TAG, "tun_stats")
return super.tun_stats() return super.tun_stats()
} */ } */
// post control channel message // post control channel message
/* override fun post_cc_msg(msg: String) { /* override fun post_cc_msg(msg: String) {
Log.d(TAG, "post_cc_msg: $msg") Log.v(TAG, "post_cc_msg: $msg")
super.post_cc_msg(msg) super.post_cc_msg(msg)
} */ } */
} }
@@ -11,7 +11,7 @@ class OpenVpnConfig private constructor(
class Builder : ProtocolConfig.Builder(false) { class Builder : ProtocolConfig.Builder(false) {
override var mtu: Int = OPENVPN_DEFAULT_MTU override var mtu: Int = OPENVPN_DEFAULT_MTU
override fun build(): OpenVpnConfig = configBuild().run { OpenVpnConfig(this@Builder) } override fun build(): OpenVpnConfig = OpenVpnConfig(this)
} }
companion object { companion object {
@@ -14,6 +14,8 @@ import java.util.zip.ZipFile
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import org.amnezia.vpn.util.Log import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.net.InetNetwork import org.amnezia.vpn.util.net.InetNetwork
import org.amnezia.vpn.util.net.IpRange
import org.amnezia.vpn.util.net.IpRangeSet
import org.json.JSONObject import org.json.JSONObject
private const val TAG = "Protocol" private const val TAG = "Protocol"
@@ -51,16 +53,40 @@ abstract class Protocol {
val splitTunnelType = config.optInt("splitTunnelType") val splitTunnelType = config.optInt("splitTunnelType")
if (splitTunnelType == SPLIT_TUNNEL_DISABLE) return if (splitTunnelType == SPLIT_TUNNEL_DISABLE) return
val splitTunnelSites = config.getJSONArray("splitTunnelSites") val splitTunnelSites = config.getJSONArray("splitTunnelSites")
val addressHandlerFunc = when (splitTunnelType) { when (splitTunnelType) {
SPLIT_TUNNEL_INCLUDE -> ::includeAddress SPLIT_TUNNEL_INCLUDE -> {
SPLIT_TUNNEL_EXCLUDE -> ::excludeAddress // remove default routes, if any
removeRoute(InetNetwork("0.0.0.0", 0))
removeRoute(InetNetwork("::", 0))
// add routes from config
for (i in 0 until splitTunnelSites.length()) {
val address = InetNetwork.parse(splitTunnelSites.getString(i))
addRoute(address)
}
}
else -> throw BadConfigException("Unexpected value of the 'splitTunnelType' parameter: $splitTunnelType") SPLIT_TUNNEL_EXCLUDE -> {
} if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// exclude routes from config
for (i in 0 until splitTunnelSites.length()) { for (i in 0 until splitTunnelSites.length()) {
val address = InetNetwork.parse(splitTunnelSites.getString(i)) val address = InetNetwork.parse(splitTunnelSites.getString(i))
addressHandlerFunc(address) excludeRoute(address)
}
} else {
// For older versions of Android, build a list of subnets without excluded addresses
val ipRangeSet = IpRangeSet()
ipRangeSet.remove(IpRange("127.0.0.0", 8))
for (i in 0 until splitTunnelSites.length()) {
val address = InetNetwork.parse(splitTunnelSites.getString(i))
ipRangeSet.remove(IpRange(address))
}
// remove default routes, if any
removeRoute(InetNetwork("0.0.0.0", 0))
removeRoute(InetNetwork("::", 0))
ipRangeSet.subnets().forEach(::addRoute)
addRoute(InetNetwork("2000::", 3))
}
}
} }
} }
@@ -5,8 +5,6 @@ import android.os.Build
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import java.net.InetAddress import java.net.InetAddress
import org.amnezia.vpn.util.net.InetNetwork import org.amnezia.vpn.util.net.InetNetwork
import org.amnezia.vpn.util.net.IpRange
import org.amnezia.vpn.util.net.IpRangeSet
open class ProtocolConfig protected constructor( open class ProtocolConfig protected constructor(
val addresses: Set<InetNetwork>, val addresses: Set<InetNetwork>,
@@ -14,8 +12,6 @@ open class ProtocolConfig protected constructor(
val searchDomain: String?, val searchDomain: String?,
val routes: Set<InetNetwork>, val routes: Set<InetNetwork>,
val excludedRoutes: Set<InetNetwork>, val excludedRoutes: Set<InetNetwork>,
val includedAddresses: Set<InetNetwork>,
val excludedAddresses: Set<InetNetwork>,
val excludedApplications: Set<String>, val excludedApplications: Set<String>,
val httpProxy: ProxyInfo?, val httpProxy: ProxyInfo?,
val allowAllAF: Boolean, val allowAllAF: Boolean,
@@ -29,8 +25,6 @@ open class ProtocolConfig protected constructor(
builder.searchDomain, builder.searchDomain,
builder.routes, builder.routes,
builder.excludedRoutes, builder.excludedRoutes,
builder.includedAddresses,
builder.excludedAddresses,
builder.excludedApplications, builder.excludedApplications,
builder.httpProxy, builder.httpProxy,
builder.allowAllAF, builder.allowAllAF,
@@ -43,8 +37,6 @@ open class ProtocolConfig protected constructor(
internal val dnsServers: MutableSet<InetAddress> = hashSetOf() internal val dnsServers: MutableSet<InetAddress> = hashSetOf()
internal val routes: MutableSet<InetNetwork> = hashSetOf() internal val routes: MutableSet<InetNetwork> = hashSetOf()
internal val excludedRoutes: MutableSet<InetNetwork> = hashSetOf() internal val excludedRoutes: MutableSet<InetNetwork> = hashSetOf()
internal val includedAddresses: MutableSet<InetNetwork> = hashSetOf()
internal val excludedAddresses: MutableSet<InetNetwork> = hashSetOf()
internal val excludedApplications: MutableSet<String> = hashSetOf() internal val excludedApplications: MutableSet<String> = hashSetOf()
internal var searchDomain: String? = null internal var searchDomain: String? = null
@@ -79,15 +71,12 @@ open class ProtocolConfig protected constructor(
fun removeRoute(route: InetNetwork) = apply { this.routes.remove(route) } fun removeRoute(route: InetNetwork) = apply { this.routes.remove(route) }
fun clearRoutes() = apply { this.routes.clear() } fun clearRoutes() = apply { this.routes.clear() }
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun excludeRoute(route: InetNetwork) = apply { this.excludedRoutes += route } fun excludeRoute(route: InetNetwork) = apply { this.excludedRoutes += route }
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun excludeRoutes(routes: Collection<InetNetwork>) = apply { this.excludedRoutes += routes } fun excludeRoutes(routes: Collection<InetNetwork>) = apply { this.excludedRoutes += routes }
fun includeAddress(addr: InetNetwork) = apply { this.includedAddresses += addr }
fun includeAddresses(addresses: Collection<InetNetwork>) = apply { this.includedAddresses += addresses }
fun excludeAddress(addr: InetNetwork) = apply { this.excludedAddresses += addr }
fun excludeAddresses(addresses: Collection<InetNetwork>) = apply { this.excludedAddresses += addresses }
fun excludeApplication(application: String) = apply { this.excludedApplications += application } fun excludeApplication(application: String) = apply { this.excludedApplications += application }
fun excludeApplications(applications: Collection<String>) = apply { this.excludedApplications += applications } fun excludeApplications(applications: Collection<String>) = apply { this.excludedApplications += applications }
@@ -102,49 +91,6 @@ open class ProtocolConfig protected constructor(
fun setMtu(mtu: Int) = apply { this.mtu = mtu } fun setMtu(mtu: Int) = apply { this.mtu = mtu }
private fun processSplitTunneling() {
if (includedAddresses.isNotEmpty() && excludedAddresses.isNotEmpty()) {
throw BadConfigException("Config contains addresses for inclusive and exclusive split tunneling at the same time")
}
if (includedAddresses.isNotEmpty()) {
// remove default routes, if any
removeRoute(InetNetwork("0.0.0.0", 0))
removeRoute(InetNetwork("::", 0))
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
// for older versions of Android, add the default route to the excluded routes
// to correctly build the excluded subnets list later
excludeRoute(InetNetwork("0.0.0.0", 0))
}
addRoutes(includedAddresses)
} else if (excludedAddresses.isNotEmpty()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// default routes are required for split tunneling in newer versions of Android
addRoute(InetNetwork("0.0.0.0", 0))
addRoute(InetNetwork("::", 0))
}
excludeRoutes(excludedAddresses)
}
}
private fun processExcludedRoutes() {
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
// and add them to routes
val ipRangeSet = IpRangeSet()
ipRangeSet.remove(IpRange("127.0.0.0", 8))
excludedRoutes.forEach {
ipRangeSet.remove(IpRange(it))
}
// remove default routes, if any
removeRoute(InetNetwork("0.0.0.0", 0))
removeRoute(InetNetwork("::", 0))
ipRangeSet.subnets().forEach(::addRoute)
addRoute(InetNetwork("2000::", 3))
}
}
private fun validate() { private fun validate() {
val errorMessage = StringBuilder() val errorMessage = StringBuilder()
@@ -157,13 +103,7 @@ open class ProtocolConfig protected constructor(
if (errorMessage.isNotEmpty()) throw BadConfigException(errorMessage.toString()) if (errorMessage.isNotEmpty()) throw BadConfigException(errorMessage.toString())
} }
protected fun configBuild() { open fun build(): ProtocolConfig = validate().run { ProtocolConfig(this@Builder) }
processSplitTunneling()
processExcludedRoutes()
validate()
}
open fun build(): ProtocolConfig = configBuild().run { ProtocolConfig(this@Builder) }
} }
companion object { companion object {
@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<exclude domain="sharedpref" path="." />
</full-backup-content>
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
<cloud-backup>
<exclude domain="sharedpref" path="." />
</cloud-backup>
<device-transfer>
<exclude domain="sharedpref" path="." />
</device-transfer>
</data-extraction-rules>
@@ -2,10 +2,8 @@ package org.amnezia.vpn
import android.content.ComponentName import android.content.ComponentName
import android.content.Intent import android.content.Intent
import android.content.Intent.EXTRA_MIME_TYPES
import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
import android.content.ServiceConnection import android.content.ServiceConnection
import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.net.VpnService import android.net.VpnService
import android.os.Bundle import android.os.Bundle
@@ -14,13 +12,11 @@ import android.os.IBinder
import android.os.Looper import android.os.Looper
import android.os.Message import android.os.Message
import android.os.Messenger import android.os.Messenger
import android.webkit.MimeTypeMap
import android.widget.Toast import android.widget.Toast
import androidx.annotation.MainThread import androidx.annotation.MainThread
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import java.io.IOException import java.io.IOException
import kotlin.LazyThreadSafetyMode.NONE import kotlin.LazyThreadSafetyMode.NONE
import kotlin.text.RegexOption.IGNORE_CASE
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -39,7 +35,6 @@ private const val TAG = "AmneziaActivity"
private const val CHECK_VPN_PERMISSION_ACTION_CODE = 1 private const val CHECK_VPN_PERMISSION_ACTION_CODE = 1
private const val CREATE_FILE_ACTION_CODE = 2 private const val CREATE_FILE_ACTION_CODE = 2
private const val OPEN_FILE_ACTION_CODE = 3
private const val BIND_SERVICE_TIMEOUT = 1000L private const val BIND_SERVICE_TIMEOUT = 1000L
class AmneziaActivity : QtActivity() { class AmneziaActivity : QtActivity() {
@@ -64,7 +59,6 @@ class AmneziaActivity : QtActivity() {
ServiceEvent.DISCONNECTED -> { ServiceEvent.DISCONNECTED -> {
QtAndroidController.onVpnDisconnected() QtAndroidController.onVpnDisconnected()
doUnbindService()
} }
ServiceEvent.RECONNECTING -> { ServiceEvent.RECONNECTING -> {
@@ -145,7 +139,7 @@ class AmneziaActivity : QtActivity() {
*/ */
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Log.d(TAG, "Create Amnezia activity: $intent") Log.v(TAG, "Create Amnezia activity: $intent")
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
vpnServiceMessenger = IpcMessenger( vpnServiceMessenger = IpcMessenger(
onDeadObjectException = ::doUnbindService, onDeadObjectException = ::doUnbindService,
@@ -156,7 +150,7 @@ class AmneziaActivity : QtActivity() {
override fun onNewIntent(intent: Intent?) { override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent) super.onNewIntent(intent)
Log.d(TAG, "onNewIntent: $intent") Log.v(TAG, "onNewIntent: $intent")
intent?.let(::processIntent) intent?.let(::processIntent)
} }
@@ -176,7 +170,7 @@ class AmneziaActivity : QtActivity() {
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
Log.d(TAG, "Start Amnezia activity") Log.v(TAG, "Start Amnezia activity")
mainScope.launch { mainScope.launch {
qtInitialized.await() qtInitialized.await()
doBindService() doBindService()
@@ -184,13 +178,13 @@ class AmneziaActivity : QtActivity() {
} }
override fun onStop() { override fun onStop() {
Log.d(TAG, "Stop Amnezia activity") Log.v(TAG, "Stop Amnezia activity")
doUnbindService() doUnbindService()
super.onStop() super.onStop()
} }
override fun onDestroy() { override fun onDestroy() {
Log.d(TAG, "Destroy Amnezia activity") Log.v(TAG, "Destroy Amnezia activity")
mainScope.cancel() mainScope.cancel()
super.onDestroy() super.onDestroy()
} }
@@ -207,19 +201,10 @@ class AmneziaActivity : QtActivity() {
} }
} }
OPEN_FILE_ACTION_CODE -> {
when (resultCode) {
RESULT_OK -> data?.data?.toString() ?: ""
else -> ""
}.let { uri ->
QtAndroidController.onFileOpened(uri)
}
}
CHECK_VPN_PERMISSION_ACTION_CODE -> { CHECK_VPN_PERMISSION_ACTION_CODE -> {
when (resultCode) { when (resultCode) {
RESULT_OK -> { RESULT_OK -> {
Log.d(TAG, "Vpn permission granted") Log.v(TAG, "Vpn permission granted")
Toast.makeText(this, "Vpn permission granted", Toast.LENGTH_LONG).show() Toast.makeText(this, "Vpn permission granted", Toast.LENGTH_LONG).show()
checkVpnPermissionCallbacks?.run { onSuccess() } checkVpnPermissionCallbacks?.run { onSuccess() }
} }
@@ -242,7 +227,7 @@ class AmneziaActivity : QtActivity() {
*/ */
@MainThread @MainThread
private fun doBindService() { private fun doBindService() {
Log.d(TAG, "Bind service") Log.v(TAG, "Bind service")
Intent(this, AmneziaVpnService::class.java).also { Intent(this, AmneziaVpnService::class.java).also {
bindService(it, serviceConnection, BIND_ABOVE_CLIENT) bindService(it, serviceConnection, BIND_ABOVE_CLIENT)
} }
@@ -253,7 +238,7 @@ class AmneziaActivity : QtActivity() {
@MainThread @MainThread
private fun doUnbindService() { private fun doUnbindService() {
if (isInBoundState) { if (isInBoundState) {
Log.d(TAG, "Unbind service") Log.v(TAG, "Unbind service")
isWaitingStatus = true isWaitingStatus = true
QtAndroidController.onServiceDisconnected() QtAndroidController.onServiceDisconnected()
vpnServiceMessenger.reset() vpnServiceMessenger.reset()
@@ -288,7 +273,7 @@ class AmneziaActivity : QtActivity() {
@MainThread @MainThread
private fun checkVpnPermission(onSuccess: () -> Unit, onFail: () -> Unit) { private fun checkVpnPermission(onSuccess: () -> Unit, onFail: () -> Unit) {
Log.d(TAG, "Check VPN permission") Log.v(TAG, "Check VPN permission")
VpnService.prepare(applicationContext)?.let { VpnService.prepare(applicationContext)?.let {
checkVpnPermissionCallbacks = CheckVpnPermissionCallbacks(onSuccess, onFail) checkVpnPermissionCallbacks = CheckVpnPermissionCallbacks(onSuccess, onFail)
startActivityForResult(it, CHECK_VPN_PERMISSION_ACTION_CODE) startActivityForResult(it, CHECK_VPN_PERMISSION_ACTION_CODE)
@@ -309,7 +294,7 @@ class AmneziaActivity : QtActivity() {
} }
private fun connectToVpn(vpnConfig: String) { private fun connectToVpn(vpnConfig: String) {
Log.d(TAG, "Connect to VPN") Log.v(TAG, "Connect to VPN")
vpnServiceMessenger.send { vpnServiceMessenger.send {
Action.CONNECT.packToMessage { Action.CONNECT.packToMessage {
putString(VPN_CONFIG, vpnConfig) putString(VPN_CONFIG, vpnConfig)
@@ -318,7 +303,7 @@ class AmneziaActivity : QtActivity() {
} }
private fun startVpnService(vpnConfig: String) { private fun startVpnService(vpnConfig: String) {
Log.d(TAG, "Start VPN service") Log.v(TAG, "Start VPN service")
Intent(this, AmneziaVpnService::class.java).apply { Intent(this, AmneziaVpnService::class.java).apply {
putExtra(VPN_CONFIG, vpnConfig) putExtra(VPN_CONFIG, vpnConfig)
}.also { }.also {
@@ -327,7 +312,7 @@ class AmneziaActivity : QtActivity() {
} }
private fun disconnectFromVpn() { private fun disconnectFromVpn() {
Log.d(TAG, "Disconnect from VPN") Log.v(TAG, "Disconnect from VPN")
vpnServiceMessenger.send(Action.DISCONNECT) vpnServiceMessenger.send(Action.DISCONNECT)
} }
@@ -371,7 +356,7 @@ class AmneziaActivity : QtActivity() {
@Suppress("unused") @Suppress("unused")
fun saveFile(fileName: String, data: String) { fun saveFile(fileName: String, data: String) {
Log.d(TAG, "Save file $fileName") Log.v(TAG, "Save file $fileName")
mainScope.launch { mainScope.launch {
tmpFileContentToSave = data tmpFileContentToSave = data
@@ -385,43 +370,17 @@ class AmneziaActivity : QtActivity() {
} }
} }
@Suppress("unused")
fun openFile(filter: String?) {
Log.v(TAG, "Open file with filter: $filter")
val mimeTypes = if (!filter.isNullOrEmpty()) {
val extensionRegex = "\\*\\.[a-z .]+".toRegex(IGNORE_CASE)
val mime = MimeTypeMap.getSingleton()
extensionRegex.findAll(filter).map {
mime.getMimeTypeFromExtension(it.value.drop(2))
}.filterNotNull().toSet()
} else emptySet()
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
Log.v(TAG, "File mimyType filter: $mimeTypes")
when (mimeTypes.size) {
1 -> type = mimeTypes.first()
in 2..Int.MAX_VALUE -> {
type = "*/*"
putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray())
}
else -> type = "*/*"
}
}.also {
startActivityForResult(it, OPEN_FILE_ACTION_CODE)
}
}
@Suppress("unused") @Suppress("unused")
fun setNotificationText(title: String, message: String, timerSec: Int) { fun setNotificationText(title: String, message: String, timerSec: Int) {
Log.v(TAG, "Set notification text") Log.v(TAG, "Set notification text")
Log.w(TAG, "Not yet implemented")
} }
@Suppress("unused") @Suppress("unused")
fun isCameraPresent(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA) fun cleanupLogs() {
Log.v(TAG, "Cleanup logs")
Log.w(TAG, "Not yet implemented")
}
@Suppress("unused") @Suppress("unused")
fun startQrCodeReader() { fun startQrCodeReader() {
@@ -430,29 +389,4 @@ class AmneziaActivity : QtActivity() {
startActivity(it) startActivity(it)
} }
} }
@Suppress("unused")
fun setSaveLogs(enabled: Boolean) {
Log.d(TAG, "Set save logs: $enabled")
mainScope.launch {
Log.saveLogs = enabled
vpnServiceMessenger.send {
Action.SET_SAVE_LOGS.packToMessage {
putBoolean(SAVE_LOGS, enabled)
}
}
}
}
@Suppress("unused")
fun exportLogsFile(fileName: String) {
Log.v(TAG, "Export logs file")
saveFile(fileName, Log.getLogs())
}
@Suppress("unused")
fun clearLogs() {
Log.v(TAG, "Clear logs")
Log.clearLogs()
}
} }
@@ -5,20 +5,14 @@ import androidx.camera.core.CameraSelector
import androidx.camera.core.CameraXConfig import androidx.camera.core.CameraXConfig
import androidx.core.app.NotificationChannelCompat.Builder import androidx.core.app.NotificationChannelCompat.Builder
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.Prefs
import org.qtproject.qt.android.bindings.QtApplication import org.qtproject.qt.android.bindings.QtApplication
private const val TAG = "AmneziaApplication"
const val NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notification" const val NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notification"
class AmneziaApplication : QtApplication(), CameraXConfig.Provider { class AmneziaApplication : QtApplication(), CameraXConfig.Provider {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
Prefs.init(this)
Log.init(this)
Log.d(TAG, "Create Amnezia application")
createNotificationChannel() createNotificationChannel()
} }
@@ -50,7 +50,6 @@ import org.amnezia.vpn.protocol.putStatistics
import org.amnezia.vpn.protocol.putStatus import org.amnezia.vpn.protocol.putStatus
import org.amnezia.vpn.protocol.wireguard.Wireguard import org.amnezia.vpn.protocol.wireguard.Wireguard
import org.amnezia.vpn.util.Log import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.Prefs
import org.amnezia.vpn.util.net.NetworkState import org.amnezia.vpn.util.net.NetworkState
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
@@ -59,8 +58,6 @@ private const val TAG = "AmneziaVpnService"
const val VPN_CONFIG = "VPN_CONFIG" const val VPN_CONFIG = "VPN_CONFIG"
const val ERROR_MSG = "ERROR_MSG" const val ERROR_MSG = "ERROR_MSG"
const val SAVE_LOGS = "SAVE_LOGS"
const val AFTER_PERMISSION_CHECK = "AFTER_PERMISSION_CHECK" const val AFTER_PERMISSION_CHECK = "AFTER_PERMISSION_CHECK"
private const val PREFS_CONFIG_KEY = "LAST_CONF" private const val PREFS_CONFIG_KEY = "LAST_CONF"
private const val NOTIFICATION_ID = 1337 private const val NOTIFICATION_ID = 1337
@@ -121,7 +118,7 @@ class AmneziaVpnService : VpnService() {
Action.CONNECT -> { Action.CONNECT -> {
val vpnConfig = msg.data.getString(VPN_CONFIG) val vpnConfig = msg.data.getString(VPN_CONFIG)
Prefs.save(PREFS_CONFIG_KEY, vpnConfig) saveConfigToPrefs(vpnConfig)
connect(vpnConfig) connect(vpnConfig)
} }
@@ -138,10 +135,6 @@ class AmneziaVpnService : VpnService() {
} }
} }
} }
Action.SET_SAVE_LOGS -> {
Log.saveLogs = msg.data.getBoolean(SAVE_LOGS)
}
} }
} }
} }
@@ -186,7 +179,7 @@ class AmneziaVpnService : VpnService() {
*/ */
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
Log.d(TAG, "Create Amnezia VPN service") Log.v(TAG, "Create Amnezia VPN service")
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
connectionScope = CoroutineScope(SupervisorJob() + Dispatchers.IO + connectionExceptionHandler) connectionScope = CoroutineScope(SupervisorJob() + Dispatchers.IO + connectionExceptionHandler)
clientMessenger = IpcMessenger(messengerName = "Client") clientMessenger = IpcMessenger(messengerName = "Client")
@@ -200,15 +193,15 @@ class AmneziaVpnService : VpnService() {
else intent?.component?.packageName != packageName else intent?.component?.packageName != packageName
if (isAlwaysOnCompat) { if (isAlwaysOnCompat) {
Log.d(TAG, "Start service via Always-on") Log.v(TAG, "Start service via Always-on")
connect(Prefs.load(PREFS_CONFIG_KEY)) connect(loadConfigFromPrefs())
} else if (intent?.getBooleanExtra(AFTER_PERMISSION_CHECK, false) == true) { } else if (intent?.getBooleanExtra(AFTER_PERMISSION_CHECK, false) == true) {
Log.d(TAG, "Start service after permission check") Log.v(TAG, "Start service after permission check")
connect(Prefs.load(PREFS_CONFIG_KEY)) connect(loadConfigFromPrefs())
} else { } else {
Log.d(TAG, "Start service") Log.v(TAG, "Start service")
val vpnConfig = intent?.getStringExtra(VPN_CONFIG) val vpnConfig = intent?.getStringExtra(VPN_CONFIG)
Prefs.save(PREFS_CONFIG_KEY, vpnConfig) saveConfigToPrefs(vpnConfig)
connect(vpnConfig) connect(vpnConfig)
} }
ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, foregroundServiceTypeCompat) ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, foregroundServiceTypeCompat)
@@ -244,7 +237,7 @@ class AmneziaVpnService : VpnService() {
} }
override fun onRevoke() { override fun onRevoke() {
Log.d(TAG, "onRevoke") Log.v(TAG, "onRevoke")
// Calls to onRevoke() method may not happen on the main thread of the process // Calls to onRevoke() method may not happen on the main thread of the process
mainScope.launch { mainScope.launch {
disconnect() disconnect()
@@ -252,7 +245,7 @@ class AmneziaVpnService : VpnService() {
} }
override fun onDestroy() { override fun onDestroy() {
Log.d(TAG, "Destroy service") Log.v(TAG, "Destroy service")
runBlocking { runBlocking {
disconnect() disconnect()
disconnectionJob?.join() disconnectionJob?.join()
@@ -263,7 +256,7 @@ class AmneziaVpnService : VpnService() {
} }
private fun stopService() { private fun stopService() {
Log.d(TAG, "Stop service") Log.v(TAG, "Stop service")
// the coroutine below will be canceled during the onDestroy call // the coroutine below will be canceled during the onDestroy call
mainScope.launch { mainScope.launch {
delay(STOP_SERVICE_TIMEOUT) delay(STOP_SERVICE_TIMEOUT)
@@ -279,7 +272,7 @@ class AmneziaVpnService : VpnService() {
private fun launchProtocolStateHandler() { private fun launchProtocolStateHandler() {
mainScope.launch { mainScope.launch {
protocolState.collect { protocolState -> protocolState.collect { protocolState ->
Log.d(TAG, "Protocol state changed: $protocolState") Log.d(TAG, "Protocol state: $protocolState")
when (protocolState) { when (protocolState) {
CONNECTED -> { CONNECTED -> {
clientMessenger.send(ServiceEvent.CONNECTED) clientMessenger.send(ServiceEvent.CONNECTED)
@@ -312,7 +305,7 @@ class AmneziaVpnService : VpnService() {
@MainThread @MainThread
private fun launchSendingStatistics() { private fun launchSendingStatistics() {
/* if (isServiceBound && isConnected) { if (isServiceBound && isConnected) {
statisticsSendingJob = mainScope.launch { statisticsSendingJob = mainScope.launch {
while (true) { while (true) {
clientMessenger.send { clientMessenger.send {
@@ -323,7 +316,7 @@ class AmneziaVpnService : VpnService() {
delay(STATISTICS_SENDING_TIMEOUT) delay(STATISTICS_SENDING_TIMEOUT)
} }
} }
} */ }
} }
@MainThread @MainThread
@@ -335,7 +328,7 @@ class AmneziaVpnService : VpnService() {
private fun connect(vpnConfig: String?) { private fun connect(vpnConfig: String?) {
if (isConnected || protocolState.value == CONNECTING) return if (isConnected || protocolState.value == CONNECTING) return
Log.d(TAG, "Start VPN connection") Log.v(TAG, "Start VPN connection")
protocolState.value = CONNECTING protocolState.value = CONNECTING
@@ -364,7 +357,7 @@ class AmneziaVpnService : VpnService() {
private fun disconnect() { private fun disconnect() {
if (isUnknown || isDisconnected || protocolState.value == DISCONNECTING) return if (isUnknown || isDisconnected || protocolState.value == DISCONNECTING) return
Log.d(TAG, "Stop VPN connection") Log.v(TAG, "Stop VPN connection")
protocolState.value = DISCONNECTING protocolState.value = DISCONNECTING
@@ -390,7 +383,7 @@ class AmneziaVpnService : VpnService() {
private fun reconnect() { private fun reconnect() {
if (!isConnected) return if (!isConnected) return
Log.d(TAG, "Reconnect VPN") Log.v(TAG, "Reconnect VPN")
protocolState.value = RECONNECTING protocolState.value = RECONNECTING
@@ -446,4 +439,10 @@ class AmneziaVpnService : VpnService() {
} else { } else {
true true
} }
private fun loadConfigFromPrefs(): String? =
Prefs.get(this).getString(PREFS_CONFIG_KEY, null)
private fun saveConfigToPrefs(config: String?) =
Prefs.get(this).edit().putString(PREFS_CONFIG_KEY, config).apply()
} }
@@ -29,20 +29,20 @@ class ImportConfigActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Log.d(TAG, "Create Import Config Activity: $intent") Log.v(TAG, "Create Import Config Activity: $intent")
intent?.let(::readConfig) intent?.let(::readConfig)
} }
override fun onNewIntent(intent: Intent?) { override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent) super.onNewIntent(intent)
Log.d(TAG, "onNewIntent: $intent") Log.v(TAG, "onNewIntent: $intent")
intent?.let(::readConfig) intent?.let(::readConfig)
} }
private fun readConfig(intent: Intent) { private fun readConfig(intent: Intent) {
when (intent.action) { when (intent.action) {
ACTION_SEND -> { ACTION_SEND -> {
Log.d(TAG, "Process SEND action, type: ${intent.type}") Log.v(TAG, "Process SEND action, type: ${intent.type}")
when (intent.type) { when (intent.type) {
"application/octet-stream" -> { "application/octet-stream" -> {
intent.getUriCompat()?.let { uri -> intent.getUriCompat()?.let { uri ->
@@ -60,7 +60,7 @@ class ImportConfigActivity : ComponentActivity() {
} }
ACTION_VIEW -> { ACTION_VIEW -> {
Log.d(TAG, "Process VIEW action, scheme: ${intent.scheme}") Log.v(TAG, "Process VIEW action, scheme: ${intent.scheme}")
when (intent.scheme) { when (intent.scheme) {
"file", "content" -> { "file", "content" -> {
intent.data?.let { uri -> intent.data?.let { uri ->
@@ -128,7 +128,7 @@ class ImportConfigActivity : ComponentActivity() {
private fun startMainActivity(config: String) { private fun startMainActivity(config: String) {
if (config.isNotBlank()) { if (config.isNotBlank()) {
Log.d(TAG, "startMainActivity") Log.v(TAG, "startMainActivity")
Intent(applicationContext, AmneziaActivity::class.java).apply { Intent(applicationContext, AmneziaActivity::class.java).apply {
action = ACTION_IMPORT_CONFIG action = ACTION_IMPORT_CONFIG
addCategory(CATEGORY_DEFAULT) addCategory(CATEGORY_DEFAULT)
@@ -32,8 +32,7 @@ enum class Action : IpcMessage {
REGISTER_CLIENT, REGISTER_CLIENT,
CONNECT, CONNECT,
DISCONNECT, DISCONNECT,
REQUEST_STATUS, REQUEST_STATUS
SET_SAVE_LOGS
} }
fun <T> T.packToMessage(): Message fun <T> T.packToMessage(): Message
@@ -0,0 +1,25 @@
package org.amnezia.vpn
import android.content.Context
import android.content.SharedPreferences
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import org.amnezia.vpn.util.Log
private const val TAG = "Prefs"
private const val PREFS_FILE = "org.amnezia.vpn.prefs"
private const val SECURE_PREFS_FILE = "$PREFS_FILE.secure"
object Prefs {
fun get(context: Context, appContext: Context = context.applicationContext): SharedPreferences =
try {
EncryptedSharedPreferences(
appContext,
SECURE_PREFS_FILE,
MasterKey(appContext)
)
} catch (e: Exception) {
Log.e(TAG, "Getting Encryption Storage failed: ${e.message}, plaintext fallback")
appContext.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)
}
}
@@ -25,7 +25,7 @@ class VpnRequestActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Log.d(TAG, "Start request activity") Log.v(TAG, "Start request activity")
val requestIntent = VpnService.prepare(applicationContext) val requestIntent = VpnService.prepare(applicationContext)
if (requestIntent != null) { if (requestIntent != null) {
if (getSystemService<KeyguardManager>()!!.isKeyguardLocked) { if (getSystemService<KeyguardManager>()!!.isKeyguardLocked) {
@@ -15,8 +15,6 @@ object QtAndroidController {
external fun onVpnReconnecting() external fun onVpnReconnecting()
external fun onStatisticsUpdate(rxBytes: Long, txBytes: Long) external fun onStatisticsUpdate(rxBytes: Long, txBytes: Long)
external fun onFileOpened(uri: String)
external fun onConfigImported(data: String) external fun onConfigImported(data: String)
external fun decodeQrCode(data: String): Boolean external fun decodeQrCode(data: String): Boolean
-4
View File
@@ -15,7 +15,3 @@ android {
buildConfig = true buildConfig = true
} }
} }
dependencies {
implementation(libs.androidx.security.crypto)
}
+25 -244
View File
@@ -1,252 +1,33 @@
package org.amnezia.vpn.util package org.amnezia.vpn.util
import android.content.Context
import android.icu.text.DateFormat
import android.icu.text.SimpleDateFormat
import android.os.Build
import android.os.Process
import java.io.File
import java.io.IOException
import java.io.RandomAccessFile
import java.nio.channels.FileChannel
import java.nio.channels.FileLock
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.Date
import java.util.Locale
import java.util.concurrent.locks.ReentrantLock
import org.amnezia.vpn.util.Log.Priority.D
import org.amnezia.vpn.util.Log.Priority.E
import org.amnezia.vpn.util.Log.Priority.F
import org.amnezia.vpn.util.Log.Priority.I
import org.amnezia.vpn.util.Log.Priority.V
import org.amnezia.vpn.util.Log.Priority.W
import android.util.Log as NativeLog import android.util.Log as NativeLog
private const val TAG = "Log" class Log {
private const val LOG_FILE_NAME = "amneziaVPN.log" companion object {
private const val ROTATE_LOG_FILE_NAME = "amneziaVPN.rotate.log" fun v(tag: String, msg: String) = debugLog(tag, msg, NativeLog::v)
private const val LOCK_FILE_NAME = ".lock"
private const val DATE_TIME_PATTERN = "MM-dd HH:mm:ss.SSS"
private const val PREFS_SAVE_LOGS_KEY = "SAVE_LOGS"
private const val LOG_MAX_FILE_SIZE = 1024 * 1024
/** fun d(tag: String, msg: String) = debugLog(tag, msg, NativeLog::d)
* | Priority | Save to file | Logcat logging |
* |-------------------|--------------|----------------------------------------------| fun i(tag: String, msg: String) = log(tag, msg, NativeLog::i)
* | Verbose | Don't save | Only in Debug build |
* | Debug | Save | In Debug build or if log saving is enabled | fun w(tag: String, msg: String) = log(tag, msg, NativeLog::w)
* | Info, Warn, Error | Save | Enabled |
* | Fatal (Assert) | Save | Enabled. Depending on system configuration, | fun e(tag: String, msg: String) = log(tag, msg, NativeLog::e)
* | | | create a report and/or terminate the process |
*/ fun v(tag: String, msg: Any?) = v(tag, msg.toString())
object Log {
private val dateTimeFormat: Any = fun d(tag: String, msg: Any?) = d(tag, msg.toString())
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)
else object : ThreadLocal<DateFormat>() { fun i(tag: String, msg: Any?) = i(tag, msg.toString())
override fun initialValue(): DateFormat = SimpleDateFormat(DATE_TIME_PATTERN, Locale.US)
fun w(tag: String, msg: Any?) = w(tag, msg.toString())
fun e(tag: String, msg: Any?) = e(tag, msg.toString())
private inline fun log(tag: String, msg: String, delegate: (String, String) -> Unit) = delegate(tag, msg)
private inline fun debugLog(tag: String, msg: String, delegate: (String, String) -> Unit) {
if (BuildConfig.DEBUG) delegate(tag, msg)
} }
private lateinit var logDir: File
private val logFile: File by lazy { File(logDir, LOG_FILE_NAME) }
private val rotateLogFile: File by lazy { File(logDir, ROTATE_LOG_FILE_NAME) }
private val fileLock: FileChannel by lazy { RandomAccessFile(File(logDir, LOCK_FILE_NAME).path, "rw").channel }
private val threadLock: ReentrantLock by lazy { ReentrantLock() }
@Volatile
private var _saveLogs: Boolean = false
var saveLogs: Boolean
get() = _saveLogs
set(value) {
if (_saveLogs != value) {
if (value && !logDir.exists() && !logDir.mkdir()) {
NativeLog.e(TAG, "Failed to create dir: $logDir")
return
}
_saveLogs = value
Prefs.save(PREFS_SAVE_LOGS_KEY, value)
}
}
@JvmStatic
fun v(tag: String, msg: String) = log(tag, msg, V)
@JvmStatic
fun d(tag: String, msg: String) = log(tag, msg, D)
@JvmStatic
fun i(tag: String, msg: String) = log(tag, msg, I)
@JvmStatic
fun w(tag: String, msg: String) = log(tag, msg, W)
@JvmStatic
fun e(tag: String, msg: String) = log(tag, msg, E)
@JvmStatic
fun f(tag: String, msg: String) = log(tag, msg, F)
fun v(tag: String, msg: Any?) = v(tag, msg.toString())
fun d(tag: String, msg: Any?) = d(tag, msg.toString())
fun i(tag: String, msg: Any?) = i(tag, msg.toString())
fun w(tag: String, msg: Any?) = w(tag, msg.toString())
fun e(tag: String, msg: Any?) = e(tag, msg.toString())
fun f(tag: String, msg: Any?) = f(tag, msg.toString())
fun init(context: Context) {
v(TAG, "Init Log")
logDir = File(context.cacheDir, "logs")
saveLogs = Prefs.load(PREFS_SAVE_LOGS_KEY)
}
fun getLogs(): String =
"${deviceInfo()}\n${readLogs()}\nLOGCAT:\n${getLogcat()}"
fun clearLogs() {
withLock {
logFile.delete()
rotateLogFile.delete()
}
}
private fun log(tag: String, msg: String, priority: Priority) {
if (saveLogs && priority != V) saveLogMsg(formatLogMsg(tag, msg, priority))
if (priority == F) {
NativeLog.wtf(tag, msg)
} else if (
(priority != V && priority != D) ||
(priority == V && BuildConfig.DEBUG) ||
(priority == D && (BuildConfig.DEBUG || saveLogs))
) {
NativeLog.println(priority.level, tag, msg)
}
}
private fun saveLogMsg(msg: String) {
withTryLock(condition = { logFile.length() > LOG_MAX_FILE_SIZE }) {
logFile.renameTo(rotateLogFile)
}
try {
logFile.appendText(msg)
} catch (e: IOException) {
NativeLog.e(TAG, "Failed to write log: $e")
}
}
private fun formatLogMsg(tag: String, msg: String, priority: Priority): String {
val date = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
LocalDateTime.now().format(dateTimeFormat as DateTimeFormatter)
} else {
@Suppress("UNCHECKED_CAST")
(dateTimeFormat as ThreadLocal<DateFormat>).get()?.format(Date())
}
return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
"$tag: $msg\n"
}
private fun deviceInfo(): String {
val sb = StringBuilder()
sb.append("Model: ").appendLine(Build.MODEL)
sb.append("Brand: ").appendLine(Build.BRAND)
sb.append("Product: ").appendLine(Build.PRODUCT)
sb.append("Device: ").appendLine(Build.DEVICE)
sb.append("Codename: ").appendLine(Build.VERSION.CODENAME)
sb.append("Release: ").appendLine(Build.VERSION.RELEASE)
sb.append("SDK: ").appendLine(Build.VERSION.SDK_INT)
sb.append("ABI: ").appendLine(Build.SUPPORTED_ABIS.joinToString())
return sb.toString()
}
private fun readLogs(): String {
var logText = ""
withLock {
try {
if (rotateLogFile.exists()) logText = rotateLogFile.readText()
if (logFile.exists()) logText += logFile.readText()
} catch (e: IOException) {
val errorMsg = "Failed to read log: $e"
NativeLog.e(TAG, errorMsg)
logText += errorMsg
}
}
return logText
}
private fun getLogcat(): String {
try {
val process = ProcessBuilder("logcat", "-d").redirectErrorStream(true).start()
return process.inputStream.reader().readText()
} catch (e: IOException) {
val errorMsg = "Failed to get logcat log: $e"
NativeLog.e(TAG, errorMsg)
return errorMsg
}
}
private fun withLock(block: () -> Unit) {
threadLock.lock()
try {
var l: FileLock? = null
try {
l = fileLock.lock()
block()
} catch (e: IOException) {
NativeLog.e(TAG, "Failed to get file lock: $e")
} finally {
try {
l?.release()
} catch (e: IOException) {
NativeLog.e(TAG, "Failed to release file lock: $e")
}
}
} finally {
threadLock.unlock()
}
}
private fun withTryLock(condition: () -> Boolean, block: () -> Unit) {
if (condition()) {
if (threadLock.tryLock()) {
try {
if (condition()) {
var l: FileLock? = null
try {
l = fileLock.tryLock()
if (l != null) {
if (condition()) {
block()
}
}
} catch (e: IOException) {
NativeLog.e(TAG, "Failed to get file tryLock: $e")
} finally {
try {
l?.release()
} catch (e: IOException) {
NativeLog.e(TAG, "Failed to release file tryLock: $e")
}
}
}
} finally {
threadLock.unlock()
}
}
}
}
private enum class Priority(val level: Int) {
V(2),
D(3),
I(4),
W(5),
E(6),
F(7)
} }
} }
@@ -1,58 +0,0 @@
package org.amnezia.vpn.util
import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import kotlin.reflect.typeOf
private const val TAG = "Prefs"
private const val PREFS_FILE = "org.amnezia.vpn.prefs"
private const val SECURE_PREFS_FILE = "$PREFS_FILE.secure"
object Prefs {
private lateinit var app: Application
val prefs: SharedPreferences
get() = try {
EncryptedSharedPreferences(
app,
SECURE_PREFS_FILE,
MasterKey(app)
)
} catch (e: Exception) {
Log.e(TAG, "Getting Encryption Storage failed: $e, plaintext fallback")
app.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)
}
fun init(app: Application) {
Log.v(TAG, "Init Prefs")
this.app = app
}
fun save(key: String, value: Boolean) =
prefs.edit().putBoolean(key, value).apply()
fun save(key: String, value: String?) =
prefs.edit().putString(key, value).apply()
fun save(key: String, value: Int) =
prefs.edit().putInt(key, value).apply()
fun save(key: String, value: Long) =
prefs.edit().putLong(key, value).apply()
fun save(key: String, value: Float) =
prefs.edit().putFloat(key, value).apply()
inline fun <reified T> load(key: String): T {
return when (typeOf<T>()) {
typeOf<Boolean>() -> prefs.getBoolean(key, false)
typeOf<String>() -> prefs.getString(key, "")
typeOf<Int>() -> prefs.getInt(key, 0)
typeOf<Long>() -> prefs.getLong(key, 0L)
typeOf<Float>() -> prefs.getFloat(key, 0f)
else -> throw IllegalArgumentException("SharedPreferences does not support type: ${typeOf<T>()}")
} as T
}
}
@@ -82,7 +82,7 @@ class NetworkState(
fun bindNetworkListener() { fun bindNetworkListener() {
if (isListenerBound) return if (isListenerBound) return
Log.d(TAG, "Bind network listener") Log.v(TAG, "Bind network listener")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler) connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -95,7 +95,7 @@ class NetworkState(
fun unbindNetworkListener() { fun unbindNetworkListener() {
if (!isListenerBound) return if (!isListenerBound) return
Log.d(TAG, "Unbind network listener") Log.v(TAG, "Unbind network listener")
connectivityManager.unregisterNetworkCallback(networkCallback) connectivityManager.unregisterNetworkCallback(networkCallback)
isListenerBound = false isListenerBound = false
currentNetwork = null currentNetwork = null
@@ -99,10 +99,7 @@ open class Wireguard : Protocol() {
} }
protected fun WireguardConfig.Builder.configWireguard(configData: Map<String, String>) { protected fun WireguardConfig.Builder.configWireguard(configData: Map<String, String>) {
configData["Address"]?.split(",")?.map { address -> configData["Address"]?.let { addAddress(InetNetwork.parse(it)) }
InetNetwork.parse(address.trim())
}?.forEach(::addAddress)
configData["DNS"]?.split(",")?.map { dns -> configData["DNS"]?.split(",")?.map { dns ->
parseInetAddress(dns.trim()) parseInetAddress(dns.trim())
}?.forEach(::addDnsServer) }?.forEach(::addDnsServer)
@@ -11,7 +11,7 @@ open class WireguardConfig protected constructor(
val endpoint: InetEndpoint, val endpoint: InetEndpoint,
val persistentKeepalive: Int, val persistentKeepalive: Int,
val publicKeyHex: String, val publicKeyHex: String,
val preSharedKeyHex: String?, val preSharedKeyHex: String,
val privateKeyHex: String val privateKeyHex: String
) : ProtocolConfig(protocolConfigBuilder) { ) : ProtocolConfig(protocolConfigBuilder) {
@@ -43,8 +43,7 @@ open class WireguardConfig protected constructor(
appendLine("endpoint=$endpoint") appendLine("endpoint=$endpoint")
if (persistentKeepalive != 0) if (persistentKeepalive != 0)
appendLine("persistent_keepalive_interval=$persistentKeepalive") appendLine("persistent_keepalive_interval=$persistentKeepalive")
if (preSharedKeyHex != null) appendLine("preshared_key=$preSharedKeyHex")
appendLine("preshared_key=$preSharedKeyHex")
} }
open class Builder : ProtocolConfig.Builder(true) { open class Builder : ProtocolConfig.Builder(true) {
@@ -57,7 +56,7 @@ open class WireguardConfig protected constructor(
internal lateinit var publicKeyHex: String internal lateinit var publicKeyHex: String
private set private set
internal var preSharedKeyHex: String? = null internal lateinit var preSharedKeyHex: String
private set private set
internal lateinit var privateKeyHex: String internal lateinit var privateKeyHex: String
@@ -75,7 +74,7 @@ open class WireguardConfig protected constructor(
fun setPrivateKeyHex(privateKeyHex: String) = apply { this.privateKeyHex = privateKeyHex } fun setPrivateKeyHex(privateKeyHex: String) = apply { this.privateKeyHex = privateKeyHex }
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) } override fun build(): WireguardConfig = WireguardConfig(this)
} }
companion object { companion object {
+1 -1
View File
@@ -90,7 +90,7 @@ include_directories(
${LIBSSH_ROOT_DIR}/include ${LIBSSH_ROOT_DIR}/include
${CLIENT_ROOT_DIR}/3rd/libssh/include ${CLIENT_ROOT_DIR}/3rd/libssh/include
${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/include ${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/include
${CLIENT_ROOT_DIR}/3rd/qtkeychain/qtkeychain ${CLIENT_ROOT_DIR}/3rd/qtkeychain
${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain ${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain
${CMAKE_CURRENT_BINARY_DIR}/3rd/libssh/include ${CMAKE_CURRENT_BINARY_DIR}/3rd/libssh/include
) )
+2 -2
View File
@@ -27,7 +27,7 @@ link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
set(HEADERS ${HEADERS} set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidutils.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h ${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h
) )
@@ -35,7 +35,7 @@ set(HEADERS ${HEADERS}
set(SOURCES ${SOURCES} set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.cpp
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidutils.cpp
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp ${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp
) )
+1 -1
View File
@@ -97,7 +97,7 @@ target_compile_options(${PROJECT} PRIVATE
-DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\" -DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\"
) )
set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/amneziawg-apple/Sources) set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/awg-apple/Sources)
target_sources(${PROJECT} PRIVATE target_sources(${PROJECT} PRIVATE
# ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosvpnprotocol.swift # ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosvpnprotocol.swift
+19 -21
View File
@@ -118,33 +118,31 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia
return QJsonDocument(jConfig).toJson(); return QJsonDocument(jConfig).toJson();
} }
QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig, const int serverIndex) QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig)
{ {
QJsonObject json = QJsonDocument::fromJson(jsonConfig.toUtf8()).object(); QJsonObject json = QJsonDocument::fromJson(jsonConfig.toUtf8()).object();
QString config = json[config_key::config].toString(); QString config = json[config_key::config].toString();
if (!m_settings->server(serverIndex).value(config_key::configVersion).toInt()) { QRegularExpression regex("redirect-gateway.*");
QRegularExpression regex("redirect-gateway.*"); config.replace(regex, "");
config.replace(regex, "");
if (m_settings->routeMode() == Settings::VpnAllSites) { if (m_settings->routeMode() == Settings::VpnAllSites) {
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n"); config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
// Prevent ipv6 leak // Prevent ipv6 leak
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n"); config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
config.append("block-ipv6\n"); config.append("block-ipv6\n");
} }
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
// no redirect-gateway // no redirect-gateway
} }
if (m_settings->routeMode() == Settings::VpnAllExceptSites) { if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
#ifndef Q_OS_ANDROID #ifndef Q_OS_ANDROID
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n"); config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
#endif #endif
// Prevent ipv6 leak // Prevent ipv6 leak
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n"); config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
config.append("block-ipv6\n"); config.append("block-ipv6\n");
}
} }
#ifndef MZ_WINDOWS #ifndef MZ_WINDOWS
+1 -1
View File
@@ -26,7 +26,7 @@ public:
QString genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container, QString genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr); const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
QString processConfigWithLocalSettings(QString jsonConfig, const int serverIndex); QString processConfigWithLocalSettings(QString jsonConfig);
QString processConfigWithExportSettings(QString jsonConfig); QString processConfigWithExportSettings(QString jsonConfig);
ErrorCode signCert(DockerContainer container, ErrorCode signCert(DockerContainer container,
+1 -1
View File
@@ -92,7 +92,7 @@ QString &VpnConfigurator::processConfigWithLocalSettings(int serverIndex, Docker
processConfigWithDnsSettings(serverIndex, container, proto, config); processConfigWithDnsSettings(serverIndex, container, proto, config);
if (proto == Proto::OpenVpn) { if (proto == Proto::OpenVpn) {
config = openVpnConfigurator->processConfigWithLocalSettings(config, serverIndex); config = openVpnConfigurator->processConfigWithLocalSettings(config);
} }
return config; return config;
} }
@@ -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 it " QObject::tr("ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is "
"may be recognized by analysis systems in some highly censored regions.") }, "recognised 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-probing detection. Ideal for bypassing blocking in regions with the highest levels " "active-probbing 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("Create a file vault on your server to securely store and transfer files.") } }; QObject::tr("Creates 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 "
"protecting against blocking.\n\n" "blocking protection.\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"
"WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption " "Provides stable VPN connection, high performance on all devices. 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,24 +225,6 @@ 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,7 +22,6 @@ 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,
+59 -74
View File
@@ -4,92 +4,77 @@
#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;
constexpr const qint16 qrMagicCode = 1984; bool isValid() const { return !hostName.isEmpty() && !userName.isEmpty() && !secretData.isEmpty() && port > 0; }
};
struct ServerCredentials enum ErrorCode
{ {
QString hostName; // General error codes
QString userName; NoError = 0,
QString secretData; UnknownError,
int port = 22; InternalError,
NotImplementedError,
bool isValid() const // Server errors
{ ServerCheckFailed,
return !hostName.isEmpty() && !userName.isEmpty() && !secretData.isEmpty() && port > 0; ServerPortAlreadyAllocatedError,
} ServerContainerMissingError,
}; ServerDockerFailedError,
ServerCancelInstallation,
ServerUserNotInSudo,
ServerPacketManagerError,
enum ErrorCode { // Ssh connection errors
// General error codes SshRequestDeniedError, SshInterruptedError, SshInternalError,
NoError = 0, SshPrivateKeyError, SshPrivateKeyFormatError, SshTimeoutError,
UnknownError = 100,
InternalError = 101,
NotImplementedError = 102,
// Server errors // Ssh sftp errors
ServerCheckFailed = 200, SshSftpEofError, SshSftpNoSuchFileError, SshSftpPermissionDeniedError,
ServerPortAlreadyAllocatedError = 201, SshSftpFailureError, SshSftpBadMessageError, SshSftpNoConnectionError,
ServerContainerMissingError = 202, SshSftpConnectionLostError, SshSftpOpUnsupportedError, SshSftpInvalidHandleError,
ServerDockerFailedError = 203, SshSftpNoSuchPathError, SshSftpFileAlreadyExistsError, SshSftpWriteProtectError,
ServerCancelInstallation = 204, SshSftpNoMediaError,
ServerUserNotInSudo = 205,
ServerPacketManagerError = 206,
// Ssh connection errors // Local errors
SshRequestDeniedError = 300, OpenVpnConfigMissing,
SshInterruptedError = 301, OpenVpnManagementServerError,
SshInternalError = 302, ConfigMissing,
SshPrivateKeyError = 303,
SshPrivateKeyFormatError = 304,
SshTimeoutError = 305,
// Ssh sftp errors // Distro errors
SshSftpEofError = 400, OpenVpnExecutableMissing,
SshSftpNoSuchFileError = 401, ShadowSocksExecutableMissing,
SshSftpPermissionDeniedError = 402, CloakExecutableMissing,
SshSftpFailureError = 403, AmneziaServiceConnectionFailed,
SshSftpBadMessageError = 404, ExecutableMissing,
SshSftpNoConnectionError = 405,
SshSftpConnectionLostError = 406,
SshSftpOpUnsupportedError = 407,
SshSftpInvalidHandleError = 408,
SshSftpNoSuchPathError = 409,
SshSftpFileAlreadyExistsError = 410,
SshSftpWriteProtectError = 411,
SshSftpNoMediaError = 412,
// Local errors // VPN errors
OpenVpnConfigMissing = 500, OpenVpnAdaptersInUseError,
OpenVpnManagementServerError = 501, OpenVpnUnknownError,
ConfigMissing = 502, OpenVpnTapAdapterError,
AddressPoolError,
// Distro errors // 3rd party utils errors
OpenVpnExecutableMissing = 600, OpenSslFailed,
ShadowSocksExecutableMissing = 601, ShadowSocksExecutableCrashed,
CloakExecutableMissing = 602, CloakExecutableCrashed,
AmneziaServiceConnectionFailed = 603,
ExecutableMissing = 604,
// VPN errors // import and install errors
OpenVpnAdaptersInUseError = 700, ImportInvalidConfigError,
OpenVpnUnknownError = 701,
OpenVpnTapAdapterError = 702,
AddressPoolError = 703,
// 3rd party utils errors // Android errors
OpenSslFailed = 800, AndroidError
ShadowSocksExecutableCrashed = 801, };
CloakExecutableCrashed = 802,
// import and install errors
ImportInvalidConfigError = 900,
// Android errors
AndroidError = 1000
};
} // namespace amnezia } // namespace amnezia
+42 -46
View File
@@ -2,74 +2,70 @@
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): errorMessage = QObject::tr("No error"); break; case(NoError): return QObject::tr("No error");
case(UnknownError): errorMessage = QObject::tr("Unknown Error"); break; case(UnknownError): return QObject::tr("Unknown Error");
case(NotImplementedError): errorMessage = QObject::tr("Function not implemented"); break; case(NotImplementedError): return QObject::tr("Function not implemented");
// Server errors // Server errors
case(ServerCheckFailed): errorMessage = QObject::tr("Server check failed"); break; case(ServerCheckFailed): return QObject::tr("Server check failed");
case(ServerPortAlreadyAllocatedError): errorMessage = QObject::tr("Server port already used. Check for another software"); break; case(ServerPortAlreadyAllocatedError): return QObject::tr("Server port already used. Check for another software");
case(ServerContainerMissingError): errorMessage = QObject::tr("Server error: Docker container missing"); break; case(ServerContainerMissingError): return QObject::tr("Server error: Docker container missing");
case(ServerDockerFailedError): errorMessage = QObject::tr("Server error: Docker failed"); break; case(ServerDockerFailedError): return QObject::tr("Server error: Docker failed");
case(ServerCancelInstallation): errorMessage = QObject::tr("Installation canceled by user"); break; case(ServerCancelInstallation): return QObject::tr("Installation canceled by user");
case(ServerUserNotInSudo): errorMessage = QObject::tr("The user does not have permission to use sudo"); break; case(ServerUserNotInSudo): return QObject::tr("The user does not have permission to use sudo");
// Libssh errors // Libssh errors
case(SshRequestDeniedError): errorMessage = QObject::tr("Ssh request was denied"); break; case(SshRequestDeniedError): return QObject::tr("Ssh request was denied");
case(SshInterruptedError): errorMessage = QObject::tr("Ssh request was interrupted"); break; case(SshInterruptedError): return QObject::tr("Ssh request was interrupted");
case(SshInternalError): errorMessage = QObject::tr("Ssh internal error"); break; case(SshInternalError): return QObject::tr("Ssh internal error");
case(SshPrivateKeyError): errorMessage = QObject::tr("Invalid private key or invalid passphrase entered"); break; case(SshPrivateKeyError): return QObject::tr("Invalid private key or invalid passphrase entered");
case(SshPrivateKeyFormatError): errorMessage = QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types"); break; case(SshPrivateKeyFormatError): return QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types");
case(SshTimeoutError): errorMessage = QObject::tr("Timeout connecting to server"); break; case(SshTimeoutError): return QObject::tr("Timeout connecting to server");
// Libssh sftp errors // Libssh sftp errors
case(SshSftpEofError): errorMessage = QObject::tr("Sftp error: End-of-file encountered"); break; case(SshSftpEofError): return QObject::tr("Sftp error: End-of-file encountered");
case(SshSftpNoSuchFileError): errorMessage = QObject::tr("Sftp error: File does not exist"); break; case(SshSftpNoSuchFileError): return QObject::tr("Sftp error: File does not exist");
case(SshSftpPermissionDeniedError): errorMessage = QObject::tr("Sftp error: Permission denied"); break; case(SshSftpPermissionDeniedError): return QObject::tr("Sftp error: Permission denied");
case(SshSftpFailureError): errorMessage = QObject::tr("Sftp error: Generic failure"); break; case(SshSftpFailureError): return QObject::tr("Sftp error: Generic failure");
case(SshSftpBadMessageError): errorMessage = QObject::tr("Sftp error: Garbage received from server"); break; case(SshSftpBadMessageError): return QObject::tr("Sftp error: Garbage received from server");
case(SshSftpNoConnectionError): errorMessage = QObject::tr("Sftp error: No connection has been set up"); break; case(SshSftpNoConnectionError): return QObject::tr("Sftp error: No connection has been set up");
case(SshSftpConnectionLostError): errorMessage = QObject::tr("Sftp error: There was a connection, but we lost it"); break; case(SshSftpConnectionLostError): return QObject::tr("Sftp error: There was a connection, but we lost it");
case(SshSftpOpUnsupportedError): errorMessage = QObject::tr("Sftp error: Operation not supported by libssh yet"); break; case(SshSftpOpUnsupportedError): return QObject::tr("Sftp error: Operation not supported by libssh yet");
case(SshSftpInvalidHandleError): errorMessage = QObject::tr("Sftp error: Invalid file handle"); break; case(SshSftpInvalidHandleError): return QObject::tr("Sftp error: Invalid file handle");
case(SshSftpNoSuchPathError): errorMessage = QObject::tr("Sftp error: No such file or directory path exists"); break; case(SshSftpNoSuchPathError): return QObject::tr("Sftp error: No such file or directory path exists");
case(SshSftpFileAlreadyExistsError): errorMessage = QObject::tr("Sftp error: An attempt to create an already existing file or directory has been made"); break; case(SshSftpFileAlreadyExistsError): return QObject::tr("Sftp error: An attempt to create an already existing file or directory has been made");
case(SshSftpWriteProtectError): errorMessage = QObject::tr("Sftp error: Write-protected filesystem"); break; case(SshSftpWriteProtectError): return QObject::tr("Sftp error: Write-protected filesystem");
case(SshSftpNoMediaError): errorMessage = QObject::tr("Sftp error: No media was in remote drive"); break; case(SshSftpNoMediaError): return QObject::tr("Sftp error: No media was in remote drive");
// Local errors // Local errors
case (OpenVpnConfigMissing): errorMessage = QObject::tr("OpenVPN config missing"); break; case (OpenVpnConfigMissing): return QObject::tr("OpenVPN config missing");
case (OpenVpnManagementServerError): errorMessage = QObject::tr("OpenVPN management server error"); break; case (OpenVpnManagementServerError): return QObject::tr("OpenVPN management server error");
// Distro errors // Distro errors
case (OpenVpnExecutableMissing): errorMessage = QObject::tr("OpenVPN executable missing"); break; case (OpenVpnExecutableMissing): return QObject::tr("OpenVPN executable missing");
case (ShadowSocksExecutableMissing): errorMessage = QObject::tr("ShadowSocks (ss-local) executable missing"); break; case (ShadowSocksExecutableMissing): return QObject::tr("ShadowSocks (ss-local) executable missing");
case (CloakExecutableMissing): errorMessage = QObject::tr("Cloak (ck-client) executable missing"); break; case (CloakExecutableMissing): return QObject::tr("Cloak (ck-client) executable missing");
case (AmneziaServiceConnectionFailed): errorMessage = QObject::tr("Amnezia helper service error"); break; case (AmneziaServiceConnectionFailed): return QObject::tr("Amnezia helper service error");
case (OpenSslFailed): errorMessage = QObject::tr("OpenSSL failed"); break; case (OpenSslFailed): return QObject::tr("OpenSSL failed");
// VPN errors // VPN errors
case (OpenVpnAdaptersInUseError): errorMessage = QObject::tr("Can't connect: another VPN connection is active"); break; case (OpenVpnAdaptersInUseError): return QObject::tr("Can't connect: another VPN connection is active");
case (OpenVpnTapAdapterError): errorMessage = QObject::tr("Can't setup OpenVPN TAP network adapter"); break; case (OpenVpnTapAdapterError): return QObject::tr("Can't setup OpenVPN TAP network adapter");
case (AddressPoolError): errorMessage = QObject::tr("VPN pool error: no available addresses"); break; case (AddressPoolError): return QObject::tr("VPN pool error: no available addresses");
case (ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break; case (ImportInvalidConfigError): return QObject::tr("The config does not contain any containers and credentials for connecting to the server");
// Android errors // Android errors
case (AndroidError): errorMessage = QObject::tr("VPN connection error"); break; case (AndroidError): return QObject::tr("VPN connection error");
case(InternalError): case(InternalError):
default: default:
errorMessage = QObject::tr("Internal error"); break; return QObject::tr("Internal error");
} }
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,7 +33,6 @@ 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();
} }
} }
@@ -47,7 +46,6 @@ 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();
} }
} }
+1 -1
View File
@@ -58,7 +58,7 @@ target_link_libraries(networkextension PRIVATE ${FW_UI_KIT})
target_compile_options(networkextension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\") target_compile_options(networkextension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\")
target_compile_options(networkextension PRIVATE -DNETWORK_EXTENSION=1) target_compile_options(networkextension PRIVATE -DNETWORK_EXTENSION=1)
set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/amneziawg-apple/Sources) set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/awg-apple/Sources)
target_sources(networkextension PRIVATE target_sources(networkextension PRIVATE
${WG_APPLE_SOURCE_DIR}/WireGuardKit/WireGuardAdapter.swift ${WG_APPLE_SOURCE_DIR}/WireGuardKit/WireGuardAdapter.swift
@@ -1,6 +1,6 @@
#include "wireguard-go-version.h" #include "wireguard-go-version.h"
#include "3rd/amneziawg-apple/Sources/WireGuardKitGo/wireguard.h" #include "3rd/awg-apple/Sources/WireGuardKitGo/wireguard.h"
#include "3rd/amneziawg-apple/Sources/WireGuardKitC/WireGuardKitC.h" #include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
+1 -1
View File
@@ -29,7 +29,7 @@ void debugMessageHandler(QtMsgType type, const QMessageLogContext& context, cons
} }
// Skip annoying messages from Qt // Skip annoying messages from Qt
if (msg.startsWith("Unknown property") || msg.startsWith("Could not create pixmap") || msg.startsWith("Populating font") || msg.startsWith("stale focus object")) { if (msg.startsWith("Unknown property") || msg.startsWith("Could not create pixmap") || msg.startsWith("Populating font")) {
return; return;
} }
+30 -143
View File
@@ -1,10 +1,8 @@
#include <QCoreApplication>
#include <QJniEnvironment> #include <QJniEnvironment>
#include <QJsonDocument> #include <QJsonDocument>
#include <QQmlFile>
#include <QEventLoop>
#include "android_controller.h" #include "android_controller.h"
#include "android_utils.h"
#include "ui/controllers/importController.h" #include "ui/controllers/importController.h"
namespace namespace
@@ -12,15 +10,13 @@ namespace
AndroidController *s_instance = nullptr; AndroidController *s_instance = nullptr;
constexpr auto QT_ANDROID_CONTROLLER_CLASS = "org/amnezia/vpn/qt/QtAndroidController"; constexpr auto QT_ANDROID_CONTROLLER_CLASS = "org/amnezia/vpn/qt/QtAndroidController";
constexpr auto ANDROID_LOG_CLASS = "org/amnezia/vpn/util/Log";
constexpr auto TAG = "AmneziaQt";
} // namespace } // namespace
AndroidController::AndroidController() : QObject() AndroidController::AndroidController() : QObject()
{ {
connect(this, &AndroidController::status, this, connect(this, &AndroidController::status, this,
[this](AndroidController::ConnectionState state) { [this](AndroidController::ConnectionState state) {
qDebug() << "Android event: status =" << textConnectionState(state); qDebug() << "Android event: status; state:" << textConnectionState(state);
if (isWaitingStatus) { if (isWaitingStatus) {
qDebug() << "Initialization by service status"; qDebug() << "Initialization by service status";
isWaitingStatus = false; isWaitingStatus = false;
@@ -110,7 +106,6 @@ bool AndroidController::initialize()
{"onVpnDisconnected", "()V", reinterpret_cast<void *>(onVpnDisconnected)}, {"onVpnDisconnected", "()V", reinterpret_cast<void *>(onVpnDisconnected)},
{"onVpnReconnecting", "()V", reinterpret_cast<void *>(onVpnReconnecting)}, {"onVpnReconnecting", "()V", reinterpret_cast<void *>(onVpnReconnecting)},
{"onStatisticsUpdate", "(JJ)V", reinterpret_cast<void *>(onStatisticsUpdate)}, {"onStatisticsUpdate", "(JJ)V", reinterpret_cast<void *>(onStatisticsUpdate)},
{"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onFileOpened)},
{"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onConfigImported)}, {"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onConfigImported)},
{"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)} {"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)}
}; };
@@ -128,19 +123,24 @@ bool AndroidController::initialize()
// static // static
template <typename Ret, typename ...Args> template <typename Ret, typename ...Args>
auto AndroidController::callActivityMethod(const char *methodName, const char *signature, Args &&...args) auto AndroidController::callActivityMethod(const char *methodName, const char *signature,
const std::function<Ret()> &defValue, Args &&...args)
{ {
qDebug() << "Call activity method:" << methodName; qDebug() << "Call activity method:" << methodName;
QJniObject activity = AndroidUtils::getActivity(); QJniObject activity = QNativeInterface::QAndroidApplication::context();
Q_ASSERT(activity.isValid()); if (activity.isValid()) {
return activity.callMethod<Ret>(methodName, signature, std::forward<Args>(args)...); return activity.callMethod<Ret>(methodName, signature, std::forward<Args>(args)...);
} else {
qCritical() << "Activity is not valid";
return defValue();
}
} }
// static // static
template <typename ...Args> template <typename ...Args>
void AndroidController::callActivityMethod(const char *methodName, const char *signature, Args &&...args) void AndroidController::callActivityMethod(const char *methodName, const char *signature, Args &&...args)
{ {
callActivityMethod<void>(methodName, signature, std::forward<Args>(args)...); callActivityMethod<void>(methodName, signature, [] {}, std::forward<Args>(args)...);
} }
ErrorCode AndroidController::start(const QJsonObject &vpnConfig) ErrorCode AndroidController::start(const QJsonObject &vpnConfig)
@@ -165,24 +165,6 @@ void AndroidController::saveFile(const QString &fileName, const QString &data)
QJniObject::fromString(data).object<jstring>()); QJniObject::fromString(data).object<jstring>());
} }
QString AndroidController::openFile(const QString &filter)
{
QEventLoop wait;
QString fileName;
connect(this, &AndroidController::fileOpened, this,
[&fileName, &wait](const QString &uri) {
qDebug() << "Android event: file opened; uri:" << uri;
fileName = QQmlFile::urlToLocalFileOrQrc(uri);
qDebug() << "Android opened filename:" << fileName;
wait.quit();
},
static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection));
callActivityMethod("openFile", "(Ljava/lang/String;)V",
QJniObject::fromString(filter).object<jstring>());
wait.exec();
return fileName;
}
void AndroidController::setNotificationText(const QString &title, const QString &message, int timerSec) void AndroidController::setNotificationText(const QString &title, const QString &message, int timerSec)
{ {
callActivityMethod("setNotificationText", "(Ljava/lang/String;Ljava/lang/String;I)V", callActivityMethod("setNotificationText", "(Ljava/lang/String;Ljava/lang/String;I)V",
@@ -191,114 +173,11 @@ void AndroidController::setNotificationText(const QString &title, const QString
(jint) timerSec); (jint) timerSec);
} }
bool AndroidController::isCameraPresent()
{
return callActivityMethod<jboolean>("isCameraPresent", "()Z");
}
void AndroidController::startQrReaderActivity() void AndroidController::startQrReaderActivity()
{ {
callActivityMethod("startQrCodeReader", "()V"); callActivityMethod("startQrCodeReader", "()V");
} }
void AndroidController::setSaveLogs(bool enabled)
{
callActivityMethod("setSaveLogs", "(Z)V", enabled);
}
void AndroidController::exportLogsFile(const QString &fileName)
{
callActivityMethod("exportLogsFile", "(Ljava/lang/String;)V",
QJniObject::fromString(fileName).object<jstring>());
}
void AndroidController::clearLogs()
{
callActivityMethod("clearLogs", "()V");
}
// Moving log processing to the Android side
jclass AndroidController::log;
jmethodID AndroidController::logDebug;
jmethodID AndroidController::logInfo;
jmethodID AndroidController::logWarning;
jmethodID AndroidController::logError;
jmethodID AndroidController::logFatal;
// static
bool AndroidController::initLogging()
{
QJniEnvironment env;
log = env.findClass(ANDROID_LOG_CLASS);
if (log == nullptr) {
qCritical() << "Android log class" << ANDROID_LOG_CLASS << "not found";
return false;
}
auto logMethodSignature = "(Ljava/lang/String;Ljava/lang/String;)V";
logDebug = env.findStaticMethod(log, "d", logMethodSignature);
if (logDebug == nullptr) {
qCritical() << "Android debug log method not found";
return false;
}
logInfo = env.findStaticMethod(log, "i", logMethodSignature);
if (logInfo == nullptr) {
qCritical() << "Android info log method not found";
return false;
}
logWarning = env.findStaticMethod(log, "w", logMethodSignature);
if (logWarning == nullptr) {
qCritical() << "Android warning log method not found";
return false;
}
logError = env.findStaticMethod(log, "e", logMethodSignature);
if (logError == nullptr) {
qCritical() << "Android error log method not found";
return false;
}
logFatal = env.findStaticMethod(log, "f", logMethodSignature);
if (logFatal == nullptr) {
qCritical() << "Android fatal log method not found";
return false;
}
qInstallMessageHandler(messageHandler);
return true;
}
// static
void AndroidController::messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message)
{
jmethodID logMethod = logDebug;
switch (type) {
case QtDebugMsg:
logMethod = logDebug;
break;
case QtInfoMsg:
logMethod = logInfo;
break;
case QtWarningMsg:
logMethod = logWarning;
break;
case QtCriticalMsg:
logMethod = logError;
break;
case QtFatalMsg:
logMethod = logFatal;
break;
}
QString formattedMessage = qFormatLogMessage(type, context, message);
QJniObject::callStaticMethod<void>(log, logMethod,
QJniObject::fromString(TAG).object<jstring>(),
QJniObject::fromString(formattedMessage).object<jstring>());
}
void AndroidController::qtAndroidControllerInitialized() void AndroidController::qtAndroidControllerInitialized()
{ {
callActivityMethod("qtAndroidControllerInitialized", "()V"); callActivityMethod("qtAndroidControllerInitialized", "()V");
@@ -405,20 +284,21 @@ void AndroidController::onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBy
emit AndroidController::instance()->statisticsUpdated((quint64) rxBytes, (quint64) txBytes); emit AndroidController::instance()->statisticsUpdated((quint64) rxBytes, (quint64) txBytes);
} }
// static
void AndroidController::onFileOpened(JNIEnv *env, jobject thiz, jstring uri)
{
Q_UNUSED(thiz);
emit AndroidController::instance()->fileOpened(AndroidUtils::convertJString(env, uri));
}
// static // static
void AndroidController::onConfigImported(JNIEnv *env, jobject thiz, jstring data) void AndroidController::onConfigImported(JNIEnv *env, jobject thiz, jstring data)
{ {
Q_UNUSED(env);
Q_UNUSED(thiz); Q_UNUSED(thiz);
emit AndroidController::instance()->configImported(AndroidUtils::convertJString(env, data)); const char *buffer = env->GetStringUTFChars(data, nullptr);
if (!buffer) {
return;
}
QString config(buffer);
env->ReleaseStringUTFChars(data, buffer);
emit AndroidController::instance()->configImported(config);
} }
// static // static
@@ -426,5 +306,12 @@ bool AndroidController::decodeQrCode(JNIEnv *env, jobject thiz, jstring data)
{ {
Q_UNUSED(thiz); Q_UNUSED(thiz);
return ImportController::decodeQrCode(AndroidUtils::convertJString(env, data)); const char *buffer = env->GetStringUTFChars(data, nullptr);
if (!buffer) {
return false;
}
QString code(buffer);
env->ReleaseStringUTFChars(data, buffer);
return ImportController::decodeQrCode(code);
} }
+4 -21
View File
@@ -18,8 +18,7 @@ public:
bool initialize(); bool initialize();
// keep synchronized with org.amnezia.vpn.protocol.ProtocolState // keep synchronized with org.amnezia.vpn.protocol.ProtocolState
enum class ConnectionState enum class ConnectionState {
{
CONNECTED, CONNECTED,
CONNECTING, CONNECTING,
DISCONNECTED, DISCONNECTED,
@@ -31,16 +30,8 @@ public:
ErrorCode start(const QJsonObject &vpnConfig); ErrorCode start(const QJsonObject &vpnConfig);
void stop(); void stop();
void setNotificationText(const QString &title, const QString &message, int timerSec); void setNotificationText(const QString &title, const QString &message, int timerSec);
void saveFile(const QString &fileName, const QString &data); void saveFile(const QString& fileName, const QString &data);
QString openFile(const QString &filter);
bool isCameraPresent();
void startQrReaderActivity(); void startQrReaderActivity();
void setSaveLogs(bool enabled);
void exportLogsFile(const QString &fileName);
void clearLogs();
static bool initLogging();
static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message);
signals: signals:
void connectionStateChanged(Vpn::ConnectionState state); void connectionStateChanged(Vpn::ConnectionState state);
@@ -52,7 +43,6 @@ signals:
void vpnDisconnected(); void vpnDisconnected();
void vpnReconnecting(); void vpnReconnecting();
void statisticsUpdated(quint64 rxBytes, quint64 txBytes); void statisticsUpdated(quint64 rxBytes, quint64 txBytes);
void fileOpened(QString uri);
void configImported(QString config); void configImported(QString config);
void importConfigFromOutside(QString config); void importConfigFromOutside(QString config);
void initConnectionState(Vpn::ConnectionState state); void initConnectionState(Vpn::ConnectionState state);
@@ -60,13 +50,6 @@ signals:
private: private:
bool isWaitingStatus = true; bool isWaitingStatus = true;
static jclass log;
static jmethodID logDebug;
static jmethodID logInfo;
static jmethodID logWarning;
static jmethodID logError;
static jmethodID logFatal;
void qtAndroidControllerInitialized(); void qtAndroidControllerInitialized();
static Vpn::ConnectionState convertState(ConnectionState state); static Vpn::ConnectionState convertState(ConnectionState state);
@@ -82,11 +65,11 @@ private:
static void onVpnReconnecting(JNIEnv *env, jobject thiz); static void onVpnReconnecting(JNIEnv *env, jobject thiz);
static void onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes); static void onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes);
static void onConfigImported(JNIEnv *env, jobject thiz, jstring data); static void onConfigImported(JNIEnv *env, jobject thiz, jstring data);
static void onFileOpened(JNIEnv *env, jobject thiz, jstring uri);
static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data); static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data);
template <typename Ret, typename ...Args> template <typename Ret, typename ...Args>
static auto callActivityMethod(const char *methodName, const char *signature, Args &&...args); static auto callActivityMethod(const char *methodName, const char *signature,
const std::function<Ret()> &defValue, Args &&...args);
template <typename ...Args> template <typename ...Args>
static void callActivityMethod(const char *methodName, const char *signature, Args &&...args); static void callActivityMethod(const char *methodName, const char *signature, Args &&...args);
}; };
@@ -1,30 +0,0 @@
#include <QCoreApplication>
#include "android_utils.h"
namespace AndroidUtils
{
QJniObject getActivity()
{
return QNativeInterface::QAndroidApplication::context();
}
QString convertJString(JNIEnv *env, jstring data)
{
int len = env->GetStringLength(data);
QString res(len, Qt::Uninitialized);
env->GetStringRegion(data, 0, len, reinterpret_cast<jchar *>(res.data()));
return res;
}
void runOnAndroidThreadSync(const std::function<void()> &runnable)
{
QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable).waitForFinished();
}
void runOnAndroidThreadAsync(const std::function<void()> &runnable)
{
QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable);
}
}
-16
View File
@@ -1,16 +0,0 @@
#ifndef ANDROID_UTILS_H
#define ANDROID_UTILS_H
#include <QJniObject>
namespace AndroidUtils
{
QJniObject getActivity();
QString convertJString(JNIEnv *env, jstring data);
void runOnAndroidThreadSync(const std::function<void()> &runnable);
void runOnAndroidThreadAsync(const std::function<void()> &runnable);
};
#endif // ANDROID_UTILS_H
+183
View File
@@ -0,0 +1,183 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "androidutils.h"
#include <QGuiApplication>
#include <QJniEnvironment>
#include <QJniObject>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkCookieJar>
#include <QTimer>
#include <QUrlQuery>
#include "jni.h"
namespace
{
AndroidUtils *s_instance = nullptr;
} // namespace
// static
QString AndroidUtils::GetDeviceName()
{
QJniEnvironment env;
jclass BUILD = env->FindClass("android/os/Build");
jfieldID model = env->GetStaticFieldID(BUILD, "MODEL", "Ljava/lang/String;");
jstring value = (jstring)env->GetStaticObjectField(BUILD, model);
if (!value) {
return QString("Android Device");
}
const char *buffer = env->GetStringUTFChars(value, nullptr);
if (!buffer) {
return QString("Android Device");
}
QString res(buffer);
env->ReleaseStringUTFChars(value, buffer);
return res;
};
// static
AndroidUtils *AndroidUtils::instance()
{
if (!s_instance) {
Q_ASSERT(qApp);
s_instance = new AndroidUtils(qApp);
}
return s_instance;
}
AndroidUtils::AndroidUtils(QObject *parent) : QObject(parent)
{
Q_ASSERT(!s_instance);
s_instance = this;
}
AndroidUtils::~AndroidUtils()
{
Q_ASSERT(s_instance == this);
s_instance = nullptr;
}
// static
void AndroidUtils::dispatchToMainThread(std::function<void()> callback)
{
QTimer *timer = new QTimer();
timer->moveToThread(qApp->thread());
timer->setSingleShot(true);
QObject::connect(timer, &QTimer::timeout, [=]() {
callback();
timer->deleteLater();
});
QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection);
}
// static
QByteArray AndroidUtils::getQByteArrayFromJString(JNIEnv *env, jstring data)
{
const char *buffer = env->GetStringUTFChars(data, nullptr);
if (!buffer) {
qDebug() << "getQByteArrayFromJString - failed to parse data.";
return QByteArray();
}
QByteArray out(buffer);
env->ReleaseStringUTFChars(data, buffer);
return out;
}
// static
QString AndroidUtils::getQStringFromJString(JNIEnv *env, jstring data)
{
const char *buffer = env->GetStringUTFChars(data, nullptr);
if (!buffer) {
qDebug() << "getQStringFromJString - failed to parse data.";
return QString();
}
QString out(buffer);
env->ReleaseStringUTFChars(data, buffer);
return out;
}
// static
QJsonObject AndroidUtils::getQJsonObjectFromJString(JNIEnv *env, jstring data)
{
QByteArray raw(getQByteArrayFromJString(env, data));
QJsonParseError jsonError;
QJsonDocument json = QJsonDocument::fromJson(raw, &jsonError);
if (QJsonParseError::NoError != jsonError.error) {
qDebug() << "getQJsonObjectFromJstring - error parsing json. Code: " << jsonError.error
<< "Offset: " << jsonError.offset << "Message: " << jsonError.errorString() << "Data: " << raw;
return QJsonObject();
}
if (!json.isObject()) {
qDebug() << "getQJsonObjectFromJString - object expected.";
return QJsonObject();
}
return json.object();
}
QJniObject AndroidUtils::getActivity()
{
return QNativeInterface::QAndroidApplication::context();
}
int AndroidUtils::GetSDKVersion()
{
QJniEnvironment env;
jclass versionClass = env->FindClass("android/os/Build$VERSION");
jfieldID sdkIntFieldID = env->GetStaticFieldID(versionClass, "SDK_INT", "I");
int sdk = env->GetStaticIntField(versionClass, sdkIntFieldID);
return sdk;
}
QString AndroidUtils::GetManufacturer()
{
QJniEnvironment env;
jclass buildClass = env->FindClass("android/os/Build");
jfieldID manuFacturerField = env->GetStaticFieldID(buildClass, "MANUFACTURER", "Ljava/lang/String;");
jstring value = (jstring)env->GetStaticObjectField(buildClass, manuFacturerField);
const char *buffer = env->GetStringUTFChars(value, nullptr);
if (!buffer) {
qDebug() << "Failed to fetch MANUFACTURER";
return QByteArray();
}
QString res(buffer);
qDebug() << "MANUFACTURER: " << res;
env->ReleaseStringUTFChars(value, buffer);
return res;
}
void AndroidUtils::runOnAndroidThreadSync(const std::function<void()> runnable)
{
QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable).waitForFinished();
}
void AndroidUtils::runOnAndroidThreadAsync(const std::function<void()> runnable)
{
QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable);
}
// Static
// Creates a copy of the passed QByteArray in the JVM and passes back a ref
jbyteArray AndroidUtils::tojByteArray(const QByteArray &data)
{
QJniEnvironment env;
jbyteArray out = env->NewByteArray(data.size());
env->SetByteArrayRegion(out, 0, data.size(), reinterpret_cast<const jbyte *>(data.constData()));
return out;
}
+49
View File
@@ -0,0 +1,49 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef ANDROIDUTILS_H
#define ANDROIDUTILS_H
#include <jni.h>
#include <QJniEnvironment>
#include <QJniObject>
#include <QObject>
#include <QString>
#include <QUrl>
class AndroidUtils final : public QObject
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(AndroidUtils)
public:
static QString GetDeviceName();
static int GetSDKVersion();
static QString GetManufacturer();
static AndroidUtils* instance();
static void dispatchToMainThread(std::function<void()> callback);
static QByteArray getQByteArrayFromJString(JNIEnv* env, jstring data);
static jbyteArray tojByteArray(const QByteArray& data);
static QString getQStringFromJString(JNIEnv* env, jstring data);
static QJsonObject getQJsonObjectFromJString(JNIEnv* env, jstring data);
static QJniObject getActivity();
static void runOnAndroidThreadSync(const std::function<void()> runnable);
static void runOnAndroidThreadAsync(const std::function<void()> runnable);
private:
AndroidUtils(QObject* parent);
~AndroidUtils();
};
#endif // ANDROIDUTILS_H
-1
View File
@@ -43,7 +43,6 @@ bool MobileUtils::shareText(const QStringList& filesToSend) {
UIPopoverPresentationController *popController = activityController.popoverPresentationController; UIPopoverPresentationController *popController = activityController.popoverPresentationController;
if (popController) { if (popController) {
popController.sourceView = qtController.view; popController.sourceView = qtController.view;
popController.sourceRect = CGRectMake(100, 100, 100, 100);
} }
QEventLoop wait; QEventLoop wait;
@@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "3rd/amneziawg-apple/Sources/WireGuardKitC/WireGuardKitC.h" #include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
+5 -113
View File
@@ -249,107 +249,8 @@ void IosController::vpnStatusDidChange(void *pNotification)
NETunnelProviderSession *session = (NETunnelProviderSession *)pNotification; NETunnelProviderSession *session = (NETunnelProviderSession *)pNotification;
if (session /* && session == TunnelManager.session */ ) { if (session /* && session == TunnelManager.session */ ) {
qDebug() << "IosController::vpnStatusDidChange" << iosStatusToState(session.status) << session; qDebug() << "IosController::vpnStatusDidChange" << iosStatusToState(session.status) << session;
emit connectionStateChanged(iosStatusToState(session.status));
if (session.status == NEVPNStatusDisconnected) {
if (@available(iOS 16.0, *)) {
[session fetchLastDisconnectErrorWithCompletionHandler:^(NSError * _Nullable error) {
if (error != nil) {
qDebug() << "Disconnect error" << error.domain << error.code << error.localizedDescription;
if ([error.domain isEqualToString:NEVPNConnectionErrorDomain]) {
switch (error.code) {
case NEVPNConnectionErrorOverslept:
qDebug() << "Disconnect error info" << "The VPN connection was terminated because the system slept for an extended period of time.";
break;
case NEVPNConnectionErrorNoNetworkAvailable:
qDebug() << "Disconnect error info" << "The VPN connection could not be established because the system is not connected to a network.";
break;
case NEVPNConnectionErrorUnrecoverableNetworkChange:
qDebug() << "Disconnect error info" << "The VPN connection was terminated because the network conditions changed in such a way that the VPN connection could not be maintained.";
break;
case NEVPNConnectionErrorConfigurationFailed:
qDebug() << "Disconnect error info" << "The VPN connection could not be established because the configuration is invalid. ";
break;
case NEVPNConnectionErrorServerAddressResolutionFailed:
qDebug() << "Disconnect error info" << "The address of the VPN server could not be determined.";
break;
case NEVPNConnectionErrorServerNotResponding:
qDebug() << "Disconnect error info" << "Network communication with the VPN server has failed.";
break;
case NEVPNConnectionErrorServerDead:
qDebug() << "Disconnect error info" << "The VPN server is no longer functioning.";
break;
case NEVPNConnectionErrorAuthenticationFailed:
qDebug() << "Disconnect error info" << "The user credentials were rejected by the VPN server.";
break;
case NEVPNConnectionErrorClientCertificateInvalid:
qDebug() << "Disconnect error info" << "The client certificate is invalid.";
break;
case NEVPNConnectionErrorClientCertificateNotYetValid:
qDebug() << "Disconnect error info" << "The client certificate will not be valid until some future point in time.";
break;
case NEVPNConnectionErrorClientCertificateExpired:
qDebug() << "Disconnect error info" << "The validity period of the client certificate has passed.";
break;
case NEVPNConnectionErrorPluginFailed:
qDebug() << "Disconnect error info" << "The VPN plugin died unexpectedly.";
break;
case NEVPNConnectionErrorConfigurationNotFound:
qDebug() << "Disconnect error info" << "The VPN configuration could not be found.";
break;
case NEVPNConnectionErrorPluginDisabled:
qDebug() << "Disconnect error info" << "The VPN plugin could not be found or needed to be updated.";
break;
case NEVPNConnectionErrorNegotiationFailed:
qDebug() << "Disconnect error info" << "The VPN protocol negotiation failed.";
break;
case NEVPNConnectionErrorServerDisconnected:
qDebug() << "Disconnect error info" << "The VPN server terminated the connection.";
break;
case NEVPNConnectionErrorServerCertificateInvalid:
qDebug() << "Disconnect error info" << "The server certificate is invalid.";
break;
case NEVPNConnectionErrorServerCertificateNotYetValid:
qDebug() << "Disconnect error info" << "The server certificate will not be valid until some future point in time.";
break;
case NEVPNConnectionErrorServerCertificateExpired:
qDebug() << "Disconnect error info" << "The validity period of the server certificate has passed.";
break;
default:
qDebug() << "Disconnect error info" << "Unknown code.";
break;
}
}
NSError *underlyingError = error.userInfo[@"NSUnderlyingError"];
if (underlyingError != nil) {
qDebug() << "Disconnect underlying error" << underlyingError.domain << underlyingError.code << underlyingError.localizedDescription;
if ([underlyingError.domain isEqualToString:@"NEAgentErrorDomain"]) {
switch (underlyingError.code) {
case 1:
qDebug() << "Disconnect underlying error" << "General. Use sysdiagnose.";
break;
case 2:
qDebug() << "Disconnect underlying error" << "Plug-in unavailable. Use sysdiagnose.";
break;
default:
qDebug() << "Disconnect underlying error" << "Unknown code. Use sysdiagnose.";
break;
}
}
}
} else {
qDebug() << "Disconnect error is absent";
}
}];
} else {
qDebug() << "Disconnect error is unavailable on iOS < 16.0";
}
}
emit connectionStateChanged(iosStatusToState(session.status));
} }
} }
@@ -451,15 +352,6 @@ bool IosController::startWireGuard(const QString &config)
void IosController::startTunnel() void IosController::startTunnel()
{ {
NSString *protocolName = @"Unknown";
NETunnelProviderProtocol *tunnelProtocol = (NETunnelProviderProtocol *)m_currentTunnel.protocolConfiguration;
if (tunnelProtocol.providerConfiguration[@"wireguard"] != nil) {
protocolName = @"WireGuard";
} else if (tunnelProtocol.providerConfiguration[@"ovpn"] != nil) {
protocolName = @"OpenVPN";
}
m_rxBytes = 0; m_rxBytes = 0;
m_txBytes = 0; m_txBytes = 0;
@@ -481,7 +373,7 @@ void IosController::startTunnel()
[m_currentTunnel loadFromPreferencesWithCompletionHandler:^(NSError *loadError) { [m_currentTunnel loadFromPreferencesWithCompletionHandler:^(NSError *loadError) {
if (loadError) { if (loadError) {
qDebug().nospace() << "IosController::start" << protocolName << ": Connect " << protocolName << " Tunnel Load Error" << loadError.localizedDescription.UTF8String; qDebug() << "IosController::startOpenVPN : Connect OpenVPN Tunnel Load Error" << loadError.localizedDescription.UTF8String;
emit connectionStateChanged(Vpn::ConnectionState::Error); emit connectionStateChanged(Vpn::ConnectionState::Error);
return; return;
} }
@@ -509,11 +401,11 @@ void IosController::startTunnel()
BOOL started = [m_currentTunnel.connection startVPNTunnelWithOptions:nil andReturnError:&startError]; BOOL started = [m_currentTunnel.connection startVPNTunnelWithOptions:nil andReturnError:&startError];
if (!started || startError) { if (!started || startError) {
qDebug().nospace() << "IosController::start" << protocolName << " : Connect " << protocolName << " Tunnel Start Error" qDebug() << "IosController::startOpenVPN : Connect OpenVPN Tunnel Start Error"
<< (startError ? startError.localizedDescription.UTF8String : ""); << (startError ? startError.localizedDescription.UTF8String : "");
emit connectionStateChanged(Vpn::ConnectionState::Error); emit connectionStateChanged(Vpn::ConnectionState::Error);
} else { } else {
qDebug().nospace() << "IosController::start" << protocolName << " : Starting the tunnel succeeded"; qDebug() << "IosController::startOpenVPN : Starting the tunnel succeeded";
} }
}]; }];
}); });
+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)
} }
private func writeLog(to targetFile: String) -> Bool { 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)
} }
@@ -1,518 +0,0 @@
// 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"));
}
@@ -1,107 +0,0 @@
// 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,9 +11,7 @@
#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"
@@ -118,27 +116,7 @@ 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);
} }
@@ -162,9 +140,6 @@ 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;
} }
@@ -277,31 +252,6 @@ 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,11 +8,8 @@
#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
@@ -37,7 +34,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();
@@ -1,199 +0,0 @@
// 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);
}
@@ -1,90 +0,0 @@
// 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,30 +114,9 @@ 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);
} }
@@ -161,10 +140,6 @@ 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;
} }
@@ -327,31 +302,6 @@ 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,7 +10,6 @@
#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
@@ -35,7 +34,6 @@ 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();
} }
+10 -36
View File
@@ -4,7 +4,6 @@
#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"
@@ -54,11 +53,6 @@ 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);
} }
@@ -91,13 +85,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;
} }
} }
@@ -144,18 +138,12 @@ uint OpenVpnProtocol::selectMgmtPort()
void OpenVpnProtocol::updateRouteGateway(QString line) void OpenVpnProtocol::updateRouteGateway(QString line)
{ {
if (line.contains("net_route_v4_best_gw")) { // TODO: fix for macos
QStringList params = line.split(" "); line = line.split("ROUTE_GATEWAY", Qt::SkipEmptyParts).at(1);
if (params.size() == 6) { if (!line.contains("/"))
m_routeGateway = params.at(3); return;
} m_routeGateway = line.split("/", Qt::SkipEmptyParts).first();
} else { m_routeGateway.replace(" ", "");
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;
} }
@@ -294,7 +282,7 @@ void OpenVpnProtocol::onReadyReadDataFromManagementServer()
} }
} }
if (line.contains("ROUTE_GATEWAY") || line.contains("net_route_v4_best_gw")) { if (line.contains("ROUTE_GATEWAY")) {
updateRouteGateway(line); updateRouteGateway(line);
} }
@@ -332,28 +320,14 @@ 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,7 +44,6 @@ 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,6 +13,9 @@
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) {
@@ -47,9 +50,45 @@ 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,6 +26,15 @@ 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;
}; };
+15 -15
View File
@@ -25,7 +25,8 @@ SecureQSettings::SecureQSettings(const QString &organization, const QString &app
if (encryptionRequired() && !encrypted) { if (encryptionRequired() && !encrypted) {
for (const QString &key : m_settings.allKeys()) { for (const QString &key : m_settings.allKeys()) {
if (encryptedKeys.contains(key)) { if (encryptedKeys.contains(key)) {
const QVariant &val = value(key); QVariant val;
value(key, val);
setValue(key, val); setValue(key, val);
} }
} }
@@ -34,16 +35,18 @@ SecureQSettings::SecureQSettings(const QString &organization, const QString &app
} }
} }
QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue) const void SecureQSettings::value(const QString &key, QVariant &returnValue, const QVariant &defaultValue) const
{ {
QMutexLocker locker(&mutex);
if (m_cache.contains(key)) { if (m_cache.contains(key)) {
return m_cache.value(key); returnValue = m_cache.value(key);
return;
}
if (!m_settings.contains(key)) {
returnValue = defaultValue;
return;
} }
if (!m_settings.contains(key))
return defaultValue;
QVariant retVal; QVariant retVal;
@@ -54,7 +57,7 @@ QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue
if (getEncKey().isEmpty() || getEncIv().isEmpty()) { if (getEncKey().isEmpty() || getEncIv().isEmpty()) {
qCritical() << "SecureQSettings::setValue Decryption requested, but key is empty"; qCritical() << "SecureQSettings::setValue Decryption requested, but key is empty";
return {}; return;
} }
QByteArray encryptedValue = retVal.toByteArray().mid(magicString.size()); QByteArray encryptedValue = retVal.toByteArray().mid(magicString.size());
@@ -75,13 +78,11 @@ QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue
} }
m_cache.insert(key, retVal); m_cache.insert(key, retVal);
return retVal; returnValue = retVal;
} }
void SecureQSettings::setValue(const QString &key, const QVariant &value) void SecureQSettings::setValue(const QString &key, const QVariant &value)
{ {
QMutexLocker locker(&mutex);
if (encryptionRequired() && encryptedKeys.contains(key)) { if (encryptionRequired() && encryptedKeys.contains(key)) {
if (!getEncKey().isEmpty() && !getEncIv().isEmpty()) { if (!getEncKey().isEmpty() && !getEncIv().isEmpty()) {
QByteArray decryptedValue; QByteArray decryptedValue;
@@ -107,8 +108,6 @@ void SecureQSettings::setValue(const QString &key, const QVariant &value)
void SecureQSettings::remove(const QString &key) void SecureQSettings::remove(const QString &key)
{ {
QMutexLocker locker(&mutex);
m_settings.remove(key); m_settings.remove(key);
m_cache.remove(key); m_cache.remove(key);
@@ -125,7 +124,9 @@ QByteArray SecureQSettings::backupAppConfig() const
QJsonObject cfg; QJsonObject cfg;
for (const QString &key : m_settings.allKeys()) { for (const QString &key : m_settings.allKeys()) {
cfg.insert(key, QJsonValue::fromVariant(value(key))); QVariant v;
value(key, v);
cfg.insert(key, QJsonValue::fromVariant(v));
} }
return QJsonDocument(cfg).toJson(); return QJsonDocument(cfg).toJson();
@@ -253,7 +254,6 @@ void SecureQSettings::setSecTag(const QString &tag, const QByteArray &data)
void SecureQSettings::clearSettings() void SecureQSettings::clearSettings()
{ {
QMutexLocker locker(&mutex);
m_settings.clear(); m_settings.clear();
m_cache.clear(); m_cache.clear();
sync(); sync();
+2 -1
View File
@@ -20,8 +20,9 @@ public:
explicit SecureQSettings(const QString &organization, const QString &application = QString(), explicit SecureQSettings(const QString &organization, const QString &application = QString(),
QObject *parent = nullptr); QObject *parent = nullptr);
Q_INVOKABLE QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; Q_INVOKABLE void value(const QString &key, QVariant &eturnValue, const QVariant &defaultValue = QVariant()) const;
Q_INVOKABLE void setValue(const QString &key, const QVariant &value); Q_INVOKABLE void setValue(const QString &key, const QVariant &value);
void remove(const QString &key); void remove(const QString &key);
void sync(); void sync();
@@ -1,6 +1,5 @@
if which apt-get > /dev/null 2>&1; then LOCK_FILE="/var/lib/dpkg/lock-frontend";\ if which apt-get > /dev/null 2>&1; then LOCK_FILE="/var/lib/dpkg/lock-frontend";\
elif which dnf > /dev/null 2>&1; then LOCK_FILE="/var/run/dnf.pid";\ elif which dnf > /dev/null 2>&1; then LOCK_FILE="/var/run/dnf.pid";\
elif which yum > /dev/null 2>&1; then LOCK_FILE="/var/run/yum.pid";\ elif which yum > /dev/null 2>&1; then LOCK_FILE="/var/run/yum.pid";\
elif which pacman > /dev/null 2>&1; then LOCK_FILE="/var/lib/pacman/db.lck";\
else echo "Packet manager not found"; echo "Internal error"; exit 1; fi;\ else echo "Packet manager not found"; echo "Internal error"; exit 1; fi;\
if command -v fuser > /dev/null 2>&1; then sudo fuser $LOCK_FILE 2>/dev/null; else echo "fuser not installed"; fi if command -v fuser > /dev/null 2>&1; then sudo fuser $LOCK_FILE 2>/dev/null; else echo "fuser not installed"; fi
-1
View File
@@ -1,7 +1,6 @@
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\ if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); silent_inst="-yq install"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\ elif which dnf > /dev/null 2>&1; then pm=$(which dnf); silent_inst="-yq install"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\
elif which yum > /dev/null 2>&1; then pm=$(which yum); silent_inst="-y -q install"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\ elif which yum > /dev/null 2>&1; then pm=$(which yum); silent_inst="-y -q install"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); silent_inst="-S --noconfirm --noprogressbar --quiet"; check_pkgs="> /dev/null 2>&1"; docker_pkg="docker"; dist="archlinux";\
else echo "Packet manager not found"; exit 1; fi;\ else echo "Packet manager not found"; exit 1; fi;\
echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg";\ echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg";\
if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\ if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\
+26 -20
View File
@@ -39,12 +39,12 @@ Settings::Settings(QObject *parent) : QObject(parent), m_settings(ORGANIZATION_N
} }
} }
int Settings::serversCount() const int Settings::serversCount()
{ {
return serversArray().size(); return serversArray().size();
} }
QJsonObject Settings::server(int index) const QJsonObject Settings::server(int index)
{ {
const QJsonArray &servers = serversArray(); const QJsonArray &servers = serversArray();
if (index >= servers.size()) if (index >= servers.size())
@@ -88,12 +88,12 @@ void Settings::setDefaultContainer(int serverIndex, DockerContainer container)
editServer(serverIndex, s); editServer(serverIndex, s);
} }
DockerContainer Settings::defaultContainer(int serverIndex) const DockerContainer Settings::defaultContainer(int serverIndex)
{ {
return ContainerProps::containerFromString(defaultContainerName(serverIndex)); return ContainerProps::containerFromString(defaultContainerName(serverIndex));
} }
QString Settings::defaultContainerName(int serverIndex) const QString Settings::defaultContainerName(int serverIndex)
{ {
QString name = server(serverIndex).value(config_key::defaultContainer).toString(); QString name = server(serverIndex).value(config_key::defaultContainer).toString();
if (name.isEmpty()) { if (name.isEmpty()) {
@@ -102,7 +102,7 @@ QString Settings::defaultContainerName(int serverIndex) const
return name; return name;
} }
QMap<DockerContainer, QJsonObject> Settings::containers(int serverIndex) const QMap<DockerContainer, QJsonObject> Settings::containers(int serverIndex)
{ {
const QJsonArray &containers = server(serverIndex).value(config_key::containers).toArray(); const QJsonArray &containers = server(serverIndex).value(config_key::containers).toArray();
@@ -186,7 +186,7 @@ void Settings::clearLastConnectionConfig(int serverIndex, DockerContainer contai
setProtocolConfig(serverIndex, container, proto, c); setProtocolConfig(serverIndex, container, proto, c);
} }
bool Settings::haveAuthData(int serverIndex) const bool Settings::haveAuthData(int serverIndex)
{ {
if (serverIndex < 0) if (serverIndex < 0)
return false; return false;
@@ -194,7 +194,7 @@ bool Settings::haveAuthData(int serverIndex) const
return (!cred.hostName.isEmpty() && !cred.userName.isEmpty() && !cred.secretData.isEmpty()); return (!cred.hostName.isEmpty() && !cred.userName.isEmpty() && !cred.secretData.isEmpty());
} }
QString Settings::nextAvailableServerName() const QString Settings::nextAvailableServerName()
{ {
int i = 0; int i = 0;
bool nameExist = false; bool nameExist = false;
@@ -216,7 +216,6 @@ QString Settings::nextAvailableServerName() const
void Settings::setSaveLogs(bool enabled) void Settings::setSaveLogs(bool enabled)
{ {
setValue("Conf/saveLogs", enabled); setValue("Conf/saveLogs", enabled);
#ifndef Q_OS_ANDROID
if (!isSaveLogs()) { if (!isSaveLogs()) {
Logger::deInit(); Logger::deInit();
} else { } else {
@@ -224,11 +223,10 @@ void Settings::setSaveLogs(bool enabled)
qWarning() << "Initialization of debug subsystem failed"; qWarning() << "Initialization of debug subsystem failed";
} }
} }
#endif emit saveLogsChanged();
emit saveLogsChanged(enabled);
} }
QString Settings::routeModeString(RouteMode mode) const QString Settings::routeModeString(RouteMode mode)
{ {
switch (mode) { switch (mode) {
case VpnAllSites: return "AllSites"; case VpnAllSites: return "AllSites";
@@ -237,7 +235,7 @@ QString Settings::routeModeString(RouteMode mode) const
} }
} }
Settings::RouteMode Settings::routeMode() const Settings::RouteMode Settings::routeMode()
{ {
return static_cast<RouteMode>(value("Conf/routeMode", 0).toInt()); return static_cast<RouteMode>(value("Conf/routeMode", 0).toInt());
} }
@@ -269,7 +267,7 @@ void Settings::addVpnSites(RouteMode mode, const QMap<QString, QString> &sites)
setVpnSites(mode, allSites); setVpnSites(mode, allSites);
} }
QStringList Settings::getVpnIps(RouteMode mode) const QStringList Settings::getVpnIps(RouteMode mode)
{ {
QStringList ips; QStringList ips;
const QVariantMap &m = vpnSites(mode); const QVariantMap &m = vpnSites(mode);
@@ -325,12 +323,12 @@ void Settings::removeAllVpnSites(RouteMode mode)
setVpnSites(mode, QVariantMap()); setVpnSites(mode, QVariantMap());
} }
QString Settings::primaryDns() const QString Settings::primaryDns()
{ {
return value("Conf/primaryDns", cloudFlareNs1).toString(); return value("Conf/primaryDns", cloudFlareNs1).toString();
} }
QString Settings::secondaryDns() const QString Settings::secondaryDns()
{ {
return value("Conf/secondaryDns", cloudFlareNs2).toString(); return value("Conf/secondaryDns", cloudFlareNs2).toString();
} }
@@ -340,12 +338,12 @@ void Settings::clearSettings()
m_settings.clearSettings(); m_settings.clearSettings();
} }
ServerCredentials Settings::defaultServerCredentials() const ServerCredentials Settings::defaultServerCredentials()
{ {
return serverCredentials(defaultServerIndex()); return serverCredentials(defaultServerIndex());
} }
ServerCredentials Settings::serverCredentials(int index) const ServerCredentials Settings::serverCredentials(int index)
{ {
const QJsonObject &s = server(index); const QJsonObject &s = server(index);
@@ -358,18 +356,26 @@ ServerCredentials Settings::serverCredentials(int index) const
return credentials; return credentials;
} }
QVariant Settings::value(const QString &key, const QVariant &defaultValue) const QVariant Settings::value(const QString &key, const QVariant &defaultValue)
{ {
QVariant returnValue; QVariant returnValue;
// if (defaultValue.isNull() || !defaultValue.isValid()) {
// QMetaObject::invokeMethod(&m_settings, "value",
// Qt::QueuedConnection,
// Q_ARG(const QString&, key),
// Q_ARG(QVariant, returnValue));
// } else {
if (QThread::currentThread() == QCoreApplication::instance()->thread()) { if (QThread::currentThread() == QCoreApplication::instance()->thread()) {
returnValue = m_settings.value(key, defaultValue); m_settings.value(key, returnValue, defaultValue);
} else { } else {
QMetaObject::invokeMethod(&m_settings, "value", QMetaObject::invokeMethod(&m_settings, "value",
Qt::BlockingQueuedConnection, Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QVariant, returnValue),
Q_ARG(const QString&, key), Q_ARG(const QString&, key),
Q_ARG(QVariant&, returnValue),
Q_ARG(const QVariant&, defaultValue)); Q_ARG(const QVariant&, defaultValue));
} }
// }
return returnValue; return returnValue;
} }
+30 -30
View File
@@ -24,10 +24,10 @@ class Settings : public QObject
public: public:
explicit Settings(QObject *parent = nullptr); explicit Settings(QObject *parent = nullptr);
ServerCredentials defaultServerCredentials() const; ServerCredentials defaultServerCredentials();
ServerCredentials serverCredentials(int index) const; ServerCredentials serverCredentials(int index);
QJsonArray serversArray() const QJsonArray serversArray()
{ {
return QJsonDocument::fromJson(value("Servers/serversList").toByteArray()).array(); return QJsonDocument::fromJson(value("Servers/serversList").toByteArray()).array();
} }
@@ -37,13 +37,13 @@ public:
} }
// Servers section // Servers section
int serversCount() const; int serversCount();
QJsonObject server(int index) const; QJsonObject server(int index);
void addServer(const QJsonObject &server); void addServer(const QJsonObject &server);
void removeServer(int index); void removeServer(int index);
bool editServer(int index, const QJsonObject &server); bool editServer(int index, const QJsonObject &server);
int defaultServerIndex() const int defaultServerIndex()
{ {
return value("Servers/defaultServerIndex", 0).toInt(); return value("Servers/defaultServerIndex", 0).toInt();
} }
@@ -51,16 +51,16 @@ public:
{ {
setValue("Servers/defaultServerIndex", index); setValue("Servers/defaultServerIndex", index);
} }
QJsonObject defaultServer() const QJsonObject defaultServer()
{ {
return server(defaultServerIndex()); return server(defaultServerIndex());
} }
void setDefaultContainer(int serverIndex, DockerContainer container); void setDefaultContainer(int serverIndex, DockerContainer container);
DockerContainer defaultContainer(int serverIndex) const; DockerContainer defaultContainer(int serverIndex);
QString defaultContainerName(int serverIndex) const; QString defaultContainerName(int serverIndex);
QMap<DockerContainer, QJsonObject> containers(int serverIndex) const; QMap<DockerContainer, QJsonObject> containers(int serverIndex);
void setContainers(int serverIndex, const QMap<DockerContainer, QJsonObject> &containers); void setContainers(int serverIndex, const QMap<DockerContainer, QJsonObject> &containers);
QJsonObject containerConfig(int serverIndex, DockerContainer container); QJsonObject containerConfig(int serverIndex, DockerContainer container);
@@ -72,11 +72,11 @@ public:
void clearLastConnectionConfig(int serverIndex, DockerContainer container, Proto proto = Proto::Any); void clearLastConnectionConfig(int serverIndex, DockerContainer container, Proto proto = Proto::Any);
bool haveAuthData(int serverIndex) const; bool haveAuthData(int serverIndex);
QString nextAvailableServerName() const; QString nextAvailableServerName();
// App settings section // App settings section
bool isAutoConnect() const bool isAutoConnect()
{ {
return value("Conf/autoConnect", false).toBool(); return value("Conf/autoConnect", false).toBool();
} }
@@ -85,7 +85,7 @@ public:
setValue("Conf/autoConnect", enabled); setValue("Conf/autoConnect", enabled);
} }
bool isStartMinimized() const bool isStartMinimized()
{ {
return value("Conf/startMinimized", false).toBool(); return value("Conf/startMinimized", false).toBool();
} }
@@ -94,7 +94,7 @@ public:
setValue("Conf/startMinimized", enabled); setValue("Conf/startMinimized", enabled);
} }
bool isSaveLogs() const bool isSaveLogs()
{ {
return value("Conf/saveLogs", false).toBool(); return value("Conf/saveLogs", false).toBool();
} }
@@ -107,12 +107,12 @@ public:
}; };
Q_ENUM(RouteMode) Q_ENUM(RouteMode)
QString routeModeString(RouteMode mode) const; QString routeModeString(RouteMode mode);
RouteMode routeMode() const; RouteMode routeMode();
void setRouteMode(RouteMode mode) { setValue("Conf/routeMode", mode); } void setRouteMode(RouteMode mode) { setValue("Conf/routeMode", mode); }
QVariantMap vpnSites(RouteMode mode) const QVariantMap vpnSites(RouteMode mode)
{ {
return value("Conf/" + routeModeString(mode)).toMap(); return value("Conf/" + routeModeString(mode)).toMap();
} }
@@ -123,14 +123,14 @@ public:
} }
bool addVpnSite(RouteMode mode, const QString &site, const QString &ip = ""); bool addVpnSite(RouteMode mode, const QString &site, const QString &ip = "");
void addVpnSites(RouteMode mode, const QMap<QString, QString> &sites); // map <site, ip> void addVpnSites(RouteMode mode, const QMap<QString, QString> &sites); // map <site, ip>
QStringList getVpnIps(RouteMode mode) const; QStringList getVpnIps(RouteMode mode);
void removeVpnSite(RouteMode mode, const QString &site); void removeVpnSite(RouteMode mode, const QString &site);
void addVpnIps(RouteMode mode, const QStringList &ip); void addVpnIps(RouteMode mode, const QStringList &ip);
void removeVpnSites(RouteMode mode, const QStringList &sites); void removeVpnSites(RouteMode mode, const QStringList &sites);
void removeAllVpnSites(RouteMode mode); void removeAllVpnSites(RouteMode mode);
bool useAmneziaDns() const bool useAmneziaDns()
{ {
return value("Conf/useAmneziaDns", true).toBool(); return value("Conf/useAmneziaDns", true).toBool();
} }
@@ -139,16 +139,16 @@ public:
setValue("Conf/useAmneziaDns", enabled); setValue("Conf/useAmneziaDns", enabled);
} }
QString primaryDns() const; QString primaryDns();
QString secondaryDns() const; QString secondaryDns();
// QString primaryDns() const { return m_primaryDns; } // QString primaryDns() { return m_primaryDns; }
void setPrimaryDns(const QString &primaryDns) void setPrimaryDns(const QString &primaryDns)
{ {
setValue("Conf/primaryDns", primaryDns); setValue("Conf/primaryDns", primaryDns);
} }
// QString secondaryDns() const { return m_secondaryDns; } // QString secondaryDns() { return m_secondaryDns; }
void setSecondaryDns(const QString &secondaryDns) void setSecondaryDns(const QString &secondaryDns)
{ {
setValue("Conf/secondaryDns", secondaryDns); setValue("Conf/secondaryDns", secondaryDns);
@@ -160,7 +160,7 @@ public:
// static constexpr char openNicNs5[] = "94.103.153.176"; // static constexpr char openNicNs5[] = "94.103.153.176";
// static constexpr char openNicNs13[] = "144.76.103.143"; // static constexpr char openNicNs13[] = "144.76.103.143";
QByteArray backupAppConfig() const QByteArray backupAppConfig()
{ {
return m_settings.backupAppConfig(); return m_settings.backupAppConfig();
} }
@@ -178,7 +178,7 @@ public:
setValue("Conf/appLanguage", locale); setValue("Conf/appLanguage", locale);
}; };
bool isScreenshotsEnabled() const bool isScreenshotsEnabled()
{ {
return value("Conf/screenshotsEnabled", false).toBool(); return value("Conf/screenshotsEnabled", false).toBool();
} }
@@ -190,13 +190,13 @@ public:
void clearSettings(); void clearSettings();
signals: signals:
void saveLogsChanged(bool enabled); void saveLogsChanged();
private: private:
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; SecureQSettings m_settings;
void setValue(const QString &key, const QVariant &value);
mutable SecureQSettings m_settings; QVariant value(const QString &key, const QVariant &defaultValue = QVariant());
void setValue(const QString &key, const QVariant &value);
}; };
#endif // SETTINGS_H #endif // SETTINGS_H
+158 -206
View File
@@ -133,12 +133,12 @@
<context> <context>
<name>HomeContainersListView</name> <name>HomeContainersListView</name>
<message> <message>
<location filename="../ui/qml/Components/HomeContainersListView.qml" line="76"/> <location filename="../ui/qml/Components/HomeContainersListView.qml" line="58"/>
<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="85"/> <location filename="../ui/qml/Components/HomeContainersListView.qml" line="68"/>
<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="416"/> <location filename="../ui/controllers/importController.cpp" line="411"/>
<source>Scanned %1 of %2.</source> <source>Scanned %1 of %2.</source>
<translation>ارزیابی %1 از %2.</translation> <translation>ارزیابی %1 از %2.</translation>
</message> </message>
@@ -190,31 +190,26 @@ 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="324"/> <location filename="../ui/controllers/installController.cpp" line="315"/>
<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="341"/> <location filename="../ui/controllers/installController.cpp" line="332"/>
<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="487"/> <location filename="../ui/controllers/installController.cpp" line="478"/>
<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="515"/> <location filename="../ui/controllers/installController.cpp" line="506"/>
<source>Server added successfully</source> <source>Server added successfully</source>
<translation>سرور با موفقیت اضافه شد</translation> <translation>سرور با موفقیت اضافه شد</translation>
</message> </message>
@@ -282,17 +277,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="318"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="317"/>
<source>VPN protocol</source> <source>VPN protocol</source>
<translation>پروتکل ویپیان</translation> <translation>پروتکل ویپیان</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="362"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="361"/>
<source>Servers</source> <source>Servers</source>
<translation>سرورها</translation> <translation>سرورها</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="454"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="453"/>
<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>
@@ -951,11 +946,6 @@ Already installed containers were found on the server. All installed containers
<source>Show other methods on Github</source> <source>Show other methods on Github</source>
<translation>نمایش متدهای دیگر در گیت هاب</translation> <translation>نمایش متدهای دیگر در گیت هاب</translation>
</message> </message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsAbout.qml" line="104"/>
<source>https://github.com/amnezia-vpn/amnezia-client#donate</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsAbout.qml" line="113"/> <location filename="../ui/qml/Pages2/PageSettingsAbout.qml" line="113"/>
<source>Contacts</source> <source>Contacts</source>
@@ -1225,62 +1215,57 @@ 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="35"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="45"/>
<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="58"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="50"/>
<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="65"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="57"/>
<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="77"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="69"/>
<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="95"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="87"/>
<source>Restore default</source> <source>Restore default</source>
<translation>بازگشت به پیشفرض</translation> <translation>بازگشت به پیشفرض</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="98"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="90"/>
<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="99"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="91"/>
<source>Continue</source> <source>Continue</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="92"/>
<source>Cancel</source> <source>Cancel</source>
<translation>کنسل</translation> <translation>کنسل</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="108"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="100"/>
<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="120"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="112"/>
<source>Save</source> <source>Save</source>
<translation>ذخیره</translation> <translation>ذخیره</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="129"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="121"/>
<source>Settings saved</source> <source>Settings saved</source>
<translation>ذخیره تنظیمات</translation> <translation>ذخیره تنظیمات</translation>
</message> </message>
@@ -1298,52 +1283,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="87"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="86"/>
<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="109"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="108"/>
<source>Save</source> <source>Save</source>
<translation>ذخیره</translation> <translation>ذخیره</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="110"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="109"/>
<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="119"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="118"/>
<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="128"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="127"/>
<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="146"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="145"/>
<source>Clear logs?</source> <source>Clear logs?</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="146"/>
<source>Continue</source> <source>Continue</source>
<translation>ادامه</translation> <translation>ادامه</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="148"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="147"/>
<source>Cancel</source> <source>Cancel</source>
<translation>کنسل</translation> <translation>کنسل</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="155"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="154"/>
<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="168"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="167"/>
<source>Clear logs</source> <source>Clear logs</source>
<translation>پاک کردن گزارشات</translation> <translation>پاک کردن گزارشات</translation>
</message> </message>
@@ -1356,17 +1341,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="91"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="87"/>
<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="92"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="88"/>
<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="95"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="91"/>
<source>Clear cached profiles?</source> <source>Clear cached profiles?</source>
<translation>پاک کردن پروفایل ذخیره شده؟</translation> <translation>پاک کردن پروفایل ذخیره شده؟</translation>
</message> </message>
@@ -1381,81 +1366,56 @@ 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="97"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="93"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="145"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="140"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="177"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="171"/>
<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="98"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="94"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="146"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="141"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="178"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="172"/>
<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="121"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="117"/>
<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="122"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="118"/>
<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="139"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="134"/>
<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 type="vanished">حذف سرور؟</translation> <translation>حذف سرور؟</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="176"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="139"/>
<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="202"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="165"/>
<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 type="vanished">سرور از نرمافزار Amnezia پاک شود؟</translation> <translation>سرور از نرمافزار Amnezia پاک شود؟</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="207"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="170"/>
<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>
@@ -1536,95 +1496,90 @@ 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="29"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="53"/>
<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="64"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="58"/>
<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="96"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="90"/>
<source>Split tunneling</source> <source>Split tunneling</source>
<translation>جداسازی ترافیک</translation> <translation>جداسازی ترافیک</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="127"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="121"/>
<source>Mode</source> <source>Mode</source>
<translation>حالت</translation> <translation>حالت</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="205"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="199"/>
<source>Remove </source> <source>Remove </source>
<translation>حذف </translation> <translation>حذف </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="206"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="200"/>
<source>Continue</source> <source>Continue</source>
<translation>ادامه</translation> <translation>ادامه</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="207"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="201"/>
<source>Cancel</source> <source>Cancel</source>
<translation>کنسل</translation> <translation>کنسل</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="254"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="248"/>
<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="298"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="292"/>
<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="304"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="298"/>
<source>Import</source> <source>Import</source>
<translation>بارگذاری</translation> <translation>بارگذاری</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="316"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="310"/>
<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="323"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="317"/>
<source>Save sites</source> <source>Save sites</source>
<translation>ذخیره سایتها</translation> <translation>ذخیره سایتها</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="324"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="318"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="391"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="385"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="406"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="400"/>
<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="381"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="375"/>
<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="387"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="381"/>
<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="390"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="384"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="405"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="399"/>
<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="402"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="396"/>
<source>Add imported sites to existing ones</source> <source>Add imported sites to existing ones</source>
<translation>اضافه کردن سایتهای بارگذاری شده به سایتهای موجود</translation> <translation>اضافه کردن سایتهای بارگذاری شده به سایتهای موجود</translation>
</message> </message>
@@ -1751,22 +1706,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="139"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="137"/>
<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="140"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="138"/>
<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="159"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="157"/>
<source>Continue</source> <source>Continue</source>
<translation>ادامه</translation> <translation>ادامه</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="199"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="197"/>
<source>Set up later</source> <source>Set up later</source>
<translation>بعدا تنظیم شود</translation> <translation>بعدا تنظیم شود</translation>
</message> </message>
@@ -1774,7 +1729,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="61"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="60"/>
<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>
@@ -1787,33 +1742,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="67"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="66"/>
<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="68"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="67"/>
<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="69"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="68"/>
<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="127"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="126"/>
<source>Installing</source> <source>Installing</source>
<translation>در حال نصب</translation> <translation>در حال نصب</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="166"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="165"/>
<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="73"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="72"/>
<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>
@@ -1899,11 +1854,6 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<source>I have nothing</source> <source>I have nothing</source>
<translation>من هیچی ندارم</translation> <translation>من هیچی ندارم</translation>
</message> </message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="138"/>
<source>https://amnezia.org/instructions/0_starter-guide</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>PageSetupWizardTextKey</name> <name>PageSetupWizardTextKey</name>
@@ -1936,27 +1886,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="64"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="63"/>
<source>New connection</source> <source>New connection</source>
<translation>ارتباط جدید</translation> <translation>ارتباط جدید</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="91"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="90"/>
<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="106"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="105"/>
<source>Collapse content</source> <source>Collapse content</source>
<translation>جمع کردن محتوا</translation> <translation>جمع کردن محتوا</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="106"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="105"/>
<source>Show content</source> <source>Show content</source>
<translation>نمایش محتوا</translation> <translation>نمایش محتوا</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="149"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="148"/>
<source>Connect</source> <source>Connect</source>
<translation>اتصال</translation> <translation>اتصال</translation>
</message> </message>
@@ -2228,38 +2178,38 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>QKeychain::DeletePasswordJobPrivate</name> <name>QKeychain::DeletePasswordJobPrivate</name>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="104"/> <location filename="../3rd/qtkeychain/keychain_win.cpp" line="104"/>
<source>Password entry not found</source> <source>Password entry not found</source>
<translation>Password entry not found</translation> <translation>Password entry not found</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="108"/> <location filename="../3rd/qtkeychain/keychain_win.cpp" line="108"/>
<source>Could not decrypt data</source> <source>Could not decrypt data</source>
<translation>Could not decrypt data</translation> <translation>Could not decrypt data</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="585"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="552"/>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="593"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="560"/>
<source>Unknown error</source> <source>Unknown error</source>
<translation>Unknown error</translation> <translation>Unknown error</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="614"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="578"/>
<source>Could not open wallet: %1; %2</source> <source>Could not open wallet: %1; %2</source>
<translation>Could not open wallet: %1; %2</translation> <translation>Could not open wallet: %1; %2</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_haiku.cpp" line="177"/> <location filename="../3rd/qtkeychain/keychain_haiku.cpp" line="177"/>
<source>Password not found</source> <source>Password not found</source>
<translation>Password not found</translation> <translation>Password not found</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="173"/> <location filename="../3rd/qtkeychain/keychain_android.cpp" line="173"/>
<source>Could not open keystore</source> <source>Could not open keystore</source>
<translation>Could not open keystore</translation> <translation>Could not open keystore</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="179"/> <location filename="../3rd/qtkeychain/keychain_android.cpp" line="179"/>
<source>Could not remove private key from keystore</source> <source>Could not remove private key from keystore</source>
<translation>Could not remove private key from keystore</translation> <translation>Could not remove private key from keystore</translation>
</message> </message>
@@ -2267,12 +2217,12 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>QKeychain::JobPrivate</name> <name>QKeychain::JobPrivate</name>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="295"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="265"/>
<source>Unknown error</source> <source>Unknown error</source>
<translation>Unknown error</translation> <translation>Unknown error</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="542"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="509"/>
<source>Access to keychain denied</source> <source>Access to keychain denied</source>
<translation>Access to keychain denied</translation> <translation>Access to keychain denied</translation>
</message> </message>
@@ -2280,27 +2230,27 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>QKeychain::PlainTextStore</name> <name>QKeychain::PlainTextStore</name>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/plaintextstore.cpp" line="65"/> <location filename="../3rd/qtkeychain/plaintextstore.cpp" line="65"/>
<source>Could not store data in settings: access error</source> <source>Could not store data in settings: access error</source>
<translation>Could not store data in settings: access error</translation> <translation>Could not store data in settings: access error</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/plaintextstore.cpp" line="67"/> <location filename="../3rd/qtkeychain/plaintextstore.cpp" line="67"/>
<source>Could not store data in settings: format error</source> <source>Could not store data in settings: format error</source>
<translation>Could not store data in settings: format error</translation> <translation>Could not store data in settings: format error</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/plaintextstore.cpp" line="85"/> <location filename="../3rd/qtkeychain/plaintextstore.cpp" line="85"/>
<source>Could not delete data from settings: access error</source> <source>Could not delete data from settings: access error</source>
<translation>Could not delete data from settings: access error</translation> <translation>Could not delete data from settings: access error</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/plaintextstore.cpp" line="87"/> <location filename="../3rd/qtkeychain/plaintextstore.cpp" line="87"/>
<source>Could not delete data from settings: format error</source> <source>Could not delete data from settings: format error</source>
<translation>Could not delete data from settings: format error</translation> <translation>Could not delete data from settings: format error</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/plaintextstore.cpp" line="104"/> <location filename="../3rd/qtkeychain/plaintextstore.cpp" line="104"/>
<source>Entry not found</source> <source>Entry not found</source>
<translation>Entry not found</translation> <translation>Entry not found</translation>
</message> </message>
@@ -2308,80 +2258,80 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>QKeychain::ReadPasswordJobPrivate</name> <name>QKeychain::ReadPasswordJobPrivate</name>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="32"/> <location filename="../3rd/qtkeychain/keychain_win.cpp" line="32"/>
<source>Password entry not found</source> <source>Password entry not found</source>
<translation>Password entry not found</translation> <translation>Password entry not found</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="36"/> <location filename="../3rd/qtkeychain/keychain_win.cpp" line="36"/>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="139"/> <location filename="../3rd/qtkeychain/keychain_win.cpp" line="139"/>
<source>Could not decrypt data</source> <source>Could not decrypt data</source>
<translation>Could not decrypt data</translation> <translation>Could not decrypt data</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="205"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="178"/>
<source>D-Bus is not running</source> <source>D-Bus is not running</source>
<translation>D-Bus is not running</translation> <translation>D-Bus is not running</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="214"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="187"/>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="224"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="197"/>
<source>Unknown error</source> <source>Unknown error</source>
<translation>Unknown error</translation> <translation>Unknown error</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="316"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="286"/>
<source>No keychain service available</source> <source>No keychain service available</source>
<translation>No keychain service available</translation> <translation>No keychain service available</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="318"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="288"/>
<source>Could not open wallet: %1; %2</source> <source>Could not open wallet: %1; %2</source>
<translation>Could not open wallet: %1; %2</translation> <translation>Could not open wallet: %1; %2</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="363"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="333"/>
<source>Access to keychain denied</source> <source>Access to keychain denied</source>
<translation>Access to keychain denied</translation> <translation>Access to keychain denied</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="384"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="354"/>
<source>Could not determine data type: %1; %2</source> <source>Could not determine data type: %1; %2</source>
<translation>Could not determine data type: %1; %2</translation> <translation>Could not determine data type: %1; %2</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="393"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="363"/>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="52"/> <location filename="../3rd/qtkeychain/keychain_android.cpp" line="52"/>
<source>Entry not found</source> <source>Entry not found</source>
<translation>Entry not found</translation> <translation>Entry not found</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="402"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="372"/>
<source>Unsupported entry type &apos;Map&apos;</source> <source>Unsupported entry type &apos;Map&apos;</source>
<translation>Unsupported entry type &apos;Map&apos;</translation> <translation>Unsupported entry type &apos;Map&apos;</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="405"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="375"/>
<source>Unknown kwallet entry type &apos;%1&apos;</source> <source>Unknown kwallet entry type &apos;%1&apos;</source>
<translation>Unknown kwallet entry type &apos;%1&apos;</translation> <translation>Unknown kwallet entry type &apos;%1&apos;</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_haiku.cpp" line="96"/> <location filename="../3rd/qtkeychain/keychain_haiku.cpp" line="96"/>
<source>Password not found</source> <source>Password not found</source>
<translation>Password not found</translation> <translation>Password not found</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="60"/> <location filename="../3rd/qtkeychain/keychain_android.cpp" line="60"/>
<source>Could not open keystore</source> <source>Could not open keystore</source>
<translation>Could not open keystore</translation> <translation>Could not open keystore</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="68"/> <location filename="../3rd/qtkeychain/keychain_android.cpp" line="68"/>
<source>Could not retrieve private key from keystore</source> <source>Could not retrieve private key from keystore</source>
<translation>Could not retrieve private key from keystore</translation> <translation>Could not retrieve private key from keystore</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="75"/> <location filename="../3rd/qtkeychain/keychain_android.cpp" line="75"/>
<source>Could not create decryption cipher</source> <source>Could not create decryption cipher</source>
<translation>Could not create decryption cipher</translation> <translation>Could not create decryption cipher</translation>
</message> </message>
@@ -2389,73 +2339,73 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>QKeychain::WritePasswordJobPrivate</name> <name>QKeychain::WritePasswordJobPrivate</name>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="78"/> <location filename="../3rd/qtkeychain/keychain_win.cpp" line="78"/>
<source>Credential size exceeds maximum size of %1</source> <source>Credential size exceeds maximum size of %1</source>
<translation>Credential size exceeds maximum size of %1</translation> <translation>Credential size exceeds maximum size of %1</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="87"/> <location filename="../3rd/qtkeychain/keychain_win.cpp" line="87"/>
<source>Credential key exceeds maximum size of %1</source> <source>Credential key exceeds maximum size of %1</source>
<translation>Credential key exceeds maximum size of %1</translation> <translation>Credential key exceeds maximum size of %1</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="92"/> <location filename="../3rd/qtkeychain/keychain_win.cpp" line="92"/>
<source>Writing credentials failed: Win32 error code %1</source> <source>Writing credentials failed: Win32 error code %1</source>
<translation>Writing credentials failed: Win32 error code %1</translation> <translation>Writing credentials failed: Win32 error code %1</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="162"/> <location filename="../3rd/qtkeychain/keychain_win.cpp" line="162"/>
<source>Encryption failed</source> <source>Encryption failed</source>
<translation>Encryption failed</translation> <translation>Encryption failed</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="445"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="415"/>
<source>D-Bus is not running</source> <source>D-Bus is not running</source>
<translation>D-Bus is not running</translation> <translation>D-Bus is not running</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="455"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="425"/>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="482"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="452"/>
<source>Unknown error</source> <source>Unknown error</source>
<translation>Unknown error</translation> <translation>Unknown error</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="501"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="468"/>
<source>Could not open wallet: %1; %2</source> <source>Could not open wallet: %1; %2</source>
<translation>Could not open wallet: %1; %2</translation> <translation>Could not open wallet: %1; %2</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_haiku.cpp" line="144"/> <location filename="../3rd/qtkeychain/keychain_haiku.cpp" line="144"/>
<source>Password not found</source> <source>Password not found</source>
<translation>Password not found</translation> <translation>Password not found</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="95"/> <location filename="../3rd/qtkeychain/keychain_android.cpp" line="95"/>
<source>Could not open keystore</source> <source>Could not open keystore</source>
<translation>Could not open keystore</translation> <translation>Could not open keystore</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="124"/> <location filename="../3rd/qtkeychain/keychain_android.cpp" line="124"/>
<source>Could not create private key generator</source> <source>Could not create private key generator</source>
<translation>Could not create private key generator</translation> <translation>Could not create private key generator</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="131"/> <location filename="../3rd/qtkeychain/keychain_android.cpp" line="131"/>
<source>Could not generate new private key</source> <source>Could not generate new private key</source>
<translation>Could not generate new private key</translation> <translation>Could not generate new private key</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="139"/> <location filename="../3rd/qtkeychain/keychain_android.cpp" line="139"/>
<source>Could not retrieve private key from keystore</source> <source>Could not retrieve private key from keystore</source>
<translation>Could not retrieve private key from keystore</translation> <translation>Could not retrieve private key from keystore</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="147"/> <location filename="../3rd/qtkeychain/keychain_android.cpp" line="147"/>
<source>Could not create encryption cipher</source> <source>Could not create encryption cipher</source>
<translation>Could not create encryption cipher</translation> <translation>Could not create encryption cipher</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="155"/> <location filename="../3rd/qtkeychain/keychain_android.cpp" line="155"/>
<source>Could not encrypt data</source> <source>Could not encrypt data</source>
<translation>Could not encrypt data</translation> <translation>Could not encrypt data</translation>
</message> </message>
@@ -2905,72 +2855,74 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<translation>سرویس Sftp</translation> <translation>سرویس Sftp</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/libsecret.cpp" line="119"/> <location filename="../3rd/qtkeychain/libsecret.cpp" line="119"/>
<source>Entry not found</source> <source>Entry not found</source>
<translation>Entry not found</translation> <translation>Entry not found</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="255"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="225"/>
<source>Access to keychain denied</source> <source>Access to keychain denied</source>
<translation>Access to keychain denied</translation> <translation>Access to keychain denied</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="257"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="227"/>
<source>No keyring daemon</source> <source>No keyring daemon</source>
<translation>No keyring daemon</translation> <translation>No keyring daemon</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="259"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="229"/>
<source>Already unlocked</source> <source>Already unlocked</source>
<translation>Already unlocked</translation> <translation>Already unlocked</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="261"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="231"/>
<source>No such keyring</source> <source>No such keyring</source>
<translation>No such keyring</translation> <translation>No such keyring</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="263"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="233"/>
<source>Bad arguments</source> <source>Bad arguments</source>
<translation>Bad arguments</translation> <translation>Bad arguments</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="265"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="235"/>
<source>I/O error</source> <source>I/O error</source>
<translation>I/O error</translation> <translation>I/O error</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="267"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="237"/>
<source>Cancelled</source> <source>Cancelled</source>
<translation>Cancelled</translation> <translation>Cancelled</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="269"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="239"/>
<source>Keyring already exists</source> <source>Keyring already exists</source>
<translation>Keyring already exists</translation> <translation>Keyring already exists</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="271"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="241"/>
<source>No match</source> <source>No match</source>
<translation>No match</translation> <translation>No match</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="276"/> <location filename="../3rd/qtkeychain/keychain_unix.cpp" line="246"/>
<source>Unknown error</source> <source>Unknown error</source>
<translation>Unknown error</translation> <translation>Unknown error</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_haiku.cpp" line="72"/> <location filename="../3rd/qtkeychain/keychain_haiku.cpp" line="72"/>
<source>error 0x%1: %2</source> <source>error 0x%1: %2</source>
<translation>error 0x%1: %2</translation> <translation>error 0x%1: %2</translation>
</message> </message>
<message> <message>
<location filename="../3rd/wireguard-tools/contrib/highlighter/gui/highlight.cpp" line="39"/>
<source>WireGuard Configuration Highlighter</source> <source>WireGuard Configuration Highlighter</source>
<translation type="vanished">هایلایتر پیکربندی WireGuard</translation> <translation>هایلایتر پیکربندی WireGuard</translation>
</message> </message>
<message> <message>
<location filename="../3rd/wireguard-tools/contrib/highlighter/gui/highlight.cpp" line="82"/>
<source>&amp;Randomize colors</source> <source>&amp;Randomize colors</source>
<translation type="vanished">رنگهای تصادفی</translation> <translation>رنگهای تصادفی</translation>
</message> </message>
</context> </context>
<context> <context>
@@ -2984,13 +2936,13 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<context> <context>
<name>Settings</name> <name>Settings</name>
<message> <message>
<location filename="../settings.cpp" line="30"/> <location filename="../settings.cpp" line="26"/>
<source>Server #1</source> <source>Server #1</source>
<translation>Server #1</translation> <translation>Server #1</translation>
</message> </message>
<message> <message>
<location filename="../settings.cpp" line="206"/> <location filename="../settings.cpp" line="202"/>
<location filename="../settings.cpp" line="213"/> <location filename="../settings.cpp" line="209"/>
<source>Server</source> <source>Server</source>
<translation>Server</translation> <translation>Server</translation>
</message> </message>
@@ -2998,22 +2950,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="27"/> <location filename="../ui/controllers/settingsController.cpp" line="25"/>
<source>Software version</source> <source>Software version</source>
<translation>نسخه نرمافزار</translation> <translation>نسخه نرمافزار</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="148"/> <location filename="../ui/controllers/settingsController.cpp" line="137"/>
<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="154"/> <location filename="../ui/controllers/settingsController.cpp" line="143"/>
<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="132"/> <location filename="../ui/controllers/settingsController.cpp" line="122"/>
<source>Backup file is corrupted</source> <source>Backup file is corrupted</source>
<translation>فایل بکآپ خراب شده است</translation> <translation>فایل بکآپ خراب شده است</translation>
</message> </message>
@@ -3145,7 +3097,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="438"/> <location filename="../vpnconnection.cpp" line="432"/>
<source>Mbps</source> <source>Mbps</source>
<translation>Mbps</translation> <translation>Mbps</translation>
</message> </message>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+26 -33
View File
@@ -5,14 +5,12 @@
#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";
@@ -28,42 +26,33 @@ ApiController::ApiController(const QSharedPointer<ServersModel> &serversModel,
{ {
} }
void ApiController::processCloudConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, QString &config) QString ApiController::genPublicKey(const QString &protocol)
{
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", apiPayloadData.certRequest.privKey); config.replace("$OPENVPN_PRIV_KEY", m_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();
@@ -82,9 +71,13 @@ bool ApiController::updateServerConfigFromApi()
QString protocol = serverConfig.value(configKey::protocol).toString(); QString protocol = serverConfig.value(configKey::protocol).toString();
auto apiPayloadData = generateApiPayloadData(protocol); QJsonObject obj;
QByteArray requestBody = QJsonDocument(fillApiPayload(protocol, apiPayloadData)).toJson(); obj[configKey::publicKey] = genPublicKey(protocol);
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));
@@ -107,7 +100,7 @@ bool ApiController::updateServerConfigFromApi()
} }
QString configStr = ba; QString configStr = ba;
processCloudConfig(protocol, apiPayloadData, configStr); processCloudConfig(protocol, configStr);
QJsonObject cloudConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); QJsonObject cloudConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
+5 -9
View File
@@ -22,19 +22,15 @@ signals:
void errorOccurred(const QString &errorMessage); void errorOccurred(const QString &errorMessage);
private: private:
struct ApiPayloadData { QString genPublicKey(const QString &protocol);
OpenVpnConfigurator::ConnectionData certRequest; QString genCertificateRequest(const QString &protocol);
QString wireGuardClientPrivKey; void processCloudConfig(const QString &protocol, QString &config);
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 -3
View File
@@ -15,7 +15,7 @@
#include "core/errorstrings.h" #include "core/errorstrings.h"
#include "systemController.h" #include "systemController.h"
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
#include "platforms/android/android_utils.h" #include "platforms/android/androidutils.h"
#endif #endif
#include "qrcodegen.hpp" #include "qrcodegen.hpp"
@@ -327,8 +327,7 @@ 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));
} }
+1 -6
View File
@@ -239,12 +239,7 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
// && !configMap.value("PresharedKey").isEmpty() && !configMap.value("PublicKey").isEmpty()) { // && !configMap.value("PresharedKey").isEmpty() && !configMap.value("PublicKey").isEmpty()) {
lastConfig[config_key::client_priv_key] = configMap.value("PrivateKey"); lastConfig[config_key::client_priv_key] = configMap.value("PrivateKey");
lastConfig[config_key::client_ip] = configMap.value("Address"); lastConfig[config_key::client_ip] = configMap.value("Address");
if (!configMap.value("PresharedKey").isEmpty()) { lastConfig[config_key::psk_key] = configMap.value("PresharedKey");
lastConfig[config_key::psk_key] = configMap.value("PresharedKey");
} else if (!configMap.value("PreSharedKey").isEmpty()) {
lastConfig[config_key::psk_key] = configMap.value("PreSharedKey");
}
lastConfig[config_key::server_pub_key] = configMap.value("PublicKey"); lastConfig[config_key::server_pub_key] = configMap.value("PublicKey");
// } else { // } else {
// qDebug() << "Failed to import profile"; // qDebug() << "Failed to import profile";
@@ -296,15 +296,6 @@ 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,7 +31,6 @@ public slots:
void updateContainer(QJsonObject config); void updateContainer(QJsonObject config);
void removeCurrentlyProcessedServer(); void removeCurrentlyProcessedServer();
void rebootCurrentlyProcessedServer();
void removeAllContainers(); void removeAllContainers();
void removeCurrentlyProcessedContainer(); void removeCurrentlyProcessedContainer();
@@ -54,7 +53,6 @@ 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);
+1 -1
View File
@@ -7,7 +7,7 @@
#endif #endif
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
#include "platforms/android/android_utils.h" #include "../../platforms/android/androidutils.h"
#include <QJniObject> #include <QJniObject>
#endif #endif
#if defined Q_OS_MAC #if defined Q_OS_MAC
+2 -24
View File
@@ -7,21 +7,19 @@
#include "ui/qautostart.h" #include "ui/qautostart.h"
#include "version.h" #include "version.h"
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
#include "platforms/android/android_utils.h" #include "../../platforms/android/android_controller.h"
#include "platforms/android/android_controller.h" #include "../../platforms/android/androidutils.h"
#include <QJniObject> #include <QJniObject>
#endif #endif
SettingsController::SettingsController(const QSharedPointer<ServersModel> &serversModel, SettingsController::SettingsController(const QSharedPointer<ServersModel> &serversModel,
const QSharedPointer<ContainersModel> &containersModel, const QSharedPointer<ContainersModel> &containersModel,
const QSharedPointer<LanguageModel> &languageModel, const QSharedPointer<LanguageModel> &languageModel,
const QSharedPointer<SitesModel> &sitesModel,
const std::shared_ptr<Settings> &settings, QObject *parent) const std::shared_ptr<Settings> &settings, QObject *parent)
: QObject(parent), : QObject(parent),
m_serversModel(serversModel), m_serversModel(serversModel),
m_containersModel(containersModel), m_containersModel(containersModel),
m_languageModel(languageModel), m_languageModel(languageModel),
m_sitesModel(sitesModel),
m_settings(settings) m_settings(settings)
{ {
m_appVersion = QString("%1: %2 (%3)").arg(tr("Software version"), QString(APP_VERSION), __DATE__); m_appVersion = QString("%1: %2 (%3)").arg(tr("Software version"), QString(APP_VERSION), __DATE__);
@@ -92,21 +90,13 @@ void SettingsController::openLogsFolder()
void SettingsController::exportLogsFile(const QString &fileName) void SettingsController::exportLogsFile(const QString &fileName)
{ {
#ifdef Q_OS_ANDROID
AndroidController::instance()->exportLogsFile(fileName);
#else
SystemController::saveFile(fileName, Logger::getLogFile()); SystemController::saveFile(fileName, Logger::getLogFile());
#endif
} }
void SettingsController::clearLogs() void SettingsController::clearLogs()
{ {
#ifdef Q_OS_ANDROID
AndroidController::instance()->clearLogs();
#else
Logger::clearLogs(); Logger::clearLogs();
Logger::clearServiceLogs(); Logger::clearServiceLogs();
#endif
} }
void SettingsController::backupAppConfig(const QString &fileName) void SettingsController::backupAppConfig(const QString &fileName)
@@ -144,7 +134,6 @@ void SettingsController::clearSettings()
m_serversModel->resetModel(); m_serversModel->resetModel();
m_languageModel->changeLanguage( m_languageModel->changeLanguage(
static_cast<LanguageSettings::AvailableLanguageEnum>(m_languageModel->getCurrentLanguageIndex())); static_cast<LanguageSettings::AvailableLanguageEnum>(m_languageModel->getCurrentLanguageIndex()));
m_sitesModel->setRouteMode(Settings::RouteMode::VpnAllSites);
emit changeSettingsFinished(tr("All settings have been reset to default values")); emit changeSettingsFinished(tr("All settings have been reset to default values"));
} }
@@ -206,14 +195,3 @@ void SettingsController::toggleScreenshotsEnabled(bool enable)
}); });
#endif #endif
} }
bool SettingsController::isCameraPresent()
{
#if defined Q_OS_IOS
return true;
#elif defined Q_OS_ANDROID
return AndroidController::instance()->isCameraPresent();
#else
return false;
#endif
}
@@ -6,7 +6,6 @@
#include "ui/models/containers_model.h" #include "ui/models/containers_model.h"
#include "ui/models/languageModel.h" #include "ui/models/languageModel.h"
#include "ui/models/servers_model.h" #include "ui/models/servers_model.h"
#include "ui/models/sites_model.h"
class SettingsController : public QObject class SettingsController : public QObject
{ {
@@ -15,7 +14,6 @@ public:
explicit SettingsController(const QSharedPointer<ServersModel> &serversModel, explicit SettingsController(const QSharedPointer<ServersModel> &serversModel,
const QSharedPointer<ContainersModel> &containersModel, const QSharedPointer<ContainersModel> &containersModel,
const QSharedPointer<LanguageModel> &languageModel, const QSharedPointer<LanguageModel> &languageModel,
const QSharedPointer<SitesModel> &sitesModel,
const std::shared_ptr<Settings> &settings, QObject *parent = nullptr); const std::shared_ptr<Settings> &settings, QObject *parent = nullptr);
Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged) Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged)
@@ -59,8 +57,6 @@ public slots:
bool isScreenshotsEnabled(); bool isScreenshotsEnabled();
void toggleScreenshotsEnabled(bool enable); void toggleScreenshotsEnabled(bool enable);
bool isCameraPresent();
signals: signals:
void primaryDnsChanged(); void primaryDnsChanged();
void secondaryDnsChanged(); void secondaryDnsChanged();
@@ -80,7 +76,6 @@ private:
QSharedPointer<ServersModel> m_serversModel; QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<ContainersModel> m_containersModel; QSharedPointer<ContainersModel> m_containersModel;
QSharedPointer<LanguageModel> m_languageModel; QSharedPointer<LanguageModel> m_languageModel;
QSharedPointer<SitesModel> m_sitesModel;
std::shared_ptr<Settings> m_settings; std::shared_ptr<Settings> m_settings;
QString m_appVersion; QString m_appVersion;
+14 -5
View File
@@ -60,11 +60,6 @@ QString SystemController::getFileName(const QString &acceptLabel, const QString
const QString &selectedFile, const bool isSaveMode, const QString &defaultSuffix) const QString &selectedFile, const bool isSaveMode, const QString &defaultSuffix)
{ {
QString fileName; QString fileName;
#ifdef Q_OS_ANDROID
Q_ASSERT(!isSaveMode);
return AndroidController::instance()->openFile(nameFilter);
#endif
#ifdef Q_OS_IOS #ifdef Q_OS_IOS
MobileUtils mobileUtils; MobileUtils mobileUtils;
@@ -113,6 +108,20 @@ QString SystemController::getFileName(const QString &acceptLabel, const QString
} }
fileName = mainFileDialog->property("selectedFile").toString(); fileName = mainFileDialog->property("selectedFile").toString();
#ifdef Q_OS_ANDROID
// patch for files containing spaces etc
const QString sep { "raw%3A%2F" };
if (fileName.startsWith("content://") && fileName.contains(sep)) {
QString contentUrl = fileName.split(sep).at(0);
QString rawUrl = fileName.split(sep).at(1);
rawUrl.replace(" ", "%20");
fileName = contentUrl + sep + rawUrl;
}
return fileName;
#endif
return QUrl(fileName).toLocalFile(); return QUrl(fileName).toLocalFile();
} }
+19 -41
View File
@@ -231,7 +231,7 @@ ErrorCode ClientManagementModel::appendClient(const QString &clientId, const QSt
} }
} }
beginInsertRows(QModelIndex(), rowCount(), rowCount() + 1); beginInsertRows(QModelIndex(), rowCount(), 1);
QJsonObject client; QJsonObject client;
client[configKey::clientId] = clientId; client[configKey::clientId] = clientId;
@@ -245,13 +245,8 @@ ErrorCode ClientManagementModel::appendClient(const QString &clientId, const QSt
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
ServerController serverController(m_settings); ServerController serverController(m_settings);
QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); const QString clientsTableFile =
if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
|| 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) {
@@ -278,13 +273,8 @@ ErrorCode ClientManagementModel::renameClient(const int row, const QString &clie
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
ServerController serverController(m_settings); ServerController serverController(m_settings);
QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); const QString clientsTableFile =
if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
|| container == DockerContainer::Cloak) {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn));
} else {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container));
}
ErrorCode error = ErrorCode error =
serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
@@ -296,36 +286,30 @@ 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, const int serverIndex) ServerCredentials credentials)
{ {
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, serverIndex); errorCode = revokeOpenVpn(row, container, credentials);
} 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) {
const auto server = m_settings->server(serverIndex); auto client = m_clientsTable.at(row).toObject();
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());
if (containerType == container) { auto protocolConfig = containerConfig.value(ContainerProps::containerTypeToString(containerType)).toObject();
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);
}
} }
} }
} }
@@ -334,7 +318,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, const int serverIndex) ServerCredentials credentials)
{ {
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();
@@ -343,7 +327,6 @@ 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);
@@ -362,8 +345,8 @@ 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"); const QString clientsTableFile =
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn)); QString("/opt/amnezia/%1/clientsTable").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";
@@ -412,13 +395,8 @@ ErrorCode ClientManagementModel::revokeWireGuard(const int row, const DockerCont
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); const QString clientsTableFile =
if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
|| 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, const int serverIndex); ErrorCode revokeClient(const int index, const DockerContainer container, ServerCredentials credentials);
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, const int serverIndex); ErrorCode revokeOpenVpn(const int row, const DockerContainer container, ServerCredentials credentials);
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" #include "ikev2ConfigModel.h".h "
#include "protocols/protocols_defs.h" #include "protocols/protocols_defs.h"
+3 -21
View File
@@ -15,10 +15,6 @@ ServersModel::ServersModel(std::shared_ptr<Settings> settings, QObject *parent)
auto defaultContainer = ContainerProps::containerFromString(m_servers.at(serverIndex).toObject().value(config_key::defaultContainer).toString()); auto defaultContainer = ContainerProps::containerFromString(m_servers.at(serverIndex).toObject().value(config_key::defaultContainer).toString());
emit ServersModel::defaultContainerChanged(defaultContainer); emit ServersModel::defaultContainerChanged(defaultContainer);
}); });
connect(this, &ServersModel::currentlyProcessedServerIndexChanged, this, [this](const int serverIndex) {
auto defaultContainer = ContainerProps::containerFromString(m_servers.at(serverIndex).toObject().value(config_key::defaultContainer).toString());
emit ServersModel::defaultContainerChanged(defaultContainer);
});
} }
int ServersModel::rowCount(const QModelIndex &parent) const int ServersModel::rowCount(const QModelIndex &parent) const
@@ -251,9 +247,10 @@ void ServersModel::addServer(const QJsonObject &server)
void ServersModel::editServer(const QJsonObject &server) void ServersModel::editServer(const QJsonObject &server)
{ {
beginResetModel();
m_settings->editServer(m_currentlyProcessedServerIndex, server); m_settings->editServer(m_currentlyProcessedServerIndex, server);
m_servers.replace(m_currentlyProcessedServerIndex, m_settings->serversArray().at(m_currentlyProcessedServerIndex)); m_servers = m_settings->serversArray();
emit dataChanged(index(m_currentlyProcessedServerIndex, 0), index(m_currentlyProcessedServerIndex, 0)); endResetModel();
updateContainersModel(); updateContainersModel();
} }
@@ -272,7 +269,6 @@ void ServersModel::removeServer()
if (m_settings->serversCount() == 0) { if (m_settings->serversCount() == 0) {
setDefaultServerIndex(-1); setDefaultServerIndex(-1);
} }
setCurrentlyProcessedServerIndex(m_defaultServerIndex);
endResetModel(); endResetModel();
} }
@@ -435,15 +431,6 @@ 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);
@@ -539,8 +526,3 @@ void ServersModel::toggleAmneziaDns(bool enabled)
emit defaultServerDescriptionChanged(); emit defaultServerDescriptionChanged();
} }
bool ServersModel::isDefaultServerFromApi()
{
return m_settings->server(m_defaultServerIndex).value(config_key::configVersion).toInt();
}
-3
View File
@@ -88,7 +88,6 @@ 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();
@@ -98,8 +97,6 @@ public slots:
void toggleAmneziaDns(bool enabled); void toggleAmneziaDns(bool enabled);
bool isDefaultServerFromApi();
protected: protected:
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
+1 -6
View File
@@ -124,13 +124,8 @@ void Autostart::setAutostart(bool autostart) {
if (file.open(QIODevice::ReadWrite)) { if (file.open(QIODevice::ReadWrite)) {
QTextStream stream(&file); QTextStream stream(&file);
stream << "[Desktop Entry]" << Qt::endl; stream << "[Desktop Entry]" << Qt::endl;
stream << "Exec=AmneziaVPN" << Qt::endl; stream << "Exec=" << appPath() << Qt::endl;
stream << "Type=Application" << Qt::endl; stream << "Type=Application" << Qt::endl;
stream << "Name=AmneziaVPN" << Qt::endl;
stream << "Comment=Client of your self-hosted VPN" << Qt::endl;
stream << "Icon=/usr/share/pixmaps/AmneziaVPN.png" << Qt::endl;
stream << "Categories=Network;Qt;Security;" << Qt::endl;
stream << "Terminal=false" << Qt::endl;
} }
} }
} }
@@ -26,24 +26,6 @@ ListView {
id: containersRadioButtonGroup id: containersRadioButtonGroup
} }
Connections {
target: ServersModel
function onCurrentlyProcessedServerIndexChanged() {
if (ContainersModel.getDefaultContainer()) {
menuContent.checkCurrentItem()
}
}
}
function checkCurrentItem() {
var item = menuContent.itemAtIndex(currentIndex)
if (item !== null) {
var radioButton = item.children[0].children[0]
radioButton.checked = true
}
}
delegate: Item { delegate: Item {
implicitWidth: rootWidth implicitWidth: rootWidth
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
@@ -78,8 +60,9 @@ ListView {
} }
if (checked) { if (checked) {
containersDropDown.menuVisible = false
ServersModel.setDefaultContainer(proxyContainersModel.mapToSource(index)) ServersModel.setDefaultContainer(proxyContainersModel.mapToSource(index))
containersDropDown.menuVisible = false
} else { } else {
if (!isSupported && isInstalled) { if (!isSupported && isInstalled) {
PageController.showErrorMessage(qsTr("The selected protocol is not supported on the current platform")) PageController.showErrorMessage(qsTr("The selected protocol is not supported on the current platform"))
@@ -25,7 +25,7 @@ DrawerType {
property string configExtension: ".vpn" property string configExtension: ".vpn"
property string configCaption: qsTr("Save AmneziaVPN config") property string configCaption: qsTr("Save AmneziaVPN config")
property string configFileName: "amnezia_config" property string configFileName: "amnezia_config.vpn"
width: parent.width width: parent.width
height: parent.height * 0.9 height: parent.height * 0.9

Some files were not shown because too many files have changed in this diff Show More