Compare commits
23 Commits
test-win-1
...
feature/co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5bf624f02 | ||
|
|
140b3ae781 | ||
|
|
5f67ce2e45 | ||
|
|
206cccfaca | ||
|
|
e4690cee0a | ||
|
|
8e56988ed0 | ||
|
|
7cf49b8602 | ||
|
|
a449baac78 | ||
|
|
f721b90dc8 | ||
|
|
b23c61b6f7 | ||
|
|
90d3dc5574 | ||
|
|
58cbba6bd2 | ||
|
|
79e02daddb | ||
|
|
113758e381 | ||
|
|
3bba823a2c | ||
|
|
20c72ec97c | ||
|
|
e5cdf9e397 | ||
|
|
126d2362bb | ||
|
|
79ef2cda93 | ||
|
|
17585bc8c8 | ||
|
|
030ed66b21 | ||
|
|
098577ec91 | ||
|
|
4671b3a96f |
7
.github/workflows/deploy.yml
vendored
@@ -65,13 +65,6 @@ jobs:
|
||||
path: deploy/AppDir
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload translations artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_translations
|
||||
path: client/translations
|
||||
retention-days: 7
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-Windows:
|
||||
|
||||
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
|
||||
project(${PROJECT} VERSION 4.6.1.0
|
||||
project(${PROJECT} VERSION 4.5.3.0
|
||||
DESCRIPTION "AmneziaVPN"
|
||||
HOMEPAGE_URL "https://amnezia.org/"
|
||||
)
|
||||
@@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||
|
||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
set(APP_ANDROID_VERSION_CODE 56)
|
||||
set(APP_ANDROID_VERSION_CODE 52)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
|
||||
64
README.md
@@ -6,42 +6,20 @@
|
||||
|
||||
Amnezia is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
|
||||
|
||||

|
||||
|
||||
<br>
|
||||
|
||||
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/download/4.6.0.3/AmneziaVPN_4.6.0.3_x64.exe"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/win.png" width="150" style="max-width: 100%;"></a>
|
||||
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/download/4.6.0.3/AmneziaVPN_4.6.0.3.dmg"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/mac.png" width="150" style="max-width: 100%;"></a>
|
||||
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/download/4.6.0.3/AmneziaVPN_Linux_4.6.0.3.tar.zip"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/lin.png" width="150" style="max-width: 100%;"></a>
|
||||
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/tag/4.6.0.3"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/andr.png" width="150" style="max-width: 100%;"></a>
|
||||
|
||||
<br>
|
||||
|
||||
<a href="https://play.google.com/store/search?q=amnezia+vpn&c=apps"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/play.png" width="150" style="max-width: 100%;"></a>
|
||||
<a href="https://apps.apple.com/us/app/amneziavpn/id1600529900"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/apl.png" width="150" style="max-width: 100%;"></a>
|
||||
|
||||
|
||||
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- Very easy to use - enter your IP address, SSH login, and password, and Amnezia will automatically install VPN docker containers to your server and connect to the VPN.
|
||||
- OpenVPN, Shadowsocks, WireGuard, and IKEv2 protocols support.
|
||||
- OpenVPN, ShadowSocks, WireGuard, and IKEv2 protocols support.
|
||||
- Masking VPN with OpenVPN over Cloak plugin
|
||||
- Split tunneling support - add any sites to the client to enable VPN only for them (only for desktops)
|
||||
- Windows, MacOS, Linux, Android, iOS releases.
|
||||
|
||||
## Links
|
||||
|
||||
- [https://amnezia.org](https://amnezia.org) - project website
|
||||
- [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_ir](https://t.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
|
||||
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Telegram support channel (Myanmar)
|
||||
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Telegram support channel (Russian)
|
||||
[https://amnezia.org](https://amnezia.org) - project website
|
||||
[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](https://t.me/amnezia_vpn) - Telegram support channel (Russian)
|
||||
|
||||
## Tech
|
||||
|
||||
@@ -49,7 +27,7 @@ AmneziaVPN uses several open-source projects to work:
|
||||
|
||||
- [OpenSSL](https://www.openssl.org/)
|
||||
- [OpenVPN](https://openvpn.net/)
|
||||
- [Shadowsocks](https://shadowsocks.org/)
|
||||
- [ShadowSocks](https://shadowsocks.org/)
|
||||
- [Qt](https://www.qt.io/)
|
||||
- [LibSsh](https://libssh.org) - forked from Qt Creator
|
||||
- and more...
|
||||
@@ -66,19 +44,6 @@ git submodule update --init --recursive
|
||||
|
||||
Want to contribute? Welcome!
|
||||
|
||||
### Help with translations
|
||||
|
||||
Download the most actual translation files.
|
||||
|
||||
Go to ["Actions" tab](https://github.com/amnezia-vpn/amnezia-client/actions?query=is%3Asuccess+branch%3Adev), click on the first line.
|
||||
Then scroll down to the "Artifacts" section and download "AmneziaVPN_translations".
|
||||
|
||||
Unzip this file.
|
||||
Each *.ts file contains strings for one corresponding language.
|
||||
|
||||
Translate or correct some strings in one or multiple *.ts files and commit them back to this repository into the ``client/translations`` folder.
|
||||
You can do it via a web-interface or any other method you're familiar with.
|
||||
|
||||
### Building sources and deployment
|
||||
|
||||
Check deploy folder for build scripts.
|
||||
@@ -87,7 +52,7 @@ Check deploy folder for build scripts.
|
||||
|
||||
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.2. Install QT for MacOS [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.6.1. Install QT for MacOS [here](https://doc.qt.io/qt-6/macos.html) or [QT Online Installer](https://www.qt.io/download-open-source). Required modules:
|
||||
- MacOS
|
||||
- iOS
|
||||
- Qt 5 Compatibility Module
|
||||
@@ -154,11 +119,9 @@ The Android app has the following requirements:
|
||||
* Android platform SDK 33
|
||||
* CMake 3.25.0
|
||||
|
||||
After you have installed QT, QT Creator, and Android Studio, you need to configure QT Creator correctly.
|
||||
|
||||
- Click in the top menu bar on `QT Creator` -> `Preferences` -> `Devices` and select the tab `Android`.
|
||||
- Set path to JDK 11
|
||||
- Set path to Android SDK (`$ANDROID_HOME`)
|
||||
After you have installed QT, QT Creator, and Android Studio, you need to configure QT Creator correctly. Click in the top menu bar on `QT Creator` -> `Preferences` -> `Devices` and select the tab `Android`.
|
||||
* set path to JDK 11
|
||||
* set path to Android SDK ($ANDROID_HOME)
|
||||
|
||||
In case you get errors regarding missing SDK or 'SDK manager not running', you cannot fix them by correcting the paths. If you have some spare GBs on your disk, you can let QT Creator install all requirements by choosing an empty folder for `Android SDK location` and clicking on `Set Up SDK`. Be aware: This will install a second Android SDK and NDK on your machine!
|
||||
Double-check that the right CMake version is configured: Click on `QT Creator` -> `Preferences` and click on the side menu on `Kits`. Under the center content view's `Kits` tab, you'll find an entry for `CMake Tool`. If the default selected CMake version is lower than 3.25.0, install on your system CMake >= 3.25.0 and choose `System CMake at <path>` from the drop-down list. If this entry is missing, you either have not installed CMake yet or QT Creator hasn't found the path to it. In that case, click in the preferences window on the side menu item `CMake`, then on the tab `Tools` in the center content view, and finally on the button `Add` to set the path to your installed CMake.
|
||||
@@ -179,11 +142,10 @@ GPL v3.0
|
||||
|
||||
## Donate
|
||||
|
||||
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
|
||||
|
||||
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
|
||||
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
|
||||
Bitcoin: bc1qn9rhsffuxwnhcuuu4qzrwp4upkrq94xnh8r26u
|
||||
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3
|
||||
payeer.com: P2561305
|
||||
ko-fi.com: [https://ko-fi.com/amnezia_vpn](https://ko-fi.com/amnezia_vpn)
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
|
||||
2
client/3rd/OpenVPNAdapter
vendored
2
client/3rd/amneziawg-apple
vendored
@@ -157,19 +157,16 @@ void AmneziaApplication::init()
|
||||
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
||||
#endif
|
||||
|
||||
m_engine->addImportPath("qrc:/ui/qml/Modules/");
|
||||
m_engine->load(url);
|
||||
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
|
||||
|
||||
bool enabled = m_settings->isSaveLogs();
|
||||
#ifndef Q_OS_ANDROID
|
||||
if (enabled) {
|
||||
if (m_settings->isSaveLogs()) {
|
||||
if (!Logger::init()) {
|
||||
qWarning() << "Initialization of debug subsystem failed";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Logger::setServiceLogsEnabled(enabled);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (m_parser.isSet("a"))
|
||||
@@ -354,9 +351,6 @@ void AmneziaApplication::initModels()
|
||||
m_sftpConfigModel.reset(new SftpConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get());
|
||||
|
||||
m_socks5ConfigModel.reset(new Socks5ProxyConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("Socks5ProxyConfigModel", m_socks5ConfigModel.get());
|
||||
|
||||
m_clientManagementModel.reset(new ClientManagementModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
|
||||
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
#include "ui/models/protocols_model.h"
|
||||
#include "ui/models/servers_model.h"
|
||||
#include "ui/models/services/sftpConfigModel.h"
|
||||
#include "ui/models/services/socks5ProxyConfigModel.h"
|
||||
#include "ui/models/sites_model.h"
|
||||
#include "ui/models/clientManagementModel.h"
|
||||
#include "ui/models/appSplitTunnelingModel.h"
|
||||
@@ -115,7 +114,6 @@ private:
|
||||
#endif
|
||||
|
||||
QScopedPointer<SftpConfigModel> m_sftpConfigModel;
|
||||
QScopedPointer<Socks5ProxyConfigModel> m_socks5ConfigModel;
|
||||
|
||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||
QThread m_vpnConnectionThread;
|
||||
|
||||
@@ -11,9 +11,6 @@
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.camera.any" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
||||
<!-- for TV -->
|
||||
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
||||
|
||||
<!-- The following comment will be replaced upon deployment with default features based on the dependencies
|
||||
of the application. Remove the comment if you do not require these default features. -->
|
||||
@@ -34,11 +31,9 @@
|
||||
android:label="-- %%INSERT_APP_NAME%% --"
|
||||
android:icon="@mipmap/icon"
|
||||
android:roundIcon="@mipmap/icon_round"
|
||||
android:banner="@mipmap/ic_banner"
|
||||
android:theme="@style/NoActionBar"
|
||||
android:fullBackupContent="@xml/backup_content"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:hasFragileUserData="false"
|
||||
tools:targetApi="s">
|
||||
|
||||
<activity
|
||||
@@ -52,7 +47,6 @@
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
@@ -142,34 +136,8 @@
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".AwgService"
|
||||
android:process=":amneziaAwgService"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||
android:foregroundServiceType="systemExempted"
|
||||
android:exported="false"
|
||||
tools:ignore="ForegroundServicePermission">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".OpenVpnService"
|
||||
android:process=":amneziaOpenVpnService"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||
android:foregroundServiceType="systemExempted"
|
||||
android:exported="false"
|
||||
tools:ignore="ForegroundServicePermission">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".XrayService"
|
||||
android:process=":amneziaXrayService"
|
||||
android:name=".AmneziaVpnService"
|
||||
android:process=":amneziaVpnService"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||
android:foregroundServiceType="systemExempted"
|
||||
android:exported="false"
|
||||
|
||||
@@ -3,7 +3,6 @@ import com.android.build.gradle.internal.api.BaseVariantOutputImpl
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
id("property-delegate")
|
||||
}
|
||||
|
||||
@@ -69,12 +68,6 @@ android {
|
||||
}
|
||||
signingConfig = signingConfigs["release"]
|
||||
}
|
||||
|
||||
create("fdroid") {
|
||||
initWith(getByName("release"))
|
||||
signingConfig = null
|
||||
matchingFallbacks += "release"
|
||||
}
|
||||
}
|
||||
|
||||
splits {
|
||||
@@ -105,6 +98,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar", "*.aar"))))
|
||||
implementation(project(":qt"))
|
||||
implementation(project(":utils"))
|
||||
implementation(project(":protocolApi"))
|
||||
@@ -112,11 +106,9 @@ dependencies {
|
||||
implementation(project(":awg"))
|
||||
implementation(project(":openvpn"))
|
||||
implementation(project(":cloak"))
|
||||
implementation(project(":xray"))
|
||||
implementation(libs.androidx.core)
|
||||
implementation(libs.androidx.activity)
|
||||
implementation(libs.kotlinx.coroutines)
|
||||
implementation(libs.kotlinx.serialization.protobuf)
|
||||
implementation(libs.bundles.androidx.camera)
|
||||
implementation(libs.google.mlkit)
|
||||
implementation(libs.androidx.datastore)
|
||||
|
||||
@@ -8,7 +8,6 @@ androidx-camera = "1.3.0"
|
||||
androidx-security-crypto = "1.1.0-alpha06"
|
||||
androidx-datastore = "1.1.0-beta01"
|
||||
kotlinx-coroutines = "1.7.3"
|
||||
kotlinx-serialization = "1.6.3"
|
||||
google-mlkit = "17.2.0"
|
||||
|
||||
[libraries]
|
||||
@@ -22,7 +21,6 @@ androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "
|
||||
androidx-security-crypto = { module = "androidx.security:security-crypto-ktx", version.ref = "androidx-security-crypto" }
|
||||
androidx-datastore = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" }
|
||||
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
|
||||
kotlinx-serialization-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "kotlinx-serialization" }
|
||||
google-mlkit = { module = "com.google.mlkit:barcode-scanning", version.ref = "google-mlkit" }
|
||||
|
||||
[bundles]
|
||||
@@ -37,4 +35,3 @@ androidx-camera = [
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin"}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package org.amnezia.vpn.protocol.openvpn
|
||||
|
||||
import android.content.Context
|
||||
import android.net.VpnService.Builder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import net.openvpn.ovpn3.ClientAPI_Config
|
||||
import org.amnezia.vpn.protocol.BadConfigException
|
||||
import org.amnezia.vpn.protocol.Protocol
|
||||
import org.amnezia.vpn.protocol.ProtocolState
|
||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||
import org.amnezia.vpn.protocol.Statistics
|
||||
import org.amnezia.vpn.protocol.VpnStartException
|
||||
@@ -35,6 +37,7 @@ import org.json.JSONObject
|
||||
|
||||
open class OpenVpn : Protocol() {
|
||||
|
||||
private lateinit var context: Context
|
||||
private var openVpnClient: OpenVpnClient? = null
|
||||
private lateinit var scope: CoroutineScope
|
||||
|
||||
@@ -50,11 +53,10 @@ open class OpenVpn : Protocol() {
|
||||
return Statistics.EMPTY_STATISTICS
|
||||
}
|
||||
|
||||
override fun internalInit() {
|
||||
if (!isInitialized) loadSharedLibrary(context, "ovpn3")
|
||||
if (this::scope.isInitialized) {
|
||||
scope.cancel()
|
||||
}
|
||||
override fun initialize(context: Context, state: MutableStateFlow<ProtocolState>, onError: (String) -> Unit) {
|
||||
super.initialize(context, state, onError)
|
||||
loadSharedLibrary(context, "ovpn3")
|
||||
this.context = context
|
||||
scope = CoroutineScope(Dispatchers.IO)
|
||||
}
|
||||
|
||||
|
||||
@@ -27,21 +27,14 @@ private const val SPLIT_TUNNEL_EXCLUDE = 2
|
||||
abstract class Protocol {
|
||||
|
||||
abstract val statistics: Statistics
|
||||
protected lateinit var context: Context
|
||||
protected lateinit var state: MutableStateFlow<ProtocolState>
|
||||
protected lateinit var onError: (String) -> Unit
|
||||
protected var isInitialized: Boolean = false
|
||||
|
||||
fun initialize(context: Context, state: MutableStateFlow<ProtocolState>, onError: (String) -> Unit) {
|
||||
this.context = context
|
||||
open fun initialize(context: Context, state: MutableStateFlow<ProtocolState>, onError: (String) -> Unit) {
|
||||
this.state = state
|
||||
this.onError = onError
|
||||
internalInit()
|
||||
isInitialized = true
|
||||
}
|
||||
|
||||
protected abstract fun internalInit()
|
||||
|
||||
abstract fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean)
|
||||
|
||||
abstract fun stopVpn()
|
||||
|
||||
@@ -21,5 +21,5 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(fileTree(mapOf("dir" to "../libs", "include" to listOf("*.jar"))))
|
||||
implementation(fileTree(mapOf("dir" to "../libs", "include" to listOf("*.jar", "*.aar"))))
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_banner_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_banner_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 11 KiB |
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_banner_background">#1E1E1F</color>
|
||||
</resources>
|
||||
@@ -36,8 +36,6 @@ include(":wireguard")
|
||||
include(":awg")
|
||||
include(":openvpn")
|
||||
include(":cloak")
|
||||
include(":xray")
|
||||
include(":xray:libXray")
|
||||
|
||||
// get values from gradle or local properties
|
||||
val androidBuildToolsVersion: String by gradleProperties
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.amnezia.vpn
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.AlertDialog
|
||||
import android.app.NotificationManager
|
||||
import android.content.BroadcastReceiver
|
||||
@@ -35,7 +34,6 @@ import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
@@ -45,8 +43,6 @@ import org.amnezia.vpn.protocol.getStatus
|
||||
import org.amnezia.vpn.qt.QtAndroidController
|
||||
import org.amnezia.vpn.util.Log
|
||||
import org.amnezia.vpn.util.Prefs
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import org.qtproject.qt.android.bindings.QtActivity
|
||||
|
||||
private const val TAG = "AmneziaActivity"
|
||||
@@ -63,7 +59,6 @@ class AmneziaActivity : QtActivity() {
|
||||
|
||||
private lateinit var mainScope: CoroutineScope
|
||||
private val qtInitialized = CompletableDeferred<Unit>()
|
||||
private var vpnProto: VpnProto? = null
|
||||
private var isWaitingStatus = true
|
||||
private var isServiceConnected = false
|
||||
private var isInBoundState = false
|
||||
@@ -146,7 +141,6 @@ class AmneziaActivity : QtActivity() {
|
||||
override fun onBindingDied(name: ComponentName?) {
|
||||
Log.w(TAG, "Binding to the ${name?.flattenToString()} unexpectedly died")
|
||||
doUnbindService()
|
||||
QtAndroidController.onServiceDisconnected()
|
||||
doBindService()
|
||||
}
|
||||
}
|
||||
@@ -159,20 +153,15 @@ class AmneziaActivity : QtActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
Log.d(TAG, "Create Amnezia activity: $intent")
|
||||
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||
val proto = mainScope.async(Dispatchers.IO) {
|
||||
VpnStateStore.getVpnState().vpnProto
|
||||
}
|
||||
vpnServiceMessenger = IpcMessenger(
|
||||
"VpnService",
|
||||
onDeadObjectException = {
|
||||
doUnbindService()
|
||||
QtAndroidController.onServiceDisconnected()
|
||||
doBindService()
|
||||
}
|
||||
)
|
||||
registerBroadcastReceivers()
|
||||
intent?.let(::processIntent)
|
||||
runBlocking { vpnProto = proto.await() }
|
||||
}
|
||||
|
||||
private fun registerBroadcastReceivers() {
|
||||
@@ -220,21 +209,13 @@ class AmneziaActivity : QtActivity() {
|
||||
Log.d(TAG, "Start Amnezia activity")
|
||||
mainScope.launch {
|
||||
qtInitialized.await()
|
||||
vpnProto?.let { proto ->
|
||||
if (AmneziaVpnService.isRunning(applicationContext, proto.processName)) {
|
||||
doBindService()
|
||||
}
|
||||
}
|
||||
doBindService()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
Log.d(TAG, "Stop Amnezia activity")
|
||||
doUnbindService()
|
||||
mainScope.launch {
|
||||
qtInitialized.await()
|
||||
QtAndroidController.onServiceDisconnected()
|
||||
}
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
@@ -288,12 +269,10 @@ class AmneziaActivity : QtActivity() {
|
||||
@MainThread
|
||||
private fun doBindService() {
|
||||
Log.d(TAG, "Bind service")
|
||||
vpnProto?.let { proto ->
|
||||
Intent(this, proto.serviceClass).also {
|
||||
bindService(it, serviceConnection, BIND_ABOVE_CLIENT and BIND_AUTO_CREATE)
|
||||
}
|
||||
isInBoundState = true
|
||||
Intent(this, AmneziaVpnService::class.java).also {
|
||||
bindService(it, serviceConnection, BIND_ABOVE_CLIENT and BIND_AUTO_CREATE)
|
||||
}
|
||||
isInBoundState = true
|
||||
}
|
||||
|
||||
@MainThread
|
||||
@@ -301,6 +280,7 @@ class AmneziaActivity : QtActivity() {
|
||||
if (isInBoundState) {
|
||||
Log.d(TAG, "Unbind service")
|
||||
isWaitingStatus = true
|
||||
QtAndroidController.onServiceDisconnected()
|
||||
isServiceConnected = false
|
||||
vpnServiceMessenger.send(Action.UNREGISTER_CLIENT, activityMessenger)
|
||||
vpnServiceMessenger.reset()
|
||||
@@ -385,32 +365,13 @@ class AmneziaActivity : QtActivity() {
|
||||
|
||||
@MainThread
|
||||
private fun startVpn(vpnConfig: String) {
|
||||
getVpnProto(vpnConfig)?.let { proto ->
|
||||
Log.d(TAG, "Proto from config: $proto, current proto: $vpnProto")
|
||||
if (isServiceConnected) {
|
||||
if (proto.serviceClass == vpnProto?.serviceClass) {
|
||||
vpnProto = proto
|
||||
connectToVpn(vpnConfig)
|
||||
return
|
||||
}
|
||||
doUnbindService()
|
||||
}
|
||||
vpnProto = proto
|
||||
if (isServiceConnected) {
|
||||
connectToVpn(vpnConfig)
|
||||
} else {
|
||||
isWaitingStatus = false
|
||||
startVpnService(vpnConfig, proto)
|
||||
startVpnService(vpnConfig)
|
||||
doBindService()
|
||||
} ?: QtAndroidController.onServiceError()
|
||||
}
|
||||
|
||||
private fun getVpnProto(vpnConfig: String): VpnProto? = try {
|
||||
require(vpnConfig.isNotBlank()) { "Blank VPN config" }
|
||||
VpnProto.get(JSONObject(vpnConfig).getString("protocol"))
|
||||
} catch (e: JSONException) {
|
||||
Log.e(TAG, "Invalid VPN config json format: ${e.message}")
|
||||
null
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Log.e(TAG, "Protocol not found: ${e.message}")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun connectToVpn(vpnConfig: String) {
|
||||
@@ -422,15 +383,15 @@ class AmneziaActivity : QtActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun startVpnService(vpnConfig: String, proto: VpnProto) {
|
||||
Log.d(TAG, "Start VPN service: $proto")
|
||||
Intent(this, proto.serviceClass).apply {
|
||||
private fun startVpnService(vpnConfig: String) {
|
||||
Log.d(TAG, "Start VPN service")
|
||||
Intent(this, AmneziaVpnService::class.java).apply {
|
||||
putExtra(MSG_VPN_CONFIG, vpnConfig)
|
||||
}.also {
|
||||
try {
|
||||
ContextCompat.startForegroundService(this, it)
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Failed to start ${proto.serviceClass.simpleName}: $e")
|
||||
Log.e(TAG, "Failed to start AmneziaVpnService: $e")
|
||||
QtAndroidController.onServiceError()
|
||||
}
|
||||
}
|
||||
@@ -546,7 +507,7 @@ class AmneziaActivity : QtActivity() {
|
||||
}
|
||||
}.also {
|
||||
startActivityForResult(it, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
|
||||
onAny = {
|
||||
onSuccess = {
|
||||
val uri = it?.data?.toString() ?: ""
|
||||
Log.d(TAG, "Open file: $uri")
|
||||
mainScope.launch {
|
||||
@@ -560,12 +521,8 @@ class AmneziaActivity : QtActivity() {
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
@SuppressLint("UnsupportedChromeOsCameraSystemFeature")
|
||||
fun isCameraPresent(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
|
||||
|
||||
@Suppress("unused")
|
||||
fun isOnTv(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|
||||
|
||||
@Suppress("unused")
|
||||
fun startQrCodeReader() {
|
||||
Log.v(TAG, "Start camera")
|
||||
|
||||
@@ -39,9 +39,6 @@ class AmneziaTileService : TileService() {
|
||||
|
||||
@Volatile
|
||||
private var isServiceConnected = false
|
||||
|
||||
@Volatile
|
||||
private var vpnProto: VpnProto? = null
|
||||
private var isInBoundState = false
|
||||
@Volatile
|
||||
private var isVpnConfigExists = false
|
||||
@@ -97,21 +94,16 @@ class AmneziaTileService : TileService() {
|
||||
|
||||
override fun onStartListening() {
|
||||
super.onStartListening()
|
||||
scope.launch {
|
||||
Log.d(TAG, "Start listening")
|
||||
vpnProto = VpnStateStore.getVpnState().vpnProto
|
||||
vpnProto.also { proto ->
|
||||
if (proto != null && AmneziaVpnService.isRunning(applicationContext, proto.processName)) {
|
||||
Log.d(TAG, "Vpn service is running")
|
||||
doBindService()
|
||||
} else {
|
||||
Log.d(TAG, "Vpn service is not running")
|
||||
isServiceConnected = false
|
||||
updateVpnState(DISCONNECTED)
|
||||
}
|
||||
}
|
||||
vpnStateListeningJob = launchVpnStateListening()
|
||||
Log.d(TAG, "Start listening")
|
||||
if (AmneziaVpnService.isRunning(applicationContext)) {
|
||||
Log.d(TAG, "Vpn service is running")
|
||||
doBindService()
|
||||
} else {
|
||||
Log.d(TAG, "Vpn service is not running")
|
||||
isServiceConnected = false
|
||||
updateVpnState(DISCONNECTED)
|
||||
}
|
||||
vpnStateListeningJob = launchVpnStateListening()
|
||||
}
|
||||
|
||||
override fun onStopListening() {
|
||||
@@ -132,7 +124,7 @@ class AmneziaTileService : TileService() {
|
||||
}
|
||||
|
||||
private fun onClickInternal() {
|
||||
if (isVpnConfigExists && vpnProto != null) {
|
||||
if (isVpnConfigExists) {
|
||||
Log.d(TAG, "Change VPN state")
|
||||
if (qsTile.state == Tile.STATE_INACTIVE) {
|
||||
Log.d(TAG, "Start VPN")
|
||||
@@ -155,12 +147,10 @@ class AmneziaTileService : TileService() {
|
||||
|
||||
private fun doBindService() {
|
||||
Log.d(TAG, "Bind service")
|
||||
vpnProto?.let { proto ->
|
||||
Intent(this, proto.serviceClass).also {
|
||||
bindService(it, serviceConnection, BIND_ABOVE_CLIENT)
|
||||
}
|
||||
isInBoundState = true
|
||||
Intent(this, AmneziaVpnService::class.java).also {
|
||||
bindService(it, serviceConnection, BIND_ABOVE_CLIENT)
|
||||
}
|
||||
isInBoundState = true
|
||||
}
|
||||
|
||||
private fun doUnbindService() {
|
||||
@@ -190,7 +180,6 @@ class AmneziaTileService : TileService() {
|
||||
if (VpnService.prepare(applicationContext) != null) {
|
||||
Intent(this, VpnRequestActivity::class.java).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
putExtra(EXTRA_PROTOCOL, vpnProto)
|
||||
}.also {
|
||||
startActivityAndCollapseCompat(it)
|
||||
}
|
||||
@@ -200,16 +189,14 @@ class AmneziaTileService : TileService() {
|
||||
}
|
||||
|
||||
private fun startVpnService() {
|
||||
vpnProto?.let { proto ->
|
||||
try {
|
||||
ContextCompat.startForegroundService(
|
||||
applicationContext,
|
||||
Intent(this, proto.serviceClass)
|
||||
)
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Failed to start ${proto.serviceClass.simpleName}: $e")
|
||||
}
|
||||
} ?: Log.e(TAG, "Failed to start vpn service: vpnProto is null")
|
||||
try {
|
||||
ContextCompat.startForegroundService(
|
||||
applicationContext,
|
||||
Intent(this, AmneziaVpnService::class.java)
|
||||
)
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Failed to start AmneziaVpnService: $e")
|
||||
}
|
||||
}
|
||||
|
||||
private fun connectToVpn() = vpnServiceMessenger.send(Action.CONNECT)
|
||||
@@ -233,8 +220,11 @@ class AmneziaTileService : TileService() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateVpnState(state: ProtocolState) =
|
||||
scope.launch { VpnStateStore.store { it.copy(protocolState = state) } }
|
||||
private fun updateVpnState(state: ProtocolState) {
|
||||
scope.launch {
|
||||
VpnStateStore.store { it.copy(protocolState = state) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchVpnStateListening() =
|
||||
scope.launch { VpnStateStore.dataFlow().collectLatest(::updateTile) }
|
||||
@@ -242,10 +232,9 @@ class AmneziaTileService : TileService() {
|
||||
private fun updateTile(vpnState: VpnState) {
|
||||
Log.d(TAG, "Update tile: $vpnState")
|
||||
isVpnConfigExists = vpnState.serverName != null
|
||||
vpnProto = vpnState.vpnProto
|
||||
val tile = qsTile ?: return
|
||||
tile.apply {
|
||||
label = (vpnState.serverName ?: DEFAULT_TILE_LABEL) + (vpnProto?.let { " ${it.label}" } ?: "")
|
||||
label = vpnState.serverName ?: DEFAULT_TILE_LABEL
|
||||
when (val protocolState = vpnState.protocolState) {
|
||||
CONNECTED -> {
|
||||
state = Tile.STATE_ACTIVE
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.amnezia.vpn
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.ActivityManager
|
||||
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE
|
||||
import android.app.NotificationManager
|
||||
@@ -40,6 +39,7 @@ import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import org.amnezia.vpn.protocol.BadConfigException
|
||||
import org.amnezia.vpn.protocol.LoadLibraryException
|
||||
import org.amnezia.vpn.protocol.Protocol
|
||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTING
|
||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||
@@ -48,7 +48,11 @@ import org.amnezia.vpn.protocol.ProtocolState.RECONNECTING
|
||||
import org.amnezia.vpn.protocol.ProtocolState.UNKNOWN
|
||||
import org.amnezia.vpn.protocol.VpnException
|
||||
import org.amnezia.vpn.protocol.VpnStartException
|
||||
import org.amnezia.vpn.protocol.awg.Awg
|
||||
import org.amnezia.vpn.protocol.cloak.Cloak
|
||||
import org.amnezia.vpn.protocol.openvpn.OpenVpn
|
||||
import org.amnezia.vpn.protocol.putStatus
|
||||
import org.amnezia.vpn.protocol.wireguard.Wireguard
|
||||
import org.amnezia.vpn.util.Log
|
||||
import org.amnezia.vpn.util.Prefs
|
||||
import org.amnezia.vpn.util.net.NetworkState
|
||||
@@ -59,7 +63,6 @@ import org.json.JSONObject
|
||||
private const val TAG = "AmneziaVpnService"
|
||||
|
||||
const val ACTION_DISCONNECT = "org.amnezia.vpn.action.disconnect"
|
||||
const val ACTION_CONNECT = "org.amnezia.vpn.action.connect"
|
||||
|
||||
const val MSG_VPN_CONFIG = "VPN_CONFIG"
|
||||
const val MSG_ERROR = "ERROR"
|
||||
@@ -70,18 +73,19 @@ const val AFTER_PERMISSION_CHECK = "AFTER_PERMISSION_CHECK"
|
||||
private const val PREFS_CONFIG_KEY = "LAST_CONF"
|
||||
private const val PREFS_SERVER_NAME = "LAST_SERVER_NAME"
|
||||
private const val PREFS_SERVER_INDEX = "LAST_SERVER_INDEX"
|
||||
private const val PROCESS_NAME = "org.amnezia.vpn:amneziaVpnService"
|
||||
// private const val STATISTICS_SENDING_TIMEOUT = 1000L
|
||||
private const val TRAFFIC_STATS_UPDATE_TIMEOUT = 1000L
|
||||
private const val DISCONNECT_TIMEOUT = 5000L
|
||||
private const val STOP_SERVICE_TIMEOUT = 5000L
|
||||
|
||||
@SuppressLint("Registered")
|
||||
open class AmneziaVpnService : VpnService() {
|
||||
class AmneziaVpnService : VpnService() {
|
||||
|
||||
private lateinit var mainScope: CoroutineScope
|
||||
private lateinit var connectionScope: CoroutineScope
|
||||
private var isServiceBound = false
|
||||
private var vpnProto: VpnProto? = null
|
||||
private var protocol: Protocol? = null
|
||||
private val protocolCache = mutableMapOf<String, Protocol>()
|
||||
private var protocolState = MutableStateFlow(UNKNOWN)
|
||||
private var serverName: String? = null
|
||||
private var serverIndex: Int = -1
|
||||
@@ -101,7 +105,7 @@ open class AmneziaVpnService : VpnService() {
|
||||
// private var statisticsSendingJob: Job? = null
|
||||
private lateinit var networkState: NetworkState
|
||||
private lateinit var trafficStats: TrafficStats
|
||||
private var controlReceiver: BroadcastReceiver? = null
|
||||
private var disconnectReceiver: BroadcastReceiver? = null
|
||||
private var notificationStateReceiver: BroadcastReceiver? = null
|
||||
private var screenOnReceiver: BroadcastReceiver? = null
|
||||
private var screenOffReceiver: BroadcastReceiver? = null
|
||||
@@ -112,6 +116,7 @@ open class AmneziaVpnService : VpnService() {
|
||||
|
||||
private val connectionExceptionHandler = CoroutineExceptionHandler { _, e ->
|
||||
protocolState.value = DISCONNECTED
|
||||
protocol = null
|
||||
when (e) {
|
||||
is IllegalArgumentException,
|
||||
is VpnStartException,
|
||||
@@ -222,8 +227,7 @@ open class AmneziaVpnService : VpnService() {
|
||||
connect(intent?.getStringExtra(MSG_VPN_CONFIG))
|
||||
}
|
||||
ServiceCompat.startForeground(
|
||||
this, NOTIFICATION_ID,
|
||||
serviceNotification.buildNotification(serverName, vpnProto?.label, protocolState.value),
|
||||
this, NOTIFICATION_ID, serviceNotification.buildNotification(serverName, protocolState.value),
|
||||
foregroundServiceTypeCompat
|
||||
)
|
||||
return START_REDELIVER_INTENT
|
||||
@@ -288,17 +292,9 @@ open class AmneziaVpnService : VpnService() {
|
||||
|
||||
private fun registerBroadcastReceivers() {
|
||||
Log.d(TAG, "Register broadcast receivers")
|
||||
controlReceiver = registerBroadcastReceiver(
|
||||
arrayOf(ACTION_CONNECT, ACTION_DISCONNECT), ContextCompat.RECEIVER_NOT_EXPORTED
|
||||
) {
|
||||
it?.action?.let { action ->
|
||||
Log.d(TAG, "Broadcast request received: $action")
|
||||
when (action) {
|
||||
ACTION_CONNECT -> connect()
|
||||
ACTION_DISCONNECT -> disconnect()
|
||||
else -> Log.w(TAG, "Unknown action received: $action")
|
||||
}
|
||||
}
|
||||
disconnectReceiver = registerBroadcastReceiver(ACTION_DISCONNECT, ContextCompat.RECEIVER_NOT_EXPORTED) {
|
||||
Log.d(TAG, "Broadcast request received: $ACTION_DISCONNECT")
|
||||
disconnect()
|
||||
}
|
||||
|
||||
notificationStateReceiver = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
@@ -344,10 +340,10 @@ open class AmneziaVpnService : VpnService() {
|
||||
|
||||
private fun unregisterBroadcastReceivers() {
|
||||
Log.d(TAG, "Unregister broadcast receivers")
|
||||
unregisterBroadcastReceiver(controlReceiver)
|
||||
unregisterBroadcastReceiver(disconnectReceiver)
|
||||
unregisterBroadcastReceiver(notificationStateReceiver)
|
||||
unregisterScreenStateBroadcastReceivers()
|
||||
controlReceiver = null
|
||||
disconnectReceiver = null
|
||||
notificationStateReceiver = null
|
||||
}
|
||||
|
||||
@@ -360,7 +356,7 @@ open class AmneziaVpnService : VpnService() {
|
||||
protocolState.drop(1).collect { protocolState ->
|
||||
Log.d(TAG, "Protocol state changed: $protocolState")
|
||||
|
||||
serviceNotification.updateNotification(serverName, vpnProto?.label, protocolState)
|
||||
serviceNotification.updateNotification(serverName, protocolState)
|
||||
|
||||
clientMessengers.send {
|
||||
ServiceEvent.STATUS_CHANGED.packToMessage {
|
||||
@@ -368,7 +364,7 @@ open class AmneziaVpnService : VpnService() {
|
||||
}
|
||||
}
|
||||
|
||||
VpnStateStore.store { VpnState(protocolState, serverName, serverIndex, vpnProto) }
|
||||
VpnStateStore.store { VpnState(protocolState, serverName, serverIndex) }
|
||||
|
||||
when (protocolState) {
|
||||
CONNECTED -> {
|
||||
@@ -425,7 +421,7 @@ open class AmneziaVpnService : VpnService() {
|
||||
@MainThread
|
||||
private fun enableNotification() {
|
||||
registerScreenStateBroadcastReceivers()
|
||||
serviceNotification.updateNotification(serverName, vpnProto?.label, protocolState.value)
|
||||
serviceNotification.updateNotification(serverName, protocolState.value)
|
||||
launchTrafficStatsUpdate()
|
||||
}
|
||||
|
||||
@@ -488,6 +484,8 @@ open class AmneziaVpnService : VpnService() {
|
||||
|
||||
Log.d(TAG, "Start VPN connection")
|
||||
|
||||
protocolState.value = CONNECTING
|
||||
|
||||
val config = parseConfigToJson(vpnConfig)
|
||||
saveServerData(config)
|
||||
if (config == null) {
|
||||
@@ -496,16 +494,6 @@ open class AmneziaVpnService : VpnService() {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
vpnProto = VpnProto.get(config.getString("protocol"))
|
||||
} catch (e: Exception) {
|
||||
onError("Invalid VPN config: ${e.message}")
|
||||
protocolState.value = DISCONNECTED
|
||||
return
|
||||
}
|
||||
|
||||
protocolState.value = CONNECTING
|
||||
|
||||
if (!checkPermission()) {
|
||||
protocolState.value = DISCONNECTED
|
||||
return
|
||||
@@ -515,10 +503,8 @@ open class AmneziaVpnService : VpnService() {
|
||||
disconnectionJob?.join()
|
||||
disconnectionJob = null
|
||||
|
||||
vpnProto?.protocol?.let { protocol ->
|
||||
protocol.initialize(applicationContext, protocolState, ::onError)
|
||||
protocol.startVpn(config, Builder(), ::protect)
|
||||
}
|
||||
protocol = getProtocol(config.getString("protocol"))
|
||||
protocol?.startVpn(config, Builder(), ::protect)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,8 +520,8 @@ open class AmneziaVpnService : VpnService() {
|
||||
connectionJob?.join()
|
||||
connectionJob = null
|
||||
|
||||
vpnProto?.protocol?.stopVpn()
|
||||
|
||||
protocol?.stopVpn()
|
||||
protocol = null
|
||||
try {
|
||||
withTimeout(DISCONNECT_TIMEOUT) {
|
||||
// waiting for disconnect state
|
||||
@@ -557,10 +543,22 @@ open class AmneziaVpnService : VpnService() {
|
||||
protocolState.value = RECONNECTING
|
||||
|
||||
connectionJob = connectionScope.launch {
|
||||
vpnProto?.protocol?.reconnectVpn(Builder())
|
||||
protocol?.reconnectVpn(Builder())
|
||||
}
|
||||
}
|
||||
|
||||
@MainThread
|
||||
private fun getProtocol(protocolName: String): Protocol =
|
||||
protocolCache[protocolName]
|
||||
?: when (protocolName) {
|
||||
"wireguard" -> Wireguard()
|
||||
"awg" -> Awg()
|
||||
"openvpn" -> OpenVpn()
|
||||
"cloak" -> Cloak()
|
||||
else -> throw IllegalArgumentException("Protocol '$protocolName' not found")
|
||||
}.apply { initialize(applicationContext, protocolState, ::onError) }
|
||||
.also { protocolCache[protocolName] = it }
|
||||
|
||||
/**
|
||||
* Utils methods
|
||||
*/
|
||||
@@ -605,7 +603,6 @@ open class AmneziaVpnService : VpnService() {
|
||||
if (prepare(applicationContext) != null) {
|
||||
Intent(this, VpnRequestActivity::class.java).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
putExtra(EXTRA_PROTOCOL, vpnProto)
|
||||
}.also {
|
||||
startActivity(it)
|
||||
}
|
||||
@@ -615,9 +612,9 @@ open class AmneziaVpnService : VpnService() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun isRunning(context: Context, processName: String): Boolean =
|
||||
fun isRunning(context: Context): Boolean =
|
||||
context.getSystemService<ActivityManager>()!!.runningAppProcesses.any {
|
||||
it.processName == processName && it.importance <= IMPORTANCE_FOREGROUND_SERVICE
|
||||
it.processName == PROCESS_NAME && it.importance <= IMPORTANCE_FOREGROUND_SERVICE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
package org.amnezia.vpn
|
||||
|
||||
class AwgService : AmneziaVpnService()
|
||||
@@ -140,7 +140,7 @@ class CameraActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
}.addOnFailureListener {
|
||||
Log.e(TAG, "Processing QR code image failed: ${it.message}")
|
||||
Log.e(TAG, "Processing QR-code image failed: ${it.message}")
|
||||
}.addOnCompleteListener {
|
||||
imageProxy.close()
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
package org.amnezia.vpn
|
||||
|
||||
class OpenVpnService : AmneziaVpnService()
|
||||
@@ -59,14 +59,14 @@ class ServiceNotification(private val context: Context) {
|
||||
formatSpeedString(rxString, txString)
|
||||
}
|
||||
|
||||
fun buildNotification(serverName: String?, protocol: String?, state: ProtocolState): Notification {
|
||||
fun buildNotification(serverName: String?, state: ProtocolState): Notification {
|
||||
val speedString = if (state == CONNECTED) zeroSpeed else null
|
||||
|
||||
Log.d(TAG, "Build notification: $serverName, $state")
|
||||
|
||||
return notificationBuilder
|
||||
.setSmallIcon(R.drawable.ic_amnezia_round)
|
||||
.setContentTitle((serverName ?: "AmneziaVPN") + (protocol?.let { " $it" } ?: ""))
|
||||
.setContentTitle(serverName ?: "AmneziaVPN")
|
||||
.setContentText(context.getString(state))
|
||||
.setSubText(speedString)
|
||||
.setWhen(System.currentTimeMillis())
|
||||
@@ -96,10 +96,10 @@ class ServiceNotification(private val context: Context) {
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun updateNotification(serverName: String?, protocol: String?, state: ProtocolState) {
|
||||
fun updateNotification(serverName: String?, state: ProtocolState) {
|
||||
if (context.isNotificationPermissionGranted()) {
|
||||
Log.d(TAG, "Update notification: $serverName, $state")
|
||||
notificationManager.notify(NOTIFICATION_ID, buildNotification(serverName, protocol, state))
|
||||
notificationManager.notify(NOTIFICATION_ID, buildNotification(serverName, state))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ class ServiceNotification(private val context: Context) {
|
||||
context,
|
||||
DISCONNECT_REQUEST_CODE,
|
||||
Intent(ACTION_DISCONNECT).apply {
|
||||
setPackage(context.packageName)
|
||||
setPackage("org.amnezia.vpn")
|
||||
},
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
@@ -135,12 +135,10 @@ class ServiceNotification(private val context: Context) {
|
||||
DISCONNECTED -> {
|
||||
Action(
|
||||
0, context.getString(R.string.connect),
|
||||
PendingIntent.getBroadcast(
|
||||
createServicePendingIntent(
|
||||
context,
|
||||
CONNECT_REQUEST_CODE,
|
||||
Intent(ACTION_CONNECT).apply {
|
||||
setPackage(context.packageName)
|
||||
},
|
||||
Intent(context, AmneziaVpnService::class.java),
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
)
|
||||
@@ -150,6 +148,13 @@ class ServiceNotification(private val context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
private val createServicePendingIntent: (Context, Int, Intent, Int) -> PendingIntent =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
PendingIntent::getForegroundService
|
||||
} else {
|
||||
PendingIntent::getService
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun createNotificationChannel(context: Context) {
|
||||
with(NotificationManagerCompat.from(context)) {
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
package org.amnezia.vpn
|
||||
|
||||
import org.amnezia.vpn.protocol.Protocol
|
||||
import org.amnezia.vpn.protocol.awg.Awg
|
||||
import org.amnezia.vpn.protocol.cloak.Cloak
|
||||
import org.amnezia.vpn.protocol.openvpn.OpenVpn
|
||||
import org.amnezia.vpn.protocol.wireguard.Wireguard
|
||||
import org.amnezia.vpn.protocol.xray.Xray
|
||||
|
||||
enum class VpnProto(
|
||||
val label: String,
|
||||
val processName: String,
|
||||
val serviceClass: Class<out AmneziaVpnService>
|
||||
) {
|
||||
WIREGUARD(
|
||||
"WireGuard",
|
||||
"org.amnezia.vpn:amneziaAwgService",
|
||||
AwgService::class.java
|
||||
) {
|
||||
override fun createProtocol(): Protocol = Wireguard()
|
||||
},
|
||||
|
||||
AWG(
|
||||
"AmneziaWG",
|
||||
"org.amnezia.vpn:amneziaAwgService",
|
||||
AwgService::class.java
|
||||
) {
|
||||
override fun createProtocol(): Protocol = Awg()
|
||||
},
|
||||
|
||||
OPENVPN(
|
||||
"OpenVPN",
|
||||
"org.amnezia.vpn:amneziaOpenVpnService",
|
||||
OpenVpnService::class.java
|
||||
) {
|
||||
override fun createProtocol(): Protocol = OpenVpn()
|
||||
},
|
||||
|
||||
CLOAK(
|
||||
"Cloak",
|
||||
"org.amnezia.vpn:amneziaOpenVpnService",
|
||||
OpenVpnService::class.java
|
||||
) {
|
||||
override fun createProtocol(): Protocol = Cloak()
|
||||
},
|
||||
|
||||
XRAY(
|
||||
"XRay",
|
||||
"org.amnezia.vpn:amneziaXrayService",
|
||||
XrayService::class.java
|
||||
) {
|
||||
override fun createProtocol(): Protocol = Xray.instance
|
||||
},
|
||||
|
||||
SSXRAY(
|
||||
"SSXRay",
|
||||
"org.amnezia.vpn:amneziaXrayService",
|
||||
XrayService::class.java
|
||||
) {
|
||||
override fun createProtocol(): Protocol = Xray.instance
|
||||
};
|
||||
|
||||
private var _protocol: Protocol? = null
|
||||
val protocol: Protocol
|
||||
get() {
|
||||
if (_protocol == null) _protocol = createProtocol()
|
||||
return _protocol ?: throw AssertionError("Set to null by another thread")
|
||||
}
|
||||
|
||||
protected abstract fun createProtocol(): Protocol
|
||||
|
||||
companion object {
|
||||
fun get(protocolName: String): VpnProto = VpnProto.valueOf(protocolName.uppercase())
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import android.content.Intent
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_MASK
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import android.net.VpnService
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.widget.Toast
|
||||
@@ -19,11 +18,9 @@ import androidx.core.content.getSystemService
|
||||
import org.amnezia.vpn.util.Log
|
||||
|
||||
private const val TAG = "VpnRequestActivity"
|
||||
const val EXTRA_PROTOCOL = "PROTOCOL"
|
||||
|
||||
class VpnRequestActivity : ComponentActivity() {
|
||||
|
||||
private var vpnProto: VpnProto? = null
|
||||
private var userPresentReceiver: BroadcastReceiver? = null
|
||||
private val requestLauncher =
|
||||
registerForActivityResult(StartActivityForResult(), ::checkRequestResult)
|
||||
@@ -31,12 +28,6 @@ class VpnRequestActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Log.d(TAG, "Start request activity")
|
||||
vpnProto = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
intent.extras?.getSerializable(EXTRA_PROTOCOL, VpnProto::class.java)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
intent.extras?.getSerializable(EXTRA_PROTOCOL) as VpnProto
|
||||
}
|
||||
val requestIntent = VpnService.prepare(applicationContext)
|
||||
if (requestIntent != null) {
|
||||
if (getSystemService<KeyguardManager>()!!.isKeyguardLocked) {
|
||||
@@ -75,18 +66,10 @@ class VpnRequestActivity : ComponentActivity() {
|
||||
|
||||
private fun onPermissionGranted() {
|
||||
Toast.makeText(this, resources.getString(R.string.vpnGranted), Toast.LENGTH_LONG).show()
|
||||
vpnProto?.let { proto ->
|
||||
Intent(applicationContext, proto.serviceClass).apply {
|
||||
putExtra(AFTER_PERMISSION_CHECK, true)
|
||||
}.also {
|
||||
ContextCompat.startForegroundService(this, it)
|
||||
}
|
||||
} ?: run {
|
||||
Intent(this, AmneziaActivity::class.java).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}.also {
|
||||
startActivity(it)
|
||||
}
|
||||
Intent(applicationContext, AmneziaVpnService::class.java).apply {
|
||||
putExtra(AFTER_PERMISSION_CHECK, true)
|
||||
}.also {
|
||||
ContextCompat.startForegroundService(this, it)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
package org.amnezia.vpn
|
||||
|
||||
import android.app.Application
|
||||
import androidx.datastore.core.CorruptionException
|
||||
import androidx.datastore.core.MultiProcessDataStoreFactory
|
||||
import androidx.datastore.core.Serializer
|
||||
import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
|
||||
import androidx.datastore.dataStoreFile
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
import java.io.ObjectInputStream
|
||||
import java.io.ObjectOutputStream
|
||||
import java.io.OutputStream
|
||||
import java.io.Serializable
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.decodeFromByteArray
|
||||
import kotlinx.serialization.encodeToByteArray
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.amnezia.vpn.protocol.ProtocolState
|
||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||
import org.amnezia.vpn.util.Log
|
||||
@@ -24,14 +21,13 @@ import org.amnezia.vpn.util.Log
|
||||
private const val TAG = "VpnState"
|
||||
private const val STORE_FILE_NAME = "vpnState"
|
||||
|
||||
@Serializable
|
||||
data class VpnState(
|
||||
val protocolState: ProtocolState,
|
||||
val serverName: String? = null,
|
||||
val serverIndex: Int = -1,
|
||||
val vpnProto: VpnProto? = null
|
||||
) {
|
||||
val serverIndex: Int = -1
|
||||
) : Serializable {
|
||||
companion object {
|
||||
private const val serialVersionUID: Long = -1760654961004181606
|
||||
val defaultState: VpnState = VpnState(DISCONNECTED)
|
||||
}
|
||||
}
|
||||
@@ -41,11 +37,7 @@ object VpnStateStore {
|
||||
|
||||
private val dataStore = MultiProcessDataStoreFactory.create(
|
||||
serializer = VpnStateSerializer(),
|
||||
produceFile = { app.dataStoreFile(STORE_FILE_NAME) },
|
||||
corruptionHandler = ReplaceFileCorruptionHandler { e ->
|
||||
Log.e(TAG, "VpnState DataStore corrupted: $e")
|
||||
VpnState.defaultState
|
||||
}
|
||||
produceFile = { app.dataStoreFile(STORE_FILE_NAME) }
|
||||
)
|
||||
|
||||
fun init(app: Application) {
|
||||
@@ -53,36 +45,36 @@ object VpnStateStore {
|
||||
this.app = app
|
||||
}
|
||||
|
||||
fun dataFlow(): Flow<VpnState> = dataStore.data.catch { e ->
|
||||
Log.e(TAG, "Failed to read VpnState from store: ${e.message}")
|
||||
emit(VpnState.defaultState)
|
||||
}
|
||||
|
||||
suspend fun getVpnState(): VpnState = dataFlow().firstOrNull() ?: VpnState.defaultState
|
||||
fun dataFlow(): Flow<VpnState> = dataStore.data
|
||||
|
||||
suspend fun store(f: (vpnState: VpnState) -> VpnState) {
|
||||
try {
|
||||
dataStore.updateData(f)
|
||||
} catch (e: Exception) {
|
||||
} catch (e : Exception) {
|
||||
Log.e(TAG, "Failed to store VpnState: $e")
|
||||
Log.w(TAG, "Remove DataStore file")
|
||||
app.dataStoreFile(STORE_FILE_NAME).delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
private class VpnStateSerializer : Serializer<VpnState> {
|
||||
override val defaultValue: VpnState = VpnState.defaultState
|
||||
|
||||
override suspend fun readFrom(input: InputStream): VpnState = try {
|
||||
ProtoBuf.decodeFromByteArray<VpnState>(input.readBytes())
|
||||
} catch (e: SerializationException) {
|
||||
Log.e(TAG, "Failed to deserialize data: $e")
|
||||
throw CorruptionException("Failed to deserialize data", e)
|
||||
override suspend fun readFrom(input: InputStream): VpnState {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val bios = ByteArrayInputStream(input.readBytes())
|
||||
ObjectInputStream(bios).use {
|
||||
it.readObject() as VpnState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
override suspend fun writeTo(t: VpnState, output: OutputStream) =
|
||||
output.write(ProtoBuf.encodeToByteArray(t))
|
||||
override suspend fun writeTo(t: VpnState, output: OutputStream) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val baos = ByteArrayOutputStream()
|
||||
ObjectOutputStream(baos).use {
|
||||
it.writeObject(t)
|
||||
}
|
||||
output.write(baos.toByteArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
package org.amnezia.vpn
|
||||
|
||||
class XrayService : AmneziaVpnService()
|
||||
@@ -88,24 +88,16 @@ class NetworkState(
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val numberAttempts = 3
|
||||
var attemptCount = 0
|
||||
while(true) {
|
||||
try {
|
||||
try {
|
||||
connectivityManager.requestNetwork(networkRequest, networkCallback, handler)
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Failed to bind network listener: $e")
|
||||
// Android 11 bug: https://issuetracker.google.com/issues/175055271
|
||||
if (e.message?.startsWith("Package android does not belong to") == true) {
|
||||
delay(1000)
|
||||
connectivityManager.requestNetwork(networkRequest, networkCallback, handler)
|
||||
break
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Failed to bind network listener: $e")
|
||||
// Android 11 bug: https://issuetracker.google.com/issues/175055271
|
||||
if (e.message?.startsWith("Package android does not belong to") == true) {
|
||||
if (++attemptCount > numberAttempts) {
|
||||
throw e
|
||||
}
|
||||
delay(1000)
|
||||
continue
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package org.amnezia.vpn.protocol.wireguard
|
||||
|
||||
import android.content.Context
|
||||
import android.net.VpnService.Builder
|
||||
import java.util.TreeMap
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.amnezia.awg.GoBackend
|
||||
import org.amnezia.vpn.protocol.Protocol
|
||||
import org.amnezia.vpn.protocol.ProtocolState
|
||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||
import org.amnezia.vpn.protocol.Statistics
|
||||
@@ -75,8 +78,9 @@ open class Wireguard : Protocol() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun internalInit() {
|
||||
if (!isInitialized) loadSharedLibrary(context, "wg-go")
|
||||
override fun initialize(context: Context, state: MutableStateFlow<ProtocolState>, onError: (String) -> Unit) {
|
||||
super.initialize(context, state, onError)
|
||||
loadSharedLibrary(context, "wg-go")
|
||||
}
|
||||
|
||||
override fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
plugins {
|
||||
id(libs.plugins.android.library.get().pluginId)
|
||||
id(libs.plugins.kotlin.android.get().pluginId)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(17)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "org.amnezia.vpn.protocol.xray"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(project(":utils"))
|
||||
compileOnly(project(":protocolApi"))
|
||||
implementation(project(":xray:libXray"))
|
||||
implementation(libs.kotlinx.coroutines)
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
@file:Suppress("UnstableApiUsage")
|
||||
|
||||
configurations {
|
||||
maybeCreate("default")
|
||||
}
|
||||
artifacts.add("default", file("libxray.aar"))
|
||||
@@ -1,239 +0,0 @@
|
||||
package org.amnezia.vpn.protocol.xray
|
||||
|
||||
import android.content.Context
|
||||
import android.net.VpnService.Builder
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import go.Seq
|
||||
import org.amnezia.vpn.protocol.BadConfigException
|
||||
import org.amnezia.vpn.protocol.Protocol
|
||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||
import org.amnezia.vpn.protocol.Statistics
|
||||
import org.amnezia.vpn.protocol.VpnStartException
|
||||
import org.amnezia.vpn.protocol.xray.libXray.DialerController
|
||||
import org.amnezia.vpn.protocol.xray.libXray.LibXray
|
||||
import org.amnezia.vpn.protocol.xray.libXray.Logger
|
||||
import org.amnezia.vpn.protocol.xray.libXray.Tun2SocksConfig
|
||||
import org.amnezia.vpn.util.Log
|
||||
import org.amnezia.vpn.util.net.InetNetwork
|
||||
import org.amnezia.vpn.util.net.parseInetAddress
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
* Config example:
|
||||
* {
|
||||
* "appSplitTunnelType": 0,
|
||||
* "config_version": 0,
|
||||
* "description": "Server 1",
|
||||
* "dns1": "1.1.1.1",
|
||||
* "dns2": "1.0.0.1",
|
||||
* "hostName": "100.100.100.0",
|
||||
* "protocol": "xray",
|
||||
* "splitTunnelApps": [],
|
||||
* "splitTunnelSites": [],
|
||||
* "splitTunnelType": 0,
|
||||
* "xray_config_data": {
|
||||
* "inbounds": [
|
||||
* {
|
||||
* "listen": "127.0.0.1",
|
||||
* "port": 8080,
|
||||
* "protocol": "socks",
|
||||
* "settings": {
|
||||
* "udp": true
|
||||
* }
|
||||
* }
|
||||
* ],
|
||||
* "log": {
|
||||
* "loglevel": "error"
|
||||
* },
|
||||
* "outbounds": [
|
||||
* {
|
||||
* "protocol": "vless",
|
||||
* "settings": {
|
||||
* "vnext": [
|
||||
* {
|
||||
* "address": "100.100.100.0",
|
||||
* "port": 443,
|
||||
* "users": [
|
||||
* {
|
||||
* "encryption": "none",
|
||||
* "flow": "xtls-rprx-vision",
|
||||
* "id": "id"
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* ]
|
||||
* },
|
||||
* "streamSettings": {
|
||||
* "network": "tcp",
|
||||
* "realitySettings": {
|
||||
* "fingerprint": "chrome",
|
||||
* "publicKey": "publicKey",
|
||||
* "serverName": "google.com",
|
||||
* "shortId": "id",
|
||||
* "spiderX": ""
|
||||
* },
|
||||
* "security": "reality"
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*/
|
||||
|
||||
private const val TAG = "Xray"
|
||||
private const val LIBXRAY_TAG = "libXray"
|
||||
|
||||
class Xray : Protocol() {
|
||||
|
||||
private var isRunning: Boolean = false
|
||||
override val statistics: Statistics = Statistics.EMPTY_STATISTICS
|
||||
|
||||
override fun internalInit() {
|
||||
Seq.setContext(context)
|
||||
if (!isInitialized) {
|
||||
LibXray.initLogger(object : Logger {
|
||||
override fun warning(s: String) = Log.w(LIBXRAY_TAG, s)
|
||||
|
||||
override fun error(s: String) = Log.e(LIBXRAY_TAG, s)
|
||||
|
||||
override fun write(msg: ByteArray): Long {
|
||||
Log.w(LIBXRAY_TAG, String(msg))
|
||||
return msg.size.toLong()
|
||||
}
|
||||
}).isNotNullOrBlank { err ->
|
||||
Log.w(TAG, "Failed to initialize logger: $err")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||
if (isRunning) {
|
||||
Log.w(TAG, "XRay already running")
|
||||
return
|
||||
}
|
||||
|
||||
val xrayJsonConfig = config.optJSONObject("xray_config_data")
|
||||
?: config.optJSONObject("ssxray_config_data")
|
||||
?: throw BadConfigException("config_data not found")
|
||||
val xrayConfig = parseConfig(config, xrayJsonConfig)
|
||||
|
||||
(xrayJsonConfig.optJSONObject("log") ?: JSONObject().also { xrayJsonConfig.put("log", it) })
|
||||
.put("loglevel", "warning")
|
||||
.put("access", "none") // disable access log
|
||||
|
||||
start(xrayConfig, xrayJsonConfig.toString(), vpnBuilder, protect)
|
||||
state.value = CONNECTED
|
||||
isRunning = true
|
||||
}
|
||||
|
||||
private fun parseConfig(config: JSONObject, xrayJsonConfig: JSONObject): XrayConfig {
|
||||
return XrayConfig.build {
|
||||
addAddress(XrayConfig.DEFAULT_IPV4_ADDRESS)
|
||||
|
||||
config.optString("dns1").let {
|
||||
if (it.isNotBlank()) addDnsServer(parseInetAddress(it))
|
||||
}
|
||||
|
||||
config.optString("dns2").let {
|
||||
if (it.isNotBlank()) addDnsServer(parseInetAddress(it))
|
||||
}
|
||||
|
||||
addRoute(InetNetwork("0.0.0.0", 0))
|
||||
addRoute(InetNetwork("2000::0", 3))
|
||||
config.getString("hostName").let {
|
||||
excludeRoute(InetNetwork(it, 32))
|
||||
}
|
||||
|
||||
config.optString("mtu").let {
|
||||
if (it.isNotBlank()) setMtu(it.toInt())
|
||||
}
|
||||
|
||||
val socksConfig = xrayJsonConfig.getJSONArray("inbounds")[0] as JSONObject
|
||||
socksConfig.getInt("port").let { setSocksPort(it) }
|
||||
|
||||
configSplitTunneling(config)
|
||||
configAppSplitTunneling(config)
|
||||
}
|
||||
}
|
||||
|
||||
private fun start(config: XrayConfig, configJson: String, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||
buildVpnInterface(config, vpnBuilder)
|
||||
|
||||
DialerController { protect(it.toInt()) }.also {
|
||||
LibXray.registerDialerController(it).isNotNullOrBlank { err ->
|
||||
throw VpnStartException("Failed to register dialer controller: $err")
|
||||
}
|
||||
LibXray.registerListenerController(it).isNotNullOrBlank { err ->
|
||||
throw VpnStartException("Failed to register listener controller: $err")
|
||||
}
|
||||
}
|
||||
|
||||
vpnBuilder.establish().use { tunFd ->
|
||||
if (tunFd == null) {
|
||||
throw VpnStartException("Create VPN interface: permission not granted or revoked")
|
||||
}
|
||||
Log.d(TAG, "Run tun2Socks")
|
||||
runTun2Socks(config, tunFd.detachFd())
|
||||
|
||||
Log.d(TAG, "Run XRay")
|
||||
Log.i(TAG, "xray ${LibXray.xrayVersion()}")
|
||||
val assetsPath = context.getDir("assets", Context.MODE_PRIVATE).absolutePath
|
||||
LibXray.initXray(assetsPath)
|
||||
val geoDir = File(assetsPath, "geo").absolutePath
|
||||
val configPath = File(context.cacheDir, "config.json")
|
||||
Log.d(TAG, "xray.location.asset: $geoDir")
|
||||
Log.d(TAG, "config: $configPath")
|
||||
try {
|
||||
configPath.writeText(configJson)
|
||||
} catch (e: IOException) {
|
||||
LibXray.stopTun2Socks()
|
||||
throw VpnStartException("Failed to write xray config: ${e.message}")
|
||||
}
|
||||
LibXray.runXray(geoDir, configPath.absolutePath, config.maxMemory).isNotNullOrBlank { err ->
|
||||
LibXray.stopTun2Socks()
|
||||
throw VpnStartException("Failed to start xray: $err")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun stopVpn() {
|
||||
LibXray.stopXray().isNotNullOrBlank { err ->
|
||||
Log.e(TAG, "Failed to stop XRay: $err")
|
||||
}
|
||||
LibXray.stopTun2Socks().isNotNullOrBlank { err ->
|
||||
Log.e(TAG, "Failed to stop tun2Socks: $err")
|
||||
}
|
||||
|
||||
isRunning = false
|
||||
state.value = DISCONNECTED
|
||||
}
|
||||
|
||||
override fun reconnectVpn(vpnBuilder: Builder) {
|
||||
state.value = CONNECTED
|
||||
}
|
||||
|
||||
private fun runTun2Socks(config: XrayConfig, fd: Int) {
|
||||
val tun2SocksConfig = Tun2SocksConfig().apply {
|
||||
mtu = config.mtu.toLong()
|
||||
proxy = "socks5://127.0.0.1:${config.socksPort}"
|
||||
device = "fd://$fd"
|
||||
logLevel = "warning"
|
||||
}
|
||||
LibXray.startTun2Socks(tun2SocksConfig, fd.toLong()).isNotNullOrBlank { err ->
|
||||
throw VpnStartException("Failed to start tun2socks: $err")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val instance: Xray by lazy { Xray() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun String?.isNotNullOrBlank(block: (String) -> Unit) {
|
||||
if (!this.isNullOrBlank()) {
|
||||
block(this)
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package org.amnezia.vpn.protocol.xray
|
||||
|
||||
import org.amnezia.vpn.protocol.ProtocolConfig
|
||||
import org.amnezia.vpn.util.net.InetNetwork
|
||||
|
||||
private const val XRAY_DEFAULT_MTU = 1500
|
||||
private const val XRAY_DEFAULT_MAX_MEMORY: Long = 50 shl 20 // 50 MB
|
||||
|
||||
class XrayConfig protected constructor(
|
||||
protocolConfigBuilder: ProtocolConfig.Builder,
|
||||
val socksPort: Int,
|
||||
val maxMemory: Long,
|
||||
) : ProtocolConfig(protocolConfigBuilder) {
|
||||
|
||||
protected constructor(builder: Builder) : this(
|
||||
builder,
|
||||
builder.socksPort,
|
||||
builder.maxMemory
|
||||
)
|
||||
|
||||
class Builder : ProtocolConfig.Builder(false) {
|
||||
internal var socksPort: Int = 0
|
||||
private set
|
||||
|
||||
internal var maxMemory: Long = XRAY_DEFAULT_MAX_MEMORY
|
||||
private set
|
||||
|
||||
override var mtu: Int = XRAY_DEFAULT_MTU
|
||||
|
||||
fun setSocksPort(port: Int) = apply { socksPort = port }
|
||||
|
||||
fun setMaxMemory(maxMemory: Long) = apply { this.maxMemory = maxMemory }
|
||||
|
||||
override fun build(): XrayConfig = configBuild().run { XrayConfig(this@Builder) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal val DEFAULT_IPV4_ADDRESS: InetNetwork = InetNetwork("10.0.42.2", 30)
|
||||
|
||||
inline fun build(block: Builder.() -> Unit): XrayConfig = Builder().apply(block).build()
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,3 @@ foreach(abi IN ITEMS ${QT_ANDROID_ABIS})
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/libssh/android/${abi}/libssh.so
|
||||
)
|
||||
endforeach()
|
||||
|
||||
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/xray/android/libxray.aar
|
||||
DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/android/xray/libXray)
|
||||
|
||||
@@ -119,21 +119,18 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString,
|
||||
|
||||
if (!m_settings->isSitesSplitTunnelingEnabled()) {
|
||||
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
// Prevent ipv6 leak
|
||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
||||
#endif
|
||||
config.append("block-ipv6\n");
|
||||
} else if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
|
||||
|
||||
// no redirect-gateway
|
||||
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#ifndef Q_OS_ANDROID
|
||||
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
|
||||
#endif
|
||||
// Prevent ipv6 leak
|
||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
||||
#endif
|
||||
config.append("block-ipv6\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,8 +69,6 @@ QVector<amnezia::Proto> ContainerProps::protocolsForContainer(amnezia::DockerCon
|
||||
|
||||
case DockerContainer::Sftp: return { Proto::Sftp };
|
||||
|
||||
case DockerContainer::Socks5Proxy: return { Proto::Socks5Proxy };
|
||||
|
||||
default: return { defaultProtocol(container) };
|
||||
}
|
||||
}
|
||||
@@ -90,18 +88,17 @@ QMap<DockerContainer, QString> ContainerProps::containerHumanNames()
|
||||
{
|
||||
return { { DockerContainer::None, "Not installed" },
|
||||
{ DockerContainer::OpenVpn, "OpenVPN" },
|
||||
{ DockerContainer::ShadowSocks, "OpenVPN over SS" },
|
||||
{ DockerContainer::ShadowSocks, "ShadowSocks" },
|
||||
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
|
||||
{ DockerContainer::WireGuard, "WireGuard" },
|
||||
{ DockerContainer::Awg, "AmneziaWG" },
|
||||
{ DockerContainer::Xray, "XRay" },
|
||||
{ DockerContainer::Ipsec, QObject::tr("IPsec") },
|
||||
{ DockerContainer::SSXray, "Shadowsocks"},
|
||||
{ DockerContainer::SSXray, "ShadowSocks"},
|
||||
|
||||
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
||||
{ DockerContainer::Dns, QObject::tr("AmneziaDNS") },
|
||||
{ DockerContainer::Sftp, QObject::tr("SFTP file sharing service") },
|
||||
{ DockerContainer::Socks5Proxy, QObject::tr("SOCKS5 proxy server") } };
|
||||
{ DockerContainer::Dns, QObject::tr("Amnezia DNS") },
|
||||
{ DockerContainer::Sftp, QObject::tr("Sftp file sharing service") } };
|
||||
}
|
||||
|
||||
QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
|
||||
@@ -110,7 +107,7 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
|
||||
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.") },
|
||||
{ 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 it "
|
||||
"may be recognized by analysis systems in some highly censored regions.") },
|
||||
{ DockerContainer::Cloak,
|
||||
QObject::tr("OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against "
|
||||
@@ -127,16 +124,14 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
|
||||
QObject::tr("XRay with REALITY - Suitable for countries with the highest level of internet censorship. "
|
||||
"Traffic masking as web traffic at the TLS level, and protection against detection by active probing methods.") },
|
||||
{ DockerContainer::Ipsec,
|
||||
QObject::tr("IKEv2/IPsec - Modern stable protocol, a bit faster than others, restores connection after "
|
||||
QObject::tr("IKEv2 - Modern stable protocol, a bit faster than others, restores connection after "
|
||||
"signal loss. It has native support on the latest versions of Android and iOS.") },
|
||||
|
||||
{ DockerContainer::TorWebSite, QObject::tr("Deploy a WordPress site on the Tor network in two clicks.") },
|
||||
{ DockerContainer::Dns,
|
||||
QObject::tr("Replace the current DNS server with your own. This will increase your privacy level.") },
|
||||
{ DockerContainer::Sftp,
|
||||
QObject::tr("Create a file vault on your server to securely store and transfer files.") },
|
||||
{ DockerContainer::Socks5Proxy,
|
||||
QObject::tr("") } };
|
||||
QObject::tr("Create a file vault on your server to securely store and transfer files.") } };
|
||||
}
|
||||
|
||||
QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
||||
@@ -164,6 +159,7 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
||||
"However, certain traffic analysis systems might still detect a Shadowsocks connection. "
|
||||
"Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol.\n\n"
|
||||
"* Available in the AmneziaVPN only on desktop platforms\n"
|
||||
"* Normal power consumption on mobile devices\n\n"
|
||||
"* Configurable encryption protocol\n"
|
||||
"* Detectable by some DPI systems\n"
|
||||
"* Works over TCP network protocol.") },
|
||||
@@ -244,8 +240,7 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
||||
QObject::tr("After installation, Amnezia will create a\n\n file storage on your server. "
|
||||
"You will be able to access it using\n FileZilla or other SFTP clients, "
|
||||
"as well as mount the disk on your device to access\n it directly from your device.\n\n"
|
||||
"For more detailed information, you can\n find it in the support section under \"Create SFTP file storage.\" ") },
|
||||
{ DockerContainer::Socks5Proxy, QObject::tr("SOCKS5 proxy server") }
|
||||
"For more detailed information, you can\n find it in the support section under \"Create SFTP file storage.\" ") }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -270,7 +265,6 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
|
||||
case DockerContainer::TorWebSite: return Proto::TorWebSite;
|
||||
case DockerContainer::Dns: return Proto::Dns;
|
||||
case DockerContainer::Sftp: return Proto::Sftp;
|
||||
case DockerContainer::Socks5Proxy: return Proto::Socks5Proxy;
|
||||
default: return Proto::Any;
|
||||
}
|
||||
}
|
||||
@@ -285,9 +279,8 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
|
||||
case DockerContainer::WireGuard: return true;
|
||||
case DockerContainer::OpenVpn: return true;
|
||||
case DockerContainer::Awg: return true;
|
||||
case DockerContainer::Xray: return true;
|
||||
case DockerContainer::Cloak: return true;
|
||||
case DockerContainer::SSXray: return true;
|
||||
case DockerContainer::Cloak:
|
||||
return true;
|
||||
// case DockerContainer::ShadowSocks: return true;
|
||||
default: return false;
|
||||
}
|
||||
@@ -305,8 +298,6 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
|
||||
case DockerContainer::ShadowSocks: return false;
|
||||
case DockerContainer::Awg: return true;
|
||||
case DockerContainer::Cloak: return true;
|
||||
case DockerContainer::Xray: return true;
|
||||
case DockerContainer::SSXray: return true;
|
||||
default: return false;
|
||||
}
|
||||
|
||||
@@ -376,7 +367,6 @@ bool ContainerProps::isShareable(DockerContainer container)
|
||||
case DockerContainer::TorWebSite: return false;
|
||||
case DockerContainer::Dns: return false;
|
||||
case DockerContainer::Sftp: return false;
|
||||
case DockerContainer::Socks5Proxy: return false;
|
||||
default: return true;
|
||||
}
|
||||
}
|
||||
@@ -390,18 +380,3 @@ QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol,
|
||||
|
||||
return QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
||||
}
|
||||
|
||||
int ContainerProps::installPageOrder(DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case DockerContainer::OpenVpn: return 4;
|
||||
case DockerContainer::Cloak: return 5;
|
||||
case DockerContainer::ShadowSocks: return 6;
|
||||
case DockerContainer::WireGuard: return 2;
|
||||
case DockerContainer::Awg: return 1;
|
||||
case DockerContainer::Xray: return 3;
|
||||
case DockerContainer::Ipsec: return 7;
|
||||
case DockerContainer::SSXray: return 8;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,7 @@ namespace amnezia
|
||||
// non-vpn
|
||||
TorWebSite,
|
||||
Dns,
|
||||
Sftp,
|
||||
Socks5Proxy
|
||||
Sftp
|
||||
};
|
||||
Q_ENUM_NS(DockerContainer)
|
||||
} // namespace ContainerEnumNS
|
||||
@@ -72,8 +71,6 @@ namespace amnezia
|
||||
static bool isShareable(amnezia::DockerContainer container);
|
||||
|
||||
static QJsonObject getProtocolConfigFromContainer(const amnezia::Proto protocol, const QJsonObject &containerConfig);
|
||||
|
||||
static int installPageOrder(amnezia::DockerContainer container);
|
||||
};
|
||||
|
||||
static void declareQmlContainerEnum()
|
||||
|
||||
@@ -40,28 +40,6 @@ void ApiController::processApiConfig(const QString &protocol, const ApiControlle
|
||||
return;
|
||||
} else if (protocol == configKey::awg) {
|
||||
config.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey);
|
||||
auto serverConfig = QJsonDocument::fromJson(config.toUtf8()).object();
|
||||
auto containers = serverConfig.value(config_key::containers).toArray();
|
||||
if (containers.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
auto container = containers.at(0).toObject();
|
||||
QString containerName = ContainerProps::containerTypeToString(DockerContainer::Awg);
|
||||
auto containerConfig = container.value(containerName).toObject();
|
||||
auto protocolConfig = QJsonDocument::fromJson(containerConfig.value(config_key::last_config).toString().toUtf8()).object();
|
||||
containerConfig[config_key::junkPacketCount] = protocolConfig.value(config_key::junkPacketCount);
|
||||
containerConfig[config_key::junkPacketMinSize] = protocolConfig.value(config_key::junkPacketMinSize);
|
||||
containerConfig[config_key::junkPacketMaxSize] = protocolConfig.value(config_key::junkPacketMaxSize);
|
||||
containerConfig[config_key::initPacketJunkSize] = protocolConfig.value(config_key::initPacketJunkSize);
|
||||
containerConfig[config_key::responsePacketJunkSize] = protocolConfig.value(config_key::responsePacketJunkSize);
|
||||
containerConfig[config_key::initPacketMagicHeader] = protocolConfig.value(config_key::initPacketMagicHeader);
|
||||
containerConfig[config_key::responsePacketMagicHeader] = protocolConfig.value(config_key::responsePacketMagicHeader);
|
||||
containerConfig[config_key::underloadPacketMagicHeader] = protocolConfig.value(config_key::underloadPacketMagicHeader);
|
||||
containerConfig[config_key::transportPacketMagicHeader] = protocolConfig.value(config_key::transportPacketMagicHeader);
|
||||
container[containerName] = containerConfig;
|
||||
containers.replace(0, container);
|
||||
serverConfig[config_key::containers] = containers;
|
||||
config = QString(QJsonDocument(serverConfig).toJson());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ ErrorCode ServerController::runContainerScript(const ServerCredentials &credenti
|
||||
if (e)
|
||||
return e;
|
||||
|
||||
QString runner = QString("sudo docker exec -i $CONTAINER_NAME %2 %1 ").arg(fileName, (container == DockerContainer::Socks5Proxy ? "sh" : "bash"));
|
||||
QString runner = QString("sudo docker exec -i $CONTAINER_NAME bash %1 ").arg(fileName);
|
||||
e = runScript(credentials, replaceVars(runner, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
||||
|
||||
QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName);
|
||||
@@ -376,10 +376,6 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
||||
return true;
|
||||
}
|
||||
|
||||
if (container == DockerContainer::Socks5Proxy) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -520,7 +516,6 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
||||
const QJsonObject &amneziaWireguarConfig = config.value(ProtocolProps::protoToString(Proto::Awg)).toObject();
|
||||
const QJsonObject &xrayConfig = config.value(ProtocolProps::protoToString(Proto::Xray)).toObject();
|
||||
const QJsonObject &sftpConfig = config.value(ProtocolProps::protoToString(Proto::Sftp)).toObject();
|
||||
const QJsonObject &socks5ProxyConfig = config.value(ProtocolProps::protoToString(Proto::Socks5Proxy)).toObject();
|
||||
|
||||
Vars vars;
|
||||
|
||||
@@ -618,14 +613,6 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
||||
vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } });
|
||||
vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } });
|
||||
|
||||
// Socks5 proxy vars
|
||||
vars.append({ { "$SOCKS5_PROXY_PORT", socks5ProxyConfig.value(config_key::port).toString(protocols::socks5Proxy::defaultPort) } });
|
||||
auto username = socks5ProxyConfig.value(config_key:: userName).toString();
|
||||
auto password = socks5ProxyConfig.value(config_key::password).toString();
|
||||
QString socks5user = (!username.isEmpty() && !password.isEmpty()) ? QString("users %1:CL:%2").arg(username, password) : "";
|
||||
vars.append({ { "$SOCKS5_USER", socks5user } });
|
||||
vars.append({ { "$SOCKS5_AUTH_TYPE", socks5user.isEmpty() ? "none" : "strong" } });
|
||||
|
||||
QString serverIp = NetworkUtilities::getIPAddress(credentials.hostName);
|
||||
if (!serverIp.isEmpty()) {
|
||||
vars.append({ { "$SERVER_IP_ADDRESS", serverIp } });
|
||||
|
||||
@@ -9,7 +9,7 @@ QString errorString(ErrorCode code) {
|
||||
|
||||
// General error codes
|
||||
case(ErrorCode::NoError): errorMessage = QObject::tr("No error"); break;
|
||||
case(ErrorCode::UnknownError): errorMessage = QObject::tr("Unknown error"); break;
|
||||
case(ErrorCode::UnknownError): errorMessage = QObject::tr("Unknown Error"); break;
|
||||
case(ErrorCode::NotImplementedError): errorMessage = QObject::tr("Function not implemented"); break;
|
||||
case(ErrorCode::AmneziaServiceNotRunning): errorMessage = QObject::tr("Background service is not running"); break;
|
||||
|
||||
@@ -23,15 +23,15 @@ QString errorString(ErrorCode code) {
|
||||
case(ErrorCode::ServerPacketManagerError): errorMessage = QObject::tr("Server error: Packet manager error"); break;
|
||||
|
||||
// Libssh errors
|
||||
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
|
||||
case(ErrorCode::SshInterruptedError): errorMessage = QObject::tr("SSH request was interrupted"); break;
|
||||
case(ErrorCode::SshInternalError): errorMessage = QObject::tr("SSH internal error"); break;
|
||||
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("Ssh request was denied"); break;
|
||||
case(ErrorCode::SshInterruptedError): errorMessage = QObject::tr("Ssh request was interrupted"); break;
|
||||
case(ErrorCode::SshInternalError): errorMessage = QObject::tr("Ssh internal error"); break;
|
||||
case(ErrorCode::SshPrivateKeyError): errorMessage = QObject::tr("Invalid private key or invalid passphrase entered"); break;
|
||||
case(ErrorCode::SshPrivateKeyFormatError): errorMessage = QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types"); break;
|
||||
case(ErrorCode::SshTimeoutError): errorMessage = QObject::tr("Timeout connecting to server"); break;
|
||||
|
||||
// Ssh scp errors
|
||||
case(ErrorCode::SshScpFailureError): errorMessage = QObject::tr("SCP error: Generic failure"); break;
|
||||
case(ErrorCode::SshScpFailureError): errorMessage = QObject::tr("Scp error: Generic failure"); break;
|
||||
|
||||
// Local errors
|
||||
case (ErrorCode::OpenVpnConfigMissing): errorMessage = QObject::tr("OpenVPN config missing"); break;
|
||||
@@ -39,7 +39,7 @@ QString errorString(ErrorCode code) {
|
||||
|
||||
// Distro errors
|
||||
case (ErrorCode::OpenVpnExecutableMissing): errorMessage = QObject::tr("OpenVPN executable missing"); break;
|
||||
case (ErrorCode::ShadowSocksExecutableMissing): errorMessage = QObject::tr("Shadowsocks (ss-local) executable missing"); break;
|
||||
case (ErrorCode::ShadowSocksExecutableMissing): errorMessage = QObject::tr("ShadowSocks (ss-local) executable missing"); break;
|
||||
case (ErrorCode::CloakExecutableMissing): errorMessage = QObject::tr("Cloak (ck-client) executable missing"); break;
|
||||
case (ErrorCode::AmneziaServiceConnectionFailed): errorMessage = QObject::tr("Amnezia helper service error"); break;
|
||||
case (ErrorCode::OpenSslFailed): errorMessage = QObject::tr("OpenSSL failed"); break;
|
||||
|
||||
@@ -18,7 +18,6 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container)
|
||||
case DockerContainer::TorWebSite: return QLatin1String("website_tor");
|
||||
case DockerContainer::Dns: return QLatin1String("dns");
|
||||
case DockerContainer::Sftp: return QLatin1String("sftp");
|
||||
case DockerContainer::Socks5Proxy: return QLatin1String("socks5_proxy");
|
||||
default: return QString();
|
||||
}
|
||||
}
|
||||
|
||||
BIN
client/fonts/Lato-Black.ttf
Normal file
BIN
client/fonts/Lato-BlackItalic.ttf
Normal file
BIN
client/fonts/Lato-Bold.ttf
Normal file
BIN
client/fonts/Lato-BoldItalic.ttf
Normal file
BIN
client/fonts/Lato-Hairline.ttf
Normal file
BIN
client/fonts/Lato-HairlineItalic.ttf
Normal file
BIN
client/fonts/Lato-Heavy.ttf
Normal file
BIN
client/fonts/Lato-HeavyItalic.ttf
Normal file
BIN
client/fonts/Lato-Italic.ttf
Normal file
BIN
client/fonts/Lato-Light.ttf
Normal file
BIN
client/fonts/Lato-LightItalic.ttf
Normal file
BIN
client/fonts/Lato-Medium.ttf
Normal file
BIN
client/fonts/Lato-MediumItalic.ttf
Normal file
BIN
client/fonts/Lato-Regular.ttf
Normal file
BIN
client/fonts/Lato-Semibold.ttf
Normal file
BIN
client/fonts/Lato-SemiboldItalic.ttf
Normal file
BIN
client/fonts/Lato-Thin.ttf
Normal file
BIN
client/fonts/Lato-ThinItalic.ttf
Normal file
@@ -50,12 +50,10 @@ set_target_properties("networkextension" PROPERTIES
|
||||
find_library(FW_ASSETS_LIBRARY AssetsLibrary)
|
||||
find_library(FW_MOBILE_CORE MobileCoreServices)
|
||||
find_library(FW_UI_KIT UIKit)
|
||||
find_library(FW_LIBRESOLV libresolv.9.tbd)
|
||||
|
||||
target_link_libraries(networkextension PRIVATE ${FW_ASSETS_LIBRARY})
|
||||
target_link_libraries(networkextension PRIVATE ${FW_MOBILE_CORE})
|
||||
target_link_libraries(networkextension PRIVATE ${FW_UI_KIT})
|
||||
target_link_libraries(networkextension PRIVATE ${FW_LIBRESOLV})
|
||||
|
||||
target_compile_options(networkextension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\")
|
||||
target_compile_options(networkextension PRIVATE -DNETWORK_EXTENSION=1)
|
||||
@@ -82,16 +80,13 @@ target_sources(networkextension PRIVATE
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/Array+ConcurrentMap.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddress+AddrInfo.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PrivateKey.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/HevSocksTunnel.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/NELogController.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+WireGuard.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+OpenVPN.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+Xray.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/WGConfig.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/XrayConfig.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/iosglue.mm
|
||||
)
|
||||
|
||||
@@ -119,5 +114,3 @@ target_include_directories(networkextension PRIVATE ${CLIENT_ROOT_DIR})
|
||||
target_include_directories(networkextension PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
target_link_libraries(networkextension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/wireguard/ios/arm64/libwg-go.a)
|
||||
|
||||
target_link_libraries(networkextension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework)
|
||||
|
||||
@@ -33,10 +33,6 @@ void debugMessageHandler(QtMsgType type, const QMessageLogContext& context, cons
|
||||
}
|
||||
|
||||
// Skip annoying messages from Qt
|
||||
if (msg.contains("OpenType support missing for")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.startsWith("Unknown property") || msg.startsWith("Could not create pixmap") || msg.startsWith("Populating font") || msg.startsWith("stale focus object")) {
|
||||
return;
|
||||
}
|
||||
@@ -99,29 +95,6 @@ void Logger::deInit()
|
||||
m_file.close();
|
||||
}
|
||||
|
||||
bool Logger::setServiceLogsEnabled(bool enabled) {
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
IpcClient *m_IpcClient = new IpcClient;
|
||||
|
||||
if (!m_IpcClient->isSocketConnected()) {
|
||||
if (!IpcClient::init(m_IpcClient)) {
|
||||
qWarning() << "Error occurred when init IPC client";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_IpcClient->Interface()) {
|
||||
m_IpcClient->Interface()->setLogsEnabled(enabled);
|
||||
}
|
||||
else {
|
||||
qWarning() << "Error occurred setting up service logs";
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QString Logger::userLogsDir()
|
||||
{
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/log";
|
||||
@@ -164,9 +137,7 @@ bool Logger::openLogsFolder()
|
||||
bool Logger::openServiceLogsFolder()
|
||||
{
|
||||
QString path = Utils::systemLogPath();
|
||||
#ifdef Q_OS_WIN
|
||||
path = "file:///" + path;
|
||||
#endif
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
||||
return true;
|
||||
}
|
||||
@@ -209,7 +180,8 @@ void Logger::clearServiceLogs()
|
||||
}
|
||||
|
||||
if (m_IpcClient->Interface()) {
|
||||
m_IpcClient->Interface()->clearLogs();
|
||||
m_IpcClient->Interface()->setLogsEnabled(false);
|
||||
m_IpcClient->Interface()->cleanUp();
|
||||
}
|
||||
else {
|
||||
qWarning() << "Error occurred cleaning up service logs";
|
||||
|
||||
@@ -26,7 +26,6 @@ public:
|
||||
|
||||
static bool init();
|
||||
static void deInit();
|
||||
static bool setServiceLogsEnabled(bool enabled);
|
||||
static bool openLogsFolder();
|
||||
static bool openServiceLogsFolder();
|
||||
static QString appLogFileNamePath();
|
||||
|
||||
@@ -179,11 +179,6 @@ bool AndroidController::isCameraPresent()
|
||||
return callActivityMethod<jboolean>("isCameraPresent", "()Z");
|
||||
}
|
||||
|
||||
bool AndroidController::isOnTv()
|
||||
{
|
||||
return callActivityMethod<jboolean>("isOnTv", "()Z");
|
||||
}
|
||||
|
||||
void AndroidController::startQrReaderActivity()
|
||||
{
|
||||
callActivityMethod("startQrCodeReader", "()V");
|
||||
|
||||
@@ -35,7 +35,6 @@ public:
|
||||
void saveFile(const QString &fileName, const QString &data);
|
||||
QString openFile(const QString &filter);
|
||||
bool isCameraPresent();
|
||||
bool isOnTv();
|
||||
void startQrReaderActivity();
|
||||
void setSaveLogs(bool enabled);
|
||||
void exportLogsFile(const QString &fileName);
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
import HevSocks5Tunnel
|
||||
|
||||
public enum Socks5Tunnel {
|
||||
|
||||
private static var tunnelFileDescriptor: Int32? {
|
||||
var ctlInfo = ctl_info()
|
||||
withUnsafeMutablePointer(to: &ctlInfo.ctl_name) {
|
||||
$0.withMemoryRebound(to: CChar.self, capacity: MemoryLayout.size(ofValue: $0.pointee)) {
|
||||
_ = strcpy($0, "com.apple.net.utun_control")
|
||||
}
|
||||
}
|
||||
for fd: Int32 in 0...1024 {
|
||||
var addr = sockaddr_ctl()
|
||||
var ret: Int32 = -1
|
||||
var len = socklen_t(MemoryLayout.size(ofValue: addr))
|
||||
withUnsafeMutablePointer(to: &addr) {
|
||||
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
|
||||
ret = getpeername(fd, $0, &len)
|
||||
}
|
||||
}
|
||||
if ret != 0 || addr.sc_family != AF_SYSTEM {
|
||||
continue
|
||||
}
|
||||
if ctlInfo.ctl_id == 0 {
|
||||
ret = ioctl(fd, CTLIOCGINFO, &ctlInfo)
|
||||
if ret != 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if addr.sc_id == ctlInfo.ctl_id {
|
||||
return fd
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private static var interfaceName: String? {
|
||||
guard let tunnelFileDescriptor = self.tunnelFileDescriptor else {
|
||||
return nil
|
||||
}
|
||||
var buffer = [UInt8](repeating: 0, count: Int(IFNAMSIZ))
|
||||
return buffer.withUnsafeMutableBufferPointer { mutableBufferPointer in
|
||||
guard let baseAddress = mutableBufferPointer.baseAddress else {
|
||||
return nil
|
||||
}
|
||||
var ifnameSize = socklen_t(IFNAMSIZ)
|
||||
let result = getsockopt(
|
||||
tunnelFileDescriptor,
|
||||
2 /* SYSPROTO_CONTROL */,
|
||||
2 /* UTUN_OPT_IFNAME */,
|
||||
baseAddress,
|
||||
&ifnameSize
|
||||
)
|
||||
if result == 0 {
|
||||
return String(cString: baseAddress)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public static func run(withConfig filePath: String) -> Int32 {
|
||||
guard let fileDescriptor = self.tunnelFileDescriptor else {
|
||||
fatalError("Get tunnel file descriptor failed.")
|
||||
}
|
||||
return hev_socks5_tunnel_main(filePath.cString(using: .utf8), fileDescriptor)
|
||||
}
|
||||
|
||||
public static func quit() {
|
||||
hev_socks5_tunnel_quit()
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,6 @@ public func ovpnLog(_ type: OSLogType, title: String = "", message: String) {
|
||||
neLog(type, title: "OVPN: \(title)", message: message)
|
||||
}
|
||||
|
||||
public func xrayLog(_ type: OSLogType, title: String = "", message: String) {
|
||||
neLog(type, title: "XRAY: \(title)", message: message)
|
||||
}
|
||||
|
||||
public func neLog(_ type: OSLogType, title: String = "", message: String) {
|
||||
Log.log(type, title: "NE: \(title)", message: message)
|
||||
}
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
import Foundation
|
||||
import NetworkExtension
|
||||
import WireGuardKitGo
|
||||
|
||||
enum XrayErrors: Error {
|
||||
case noXrayConfig
|
||||
case xrayConfigIsWrong
|
||||
case cantSaveXrayConfig
|
||||
case cantParseListenAndPort
|
||||
case cantSaveHevSocksConfig
|
||||
}
|
||||
|
||||
extension Constants {
|
||||
static let cachesDirectory: URL = {
|
||||
if let cachesDirectoryURL = FileManager.default.urls(for: .cachesDirectory,
|
||||
in: .userDomainMask).first {
|
||||
return cachesDirectoryURL
|
||||
} else {
|
||||
fatalError("Unable to retrieve caches directory.")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
extension PacketTunnelProvider {
|
||||
func startXray(completionHandler: @escaping (Error?) -> Void) {
|
||||
|
||||
// Xray configuration
|
||||
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
||||
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
||||
let configData = providerConfiguration[Constants.xrayConfigKey] as? Data else {
|
||||
xrayLog(.error, message: "Can't get xray configuration")
|
||||
completionHandler(XrayErrors.noXrayConfig)
|
||||
return
|
||||
}
|
||||
|
||||
// Tunnel settings
|
||||
let ipv6Enabled = false
|
||||
let hideVPNIcon = false
|
||||
|
||||
let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "254.1.1.1")
|
||||
settings.mtu = 9000
|
||||
|
||||
settings.ipv4Settings = {
|
||||
let settings = NEIPv4Settings(addresses: ["198.18.0.1"], subnetMasks: ["255.255.0.0"])
|
||||
settings.includedRoutes = [NEIPv4Route.default()]
|
||||
return settings
|
||||
}()
|
||||
|
||||
settings.ipv6Settings = {
|
||||
guard ipv6Enabled else {
|
||||
return nil
|
||||
}
|
||||
let settings = NEIPv6Settings(addresses: ["fd6e:a81b:704f:1211::1"], networkPrefixLengths: [64])
|
||||
settings.includedRoutes = [NEIPv6Route.default()]
|
||||
if hideVPNIcon {
|
||||
settings.excludedRoutes = [NEIPv6Route(destinationAddress: "::", networkPrefixLength: 128)]
|
||||
}
|
||||
return settings
|
||||
}()
|
||||
|
||||
do {
|
||||
let xrayConfig = try JSONDecoder().decode(XrayConfig.self,
|
||||
from: configData)
|
||||
|
||||
var dnsArray = [String]()
|
||||
if let dns1 = xrayConfig.dns1 {
|
||||
dnsArray.append(dns1)
|
||||
}
|
||||
if let dns2 = xrayConfig.dns2 {
|
||||
dnsArray.append(dns2)
|
||||
}
|
||||
|
||||
settings.dnsSettings = !dnsArray.isEmpty
|
||||
? NEDNSSettings(servers: dnsArray)
|
||||
: NEDNSSettings(servers: ["1.1.1.1"])
|
||||
|
||||
let xrayConfigData = xrayConfig.config.data(using: .utf8)
|
||||
|
||||
guard let xrayConfigData else {
|
||||
xrayLog(.error, message: "Can't encode config to data")
|
||||
completionHandler(XrayErrors.xrayConfigIsWrong)
|
||||
return
|
||||
}
|
||||
|
||||
let jsonDict = try JSONSerialization.jsonObject(with: xrayConfigData,
|
||||
options: []) as? [String: Any]
|
||||
|
||||
guard var jsonDict else {
|
||||
xrayLog(.error, message: "Can't parse address and port for hevSocks")
|
||||
completionHandler(XrayErrors.cantParseListenAndPort)
|
||||
return
|
||||
}
|
||||
|
||||
let port = 10808
|
||||
let address = "::1"
|
||||
|
||||
if var inboundsArray = jsonDict["inbounds"] as? [[String: Any]], !inboundsArray.isEmpty {
|
||||
inboundsArray[0]["port"] = port
|
||||
inboundsArray[0]["listen"] = address
|
||||
jsonDict["inbounds"] = inboundsArray
|
||||
}
|
||||
|
||||
let updatedData = try JSONSerialization.data(withJSONObject: jsonDict, options: [])
|
||||
|
||||
setTunnelNetworkSettings(settings) { [weak self] error in
|
||||
if let error {
|
||||
completionHandler(error)
|
||||
return
|
||||
}
|
||||
|
||||
// Launch xray
|
||||
self?.setupAndStartXray(configData: updatedData) { xrayError in
|
||||
if let xrayError {
|
||||
completionHandler(xrayError)
|
||||
return
|
||||
}
|
||||
|
||||
// Launch hevSocks
|
||||
self?.setupAndRunTun2socks(configData: updatedData,
|
||||
address: address,
|
||||
port: port,
|
||||
completionHandler: completionHandler)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
completionHandler(error)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func stopXray(completionHandler: () -> Void) {
|
||||
Socks5Tunnel.quit()
|
||||
LibXrayStopXray()
|
||||
completionHandler()
|
||||
}
|
||||
|
||||
private func setupAndStartXray(configData: Data,
|
||||
completionHandler: @escaping (Error?) -> Void) {
|
||||
let path = Constants.cachesDirectory.appendingPathComponent("config.json", isDirectory: false).path
|
||||
guard FileManager.default.createFile(atPath: path, contents: configData) else {
|
||||
xrayLog(.error, message: "Can't save xray configuration")
|
||||
completionHandler(XrayErrors.cantSaveXrayConfig)
|
||||
return
|
||||
}
|
||||
|
||||
LibXrayRunXray(nil,
|
||||
path,
|
||||
Int64.max)
|
||||
|
||||
completionHandler(nil)
|
||||
xrayLog(.info, message: "Xray started")
|
||||
}
|
||||
|
||||
private func setupAndRunTun2socks(configData: Data,
|
||||
address: String,
|
||||
port: Int,
|
||||
completionHandler: @escaping (Error?) -> Void) {
|
||||
let config = """
|
||||
tunnel:
|
||||
mtu: 9000
|
||||
socks5:
|
||||
port: \(port)
|
||||
address: \(address)
|
||||
udp: 'udp'
|
||||
misc:
|
||||
task-stack-size: 20480
|
||||
connect-timeout: 5000
|
||||
read-write-timeout: 60000
|
||||
log-file: stderr
|
||||
log-level: error
|
||||
limit-nofile: 65535
|
||||
"""
|
||||
|
||||
let configurationFilePath = Constants.cachesDirectory.appendingPathComponent("config.yml", isDirectory: false).path
|
||||
guard FileManager.default.createFile(atPath: configurationFilePath, contents: config.data(using: .utf8)!) else {
|
||||
xrayLog(.info, message: "Cant save hevSocks configuration")
|
||||
completionHandler(XrayErrors.cantSaveHevSocksConfig)
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.global().async {
|
||||
xrayLog(.info, message: "Hev socks started")
|
||||
completionHandler(nil)
|
||||
Socks5Tunnel.run(withConfig: configurationFilePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,7 @@ import Darwin
|
||||
import OpenVPNAdapter
|
||||
|
||||
enum TunnelProtoType: String {
|
||||
case wireguard, openvpn, xray
|
||||
|
||||
case wireguard, openvpn
|
||||
}
|
||||
|
||||
struct Constants {
|
||||
@@ -14,7 +13,6 @@ struct Constants {
|
||||
static let processQueueName = "org.amnezia.process-packets"
|
||||
static let kActivationAttemptId = "activationAttemptId"
|
||||
static let ovpnConfigKey = "ovpn"
|
||||
static let xrayConfigKey = "xray"
|
||||
static let wireGuardConfigKey = "wireguard"
|
||||
static let loggerTag = "NET"
|
||||
|
||||
@@ -93,8 +91,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
protoType = .openvpn
|
||||
} else if (providerConfiguration?[Constants.wireGuardConfigKey] as? Data) != nil {
|
||||
protoType = .wireguard
|
||||
} else if (providerConfiguration?[Constants.xrayConfigKey] as? Data) != nil {
|
||||
protoType = .xray
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,9 +107,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
completionHandler: completionHandler)
|
||||
case .openvpn:
|
||||
startOpenVPN(completionHandler: completionHandler)
|
||||
case .xray:
|
||||
startXray(completionHandler: completionHandler)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,8 +124,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
case .openvpn:
|
||||
stopOpenVPN(with: reason,
|
||||
completionHandler: completionHandler)
|
||||
case .xray:
|
||||
stopXray(completionHandler: completionHandler)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,8 +138,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
handleWireguardStatusMessage(messageData, completionHandler: completionHandler)
|
||||
case .openvpn:
|
||||
handleOpenVPNStatusMessage(messageData, completionHandler: completionHandler)
|
||||
case .xray:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ struct WGConfig: Decodable {
|
||||
let clientIP: String
|
||||
let clientPrivateKey: String
|
||||
let serverPublicKey: String
|
||||
let presharedKey: String?
|
||||
let presharedKey: String
|
||||
var allowedIPs: [String]
|
||||
var persistentKeepAlive: String
|
||||
let splitTunnelType: Int
|
||||
@@ -65,7 +65,7 @@ struct WGConfig: Decodable {
|
||||
\(settings)
|
||||
[Peer]
|
||||
PublicKey = \(serverPublicKey)
|
||||
\(presharedKey == nil ? "" : "PresharedKey = \(presharedKey!)")
|
||||
PresharedKey = \(presharedKey)
|
||||
AllowedIPs = \(allowedIPs.joined(separator: ", "))
|
||||
Endpoint = \(hostName):\(port)
|
||||
PersistentKeepalive = \(persistentKeepAlive)
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
struct XrayConfig: Decodable {
|
||||
let dns1: String?
|
||||
let dns2: String?
|
||||
let config: String
|
||||
}
|
||||
@@ -72,12 +72,9 @@ private:
|
||||
bool setupCloak();
|
||||
bool setupWireGuard();
|
||||
bool setupAwg();
|
||||
bool setupXray();
|
||||
bool setupSSXray();
|
||||
|
||||
bool startOpenVPN(const QString &config);
|
||||
bool startWireGuard(const QString &jsonConfig);
|
||||
bool startXray(const QString &jsonConfig);
|
||||
|
||||
void startTunnel();
|
||||
|
||||
|
||||
@@ -216,12 +216,6 @@ bool IosController::connectVpn(amnezia::Proto proto, const QJsonObject& configur
|
||||
if (proto == amnezia::Proto::Awg) {
|
||||
return setupAwg();
|
||||
}
|
||||
if (proto == amnezia::Proto::Xray) {
|
||||
return setupXray();
|
||||
}
|
||||
if (proto == amnezia::Proto::SSXray) {
|
||||
return setupSSXray();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -507,42 +501,6 @@ bool IosController::setupWireGuard()
|
||||
return startWireGuard(wgConfigDocStr);
|
||||
}
|
||||
|
||||
bool IosController::setupXray()
|
||||
{
|
||||
QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::Xray)].toObject();
|
||||
QJsonDocument xrayConfigDoc(config);
|
||||
|
||||
QString xrayConfigStr(xrayConfigDoc.toJson(QJsonDocument::Compact));
|
||||
|
||||
QJsonObject finalConfig;
|
||||
finalConfig.insert(config_key::dns1, m_rawConfig[config_key::dns1].toString());
|
||||
finalConfig.insert(config_key::dns2, m_rawConfig[config_key::dns2].toString());
|
||||
finalConfig.insert(config_key::config, xrayConfigStr);
|
||||
|
||||
QJsonDocument finalConfigDoc(finalConfig);
|
||||
QString finalConfigStr(finalConfigDoc.toJson(QJsonDocument::Compact));
|
||||
|
||||
return startXray(finalConfigStr);
|
||||
}
|
||||
|
||||
bool IosController::setupSSXray()
|
||||
{
|
||||
QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::SSXray)].toObject();
|
||||
QJsonDocument ssXrayConfigDoc(config);
|
||||
|
||||
QString ssXrayConfigStr(ssXrayConfigDoc.toJson(QJsonDocument::Compact));
|
||||
|
||||
QJsonObject finalConfig;
|
||||
finalConfig.insert(config_key::dns1, m_rawConfig[config_key::dns1]);
|
||||
finalConfig.insert(config_key::dns2, m_rawConfig[config_key::dns2]);
|
||||
finalConfig.insert(config_key::config, ssXrayConfigStr);
|
||||
|
||||
QJsonDocument finalConfigDoc(finalConfig);
|
||||
QString finalConfigStr(finalConfigDoc.toJson(QJsonDocument::Compact));
|
||||
|
||||
return startXray(finalConfigStr);
|
||||
}
|
||||
|
||||
bool IosController::setupAwg()
|
||||
{
|
||||
QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::Awg)].toObject();
|
||||
@@ -632,20 +590,6 @@ bool IosController::startWireGuard(const QString &config)
|
||||
startTunnel();
|
||||
}
|
||||
|
||||
bool IosController::startXray(const QString &config)
|
||||
{
|
||||
qDebug() << "IosController::startXray";
|
||||
|
||||
NETunnelProviderProtocol *tunnelProtocol = [[NETunnelProviderProtocol alloc] init];
|
||||
tunnelProtocol.providerBundleIdentifier = [NSString stringWithUTF8String:VPN_NE_BUNDLEID];
|
||||
tunnelProtocol.providerConfiguration = @{@"xray": [[NSString stringWithUTF8String:config.toStdString().c_str()] dataUsingEncoding:NSUTF8StringEncoding]};
|
||||
tunnelProtocol.serverAddress = m_serverAddress;
|
||||
|
||||
m_currentTunnel.protocolConfiguration = tunnelProtocol;
|
||||
|
||||
startTunnel();
|
||||
}
|
||||
|
||||
void IosController::startTunnel()
|
||||
{
|
||||
NSString *protocolName = @"Unknown";
|
||||
|
||||
@@ -139,7 +139,7 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
|
||||
if (config.m_killSwitchEnabled) {
|
||||
FirewallParams params { };
|
||||
params.dnsServers.append(config.m_dnsServer);
|
||||
if (config.m_allowedIPAddressRanges.contains(IPAddress("0.0.0.0/0"))) {
|
||||
if (config.m_allowedIPAddressRanges.at(0).toString() == "0.0.0.0/0"){
|
||||
params.blockAll = true;
|
||||
if (config.m_excludedAddresses.size()) {
|
||||
params.allowNets = true;
|
||||
|
||||
@@ -137,8 +137,7 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
|
||||
if (config.m_killSwitchEnabled) {
|
||||
FirewallParams params { };
|
||||
params.dnsServers.append(config.m_dnsServer);
|
||||
|
||||
if (config.m_allowedIPAddressRanges.contains(IPAddress("0.0.0.0/0"))) {
|
||||
if (config.m_allowedIPAddressRanges.at(0).toString() == "0.0.0.0/0"){
|
||||
params.blockAll = true;
|
||||
if (config.m_excludedAddresses.size()) {
|
||||
params.allowNets = true;
|
||||
|
||||
@@ -65,20 +65,19 @@ QString ProtocolProps::transportProtoToString(TransportProto proto, Proto p)
|
||||
QMap<amnezia::Proto, QString> ProtocolProps::protocolHumanNames()
|
||||
{
|
||||
return { { Proto::OpenVpn, "OpenVPN" },
|
||||
{ Proto::ShadowSocks, "Shadowsocks" },
|
||||
{ Proto::ShadowSocks, "ShadowSocks" },
|
||||
{ Proto::Cloak, "Cloak" },
|
||||
{ Proto::WireGuard, "WireGuard" },
|
||||
{ Proto::Awg, "AmneziaWG" },
|
||||
{ Proto::Ikev2, "IKEv2" },
|
||||
{ Proto::L2tp, "L2TP" },
|
||||
{ Proto::Xray, "XRay" },
|
||||
{ Proto::SSXray, "Shadowsocks"},
|
||||
{ Proto::SSXray, "ShadowSocks"},
|
||||
|
||||
|
||||
{ Proto::TorWebSite, "Website in Tor network" },
|
||||
{ Proto::Dns, "DNS Service" },
|
||||
{ Proto::Sftp, QObject::tr("SFTP service") },
|
||||
{ Proto::Socks5Proxy, QObject::tr("SOCKS5 proxy server") } };
|
||||
{ Proto::Sftp, QObject::tr("Sftp service") } };
|
||||
}
|
||||
|
||||
QMap<amnezia::Proto, QString> ProtocolProps::protocolDescriptions()
|
||||
@@ -103,7 +102,6 @@ amnezia::ServiceType ProtocolProps::protocolService(Proto p)
|
||||
case Proto::TorWebSite: return ServiceType::Other;
|
||||
case Proto::Dns: return ServiceType::Other;
|
||||
case Proto::Sftp: return ServiceType::Other;
|
||||
case Proto::Socks5Proxy: return ServiceType::Other;
|
||||
default: return ServiceType::Other;
|
||||
}
|
||||
}
|
||||
@@ -115,7 +113,6 @@ int ProtocolProps::getPortForInstall(Proto p)
|
||||
case WireGuard:
|
||||
case ShadowSocks:
|
||||
case OpenVpn:
|
||||
case Socks5Proxy:
|
||||
return QRandomGenerator::global()->bounded(30000, 50000);
|
||||
default:
|
||||
return defaultPort(p);
|
||||
@@ -138,7 +135,6 @@ int ProtocolProps::defaultPort(Proto p)
|
||||
case Proto::TorWebSite: return -1;
|
||||
case Proto::Dns: return 53;
|
||||
case Proto::Sftp: return 222;
|
||||
case Proto::Socks5Proxy: return 38080;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
@@ -158,7 +154,6 @@ bool ProtocolProps::defaultPortChangeable(Proto p)
|
||||
case Proto::TorWebSite: return false;
|
||||
case Proto::Dns: return false;
|
||||
case Proto::Sftp: return true;
|
||||
case Proto::Socks5Proxy: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
@@ -180,7 +175,6 @@ TransportProto ProtocolProps::defaultTransportProto(Proto p)
|
||||
case Proto::TorWebSite: return TransportProto::Tcp;
|
||||
case Proto::Dns: return TransportProto::Udp;
|
||||
case Proto::Sftp: return TransportProto::Tcp;
|
||||
case Proto::Socks5Proxy: return TransportProto::Tcp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,7 +195,6 @@ bool ProtocolProps::defaultTransportProtoChangeable(Proto p)
|
||||
case Proto::TorWebSite: return false;
|
||||
case Proto::Dns: return false;
|
||||
case Proto::Sftp: return false;
|
||||
case Proto::Socks5Proxy: return false;
|
||||
default: return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -84,7 +84,6 @@ namespace amnezia
|
||||
constexpr char awg[] = "awg";
|
||||
constexpr char xray[] = "xray";
|
||||
constexpr char ssxray[] = "ssxray";
|
||||
constexpr char socks5proxy[] = "socks5proxy";
|
||||
|
||||
constexpr char configVersion[] = "config_version";
|
||||
|
||||
@@ -217,14 +216,6 @@ namespace amnezia
|
||||
constexpr char defaultUnderloadPacketMagicHeader[] = "1766607858";
|
||||
}
|
||||
|
||||
namespace socks5Proxy
|
||||
{
|
||||
constexpr char defaultUserName[] = "proxy_user";
|
||||
constexpr char defaultPort[] = "38080";
|
||||
|
||||
constexpr char proxyConfigPath[] = "/usr/local/3proxy/conf/3proxy.cfg";
|
||||
}
|
||||
|
||||
} // namespace protocols
|
||||
|
||||
namespace ProtocolEnumNS
|
||||
@@ -253,8 +244,7 @@ namespace amnezia
|
||||
// non-vpn
|
||||
TorWebSite,
|
||||
Dns,
|
||||
Sftp,
|
||||
Socks5Proxy
|
||||
Sftp
|
||||
};
|
||||
Q_ENUM_NS(Proto)
|
||||
|
||||
|
||||
@@ -11,6 +11,16 @@
|
||||
<file>images/tray/default.png</file>
|
||||
<file>images/tray/error.png</file>
|
||||
<file>images/arrow_left.png</file>
|
||||
<file>fonts/Lato-Black.ttf</file>
|
||||
<file>fonts/Lato-BlackItalic.ttf</file>
|
||||
<file>fonts/Lato-Bold.ttf</file>
|
||||
<file>fonts/Lato-BoldItalic.ttf</file>
|
||||
<file>fonts/Lato-Italic.ttf</file>
|
||||
<file>fonts/Lato-Light.ttf</file>
|
||||
<file>fonts/Lato-LightItalic.ttf</file>
|
||||
<file>fonts/Lato-Regular.ttf</file>
|
||||
<file>fonts/Lato-Thin.ttf</file>
|
||||
<file>fonts/Lato-ThinItalic.ttf</file>
|
||||
<file>images/AmneziaVPN.png</file>
|
||||
<file>images/share.png</file>
|
||||
<file>server_scripts/remove_container.sh</file>
|
||||
@@ -85,6 +95,7 @@
|
||||
<file>server_scripts/check_user_in_sudo.sh</file>
|
||||
<file>ui/qml/Controls2/BasicButtonType.qml</file>
|
||||
<file>ui/qml/Controls2/TextFieldWithHeaderType.qml</file>
|
||||
<file>fonts/pt-root-ui_vf.ttf</file>
|
||||
<file>ui/qml/Controls2/LabelWithButtonType.qml</file>
|
||||
<file>images/controls/arrow-right.svg</file>
|
||||
<file>images/controls/chevron-right.svg</file>
|
||||
@@ -187,7 +198,7 @@
|
||||
<file>ui/qml/Pages2/PageProtocolOpenVpnSettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolShadowSocksSettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolCloakSettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolXraySettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolXraySettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolRaw.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsLogging.qml</file>
|
||||
<file>ui/qml/Pages2/PageServiceSftpSettings.qml</file>
|
||||
@@ -228,13 +239,5 @@
|
||||
<file>images/controls/alert-circle.svg</file>
|
||||
<file>images/controls/file-check-2.svg</file>
|
||||
<file>ui/qml/Controls2/WarningType.qml</file>
|
||||
<file>fonts/pt-root-ui_vf.ttf</file>
|
||||
<file>ui/qml/Modules/Style/qmldir</file>
|
||||
<file>ui/qml/Modules/Style/AmneziaStyle.qml</file>
|
||||
<file>ui/qml/Pages2/PageServiceSocksProxySettings.qml</file>
|
||||
<file>server_scripts/socks5_proxy/run_container.sh</file>
|
||||
<file>server_scripts/socks5_proxy/Dockerfile</file>
|
||||
<file>server_scripts/socks5_proxy/configure_container.sh</file>
|
||||
<file>server_scripts/socks5_proxy/start.sh</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
FROM 3proxy/3proxy:latest
|
||||
|
||||
LABEL maintainer="AmneziaVPN"
|
||||
|
||||
RUN mkdir -p /opt/amnezia
|
||||
RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh
|
||||
RUN chmod a+x /opt/amnezia/start.sh
|
||||
|
||||
ENTRYPOINT [ "/bin/sh", "/opt/amnezia/start.sh" ]
|
||||
CMD [ "" ]
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo -e "#!/bin/3proxy" > /usr/local/3proxy/conf/3proxy.cfg
|
||||
echo -e "config /usr/local/3proxy/conf/3proxy.cfg" >> /usr/local/3proxy/conf/3proxy.cfg
|
||||
echo -e "timeouts 1 5 30 60 180 1800 15 60" >> /usr/local/3proxy/conf/3proxy.cfg
|
||||
|
||||
echo -e "$SOCKS5_USER" >> /usr/local/3proxy/conf/3proxy.cfg
|
||||
|
||||
echo -e "log /usr/local/3proxy/logs/3proxy.log" >> /usr/local/3proxy/conf/3proxy.cfg
|
||||
echo -e "logformat \"-\\\"\"+_G{\"\"time_unix\"\":%t, \"\"proxy\"\":{\"\"type:\"\":\"\"%N\"\", \"\"port\"\":%p}, \"\"error\"\":{\"\"code\"\":\"\"%E\"\"}, \"\"auth\"\":{\"\"user\"\":\"\"%U\"\"}, \"\"client\"\":{\"\"ip\"\":\"\"%C\"\", \"\"port\"\":%c}, \"\"server\"\":{\"\"ip\"\":\"\"%R\"\", \"\"port\"\":%r}, \"\"bytes\"\":{\"\"sent\"\":%O, \"\"received\"\":%I}, \"\"request\"\":{\"\"hostname\"\":\"\"%n\"\"}, \"\"message\"\":\"\"%T\"\"}\"" >> /usr/local/3proxy/conf/3proxy.cfg
|
||||
echo -e "auth $SOCKS5_AUTH_TYPE" >> /usr/local/3proxy/conf/3proxy.cfg
|
||||
echo -e "socks -p$SOCKS5_PROXY_PORT" >> /usr/local/3proxy/conf/3proxy.cfg
|
||||
@@ -1,5 +0,0 @@
|
||||
sudo docker run -d \
|
||||
--restart always \
|
||||
-p $SOCKS5_PROXY_PORT:$SOCKS5_PROXY_PORT/tcp \
|
||||
--name $CONTAINER_NAME \
|
||||
$CONTAINER_NAME
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This scripts copied from Amnezia client to Docker container to /opt/amnezia and launched every time container starts
|
||||
|
||||
echo "Container startup"
|
||||
|
||||
/bin/3proxy /usr/local/3proxy/conf/3proxy.cfg
|
||||
@@ -226,8 +226,6 @@ void Settings::setSaveLogs(bool enabled)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Logger::setServiceLogsEnabled(enabled);
|
||||
|
||||
if (enabled) {
|
||||
setLogEnableDate(QDateTime::currentDateTime());
|
||||
}
|
||||
|
||||