mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-22 02:01:08 +07:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f844351afd | |||
| 7b1df79da5 | |||
| ddfb60a047 | |||
| c96b268a09 | |||
| 82f5903574 | |||
| a2ec75219e | |||
| 6f58f19958 | |||
| 0612f70c06 | |||
| cf8492240e | |||
| 2bceb9f7ba | |||
| 760f935965 | |||
| eeeb2805c5 | |||
| 9a592d67ad | |||
| ea6618b2f6 | |||
| 7b092e73ad | |||
| b2e25c42c7 | |||
| c8dd38ac31 | |||
| 563ee4703f | |||
| beceed81de | |||
| 3bf96253db | |||
| da2d0ec203 | |||
| 008b858203 | |||
| 130fc8277d | |||
| 468d3357b8 | |||
| f1271da527 | |||
| 249a7c7ca3 | |||
| 0094d0ebc4 | |||
| 834b504dff |
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.networking.networkextension</key>
|
||||
<array>
|
||||
<string>packet-tunnel-provider</string>
|
||||
</array>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.org.amnezia.AmneziaVPN</string>
|
||||
</array>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>$(AppIdentifierPrefix)org.amnezia.AmneziaVPN.network-extension</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
+5
-10
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
|
||||
project(${PROJECT} VERSION 4.5.3.0
|
||||
project(${PROJECT} VERSION 4.6.0.1
|
||||
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 52)
|
||||
set(APP_ANDROID_VERSION_CODE 54)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
@@ -31,14 +31,9 @@ set(QT_BUILD_TOOLS_WHEN_CROSS_COMPILING ON)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
if(APPLE AND NOT IOS)
|
||||
set(CMAKE_OSX_ARCHITECTURES "x86_64")
|
||||
endif()
|
||||
|
||||
set(CMAKE_OSX_ARCHITECTURES "x86_64")
|
||||
|
||||
|
||||
add_subdirectory(client)
|
||||
|
||||
if(NOT IOS AND NOT ANDROID)
|
||||
add_subdirectory(service)
|
||||
|
||||
include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake)
|
||||
endif()
|
||||
|
||||
@@ -9,7 +9,7 @@ Amnezia is an open-source VPN client, with a key feature that enables you to dep
|
||||
## 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.
|
||||
@@ -27,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...
|
||||
|
||||
+1
-1
Submodule client/3rd-prebuilt updated: eb43e90f38...eab32a8e63
Vendored
+1
-1
Submodule client/3rd/OpenVPNAdapter updated: 7c821a8d5c...b7d88501b1
Vendored
+1
-1
Submodule client/3rd/amneziawg-apple updated: 0829e99ea9...76e7db556a
@@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||
set(PROJECT AmneziaVPN)
|
||||
project(${PROJECT})
|
||||
|
||||
set(IOS 1)
|
||||
|
||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||
set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER "Autogen")
|
||||
@@ -343,7 +344,7 @@ endif()
|
||||
|
||||
if(IOS)
|
||||
include(cmake/ios.cmake)
|
||||
include(cmake/ios-arch-fixup.cmake)
|
||||
#include(cmake/ios-arch-fixup.cmake)
|
||||
elseif(APPLE AND NOT IOS)
|
||||
include(cmake/osxtools.cmake)
|
||||
include(cmake/macos.cmake)
|
||||
@@ -384,4 +385,5 @@ if(NOT IOS AND NOT ANDROID)
|
||||
endif()
|
||||
|
||||
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
|
||||
set(IOS 0)
|
||||
qt_finalize_target(${PROJECT})
|
||||
|
||||
@@ -217,7 +217,7 @@ void AmneziaApplication::registerTypes()
|
||||
declareQmlProtocolEnum();
|
||||
declareQmlContainerEnum();
|
||||
|
||||
qmlRegisterType<QRCodeReader>("QRCodeReader", 1, 0, "QRCodeReader");
|
||||
// qmlRegisterType<QRCodeReader>("QRCodeReader", 1, 0, "QRCodeReader");
|
||||
|
||||
m_containerProps.reset(new ContainerProps());
|
||||
qmlRegisterSingletonInstance("ContainerProps", 1, 0, "ContainerProps", m_containerProps.get());
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef AMNEZIA_APPLICATION_H
|
||||
#define AMNEZIA_APPLICATION_H
|
||||
|
||||
#define Q_OS_IOS 1
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QQmlApplicationEngine>
|
||||
|
||||
@@ -136,8 +136,34 @@
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".AmneziaVpnService"
|
||||
android:process=":amneziaVpnService"
|
||||
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:permission="android.permission.BIND_VPN_SERVICE"
|
||||
android:foregroundServiceType="systemExempted"
|
||||
android:exported="false"
|
||||
|
||||
@@ -3,6 +3,7 @@ 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")
|
||||
}
|
||||
|
||||
@@ -68,6 +69,12 @@ android {
|
||||
}
|
||||
signingConfig = signingConfigs["release"]
|
||||
}
|
||||
|
||||
create("fdroid") {
|
||||
initWith(getByName("release"))
|
||||
signingConfig = null
|
||||
matchingFallbacks += "release"
|
||||
}
|
||||
}
|
||||
|
||||
splits {
|
||||
@@ -98,7 +105,6 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar", "*.aar"))))
|
||||
implementation(project(":qt"))
|
||||
implementation(project(":utils"))
|
||||
implementation(project(":protocolApi"))
|
||||
@@ -106,9 +112,11 @@ 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,6 +8,7 @@ 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]
|
||||
@@ -21,6 +22,7 @@ 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]
|
||||
@@ -35,3 +37,4 @@ 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,15 +1,13 @@
|
||||
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.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.cancel
|
||||
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
|
||||
@@ -37,7 +35,6 @@ import org.json.JSONObject
|
||||
|
||||
open class OpenVpn : Protocol() {
|
||||
|
||||
private lateinit var context: Context
|
||||
private var openVpnClient: OpenVpnClient? = null
|
||||
private lateinit var scope: CoroutineScope
|
||||
|
||||
@@ -53,10 +50,11 @@ open class OpenVpn : Protocol() {
|
||||
return Statistics.EMPTY_STATISTICS
|
||||
}
|
||||
|
||||
override fun initialize(context: Context, state: MutableStateFlow<ProtocolState>, onError: (String) -> Unit) {
|
||||
super.initialize(context, state, onError)
|
||||
loadSharedLibrary(context, "ovpn3")
|
||||
this.context = context
|
||||
override fun internalInit() {
|
||||
if (!isInitialized) loadSharedLibrary(context, "ovpn3")
|
||||
if (this::scope.isInitialized) {
|
||||
scope.cancel()
|
||||
}
|
||||
scope = CoroutineScope(Dispatchers.IO)
|
||||
}
|
||||
|
||||
|
||||
@@ -27,14 +27,21 @@ 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
|
||||
|
||||
open fun initialize(context: Context, state: MutableStateFlow<ProtocolState>, onError: (String) -> Unit) {
|
||||
fun initialize(context: Context, state: MutableStateFlow<ProtocolState>, onError: (String) -> Unit) {
|
||||
this.context = context
|
||||
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", "*.aar"))))
|
||||
implementation(fileTree(mapOf("dir" to "../libs", "include" to listOf("*.jar"))))
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ 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
|
||||
|
||||
@@ -34,6 +34,7 @@ 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
|
||||
@@ -43,6 +44,8 @@ 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"
|
||||
@@ -59,6 +62,7 @@ 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
|
||||
@@ -141,6 +145,7 @@ class AmneziaActivity : QtActivity() {
|
||||
override fun onBindingDied(name: ComponentName?) {
|
||||
Log.w(TAG, "Binding to the ${name?.flattenToString()} unexpectedly died")
|
||||
doUnbindService()
|
||||
QtAndroidController.onServiceDisconnected()
|
||||
doBindService()
|
||||
}
|
||||
}
|
||||
@@ -153,15 +158,20 @@ 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() {
|
||||
@@ -209,13 +219,18 @@ class AmneziaActivity : QtActivity() {
|
||||
Log.d(TAG, "Start Amnezia activity")
|
||||
mainScope.launch {
|
||||
qtInitialized.await()
|
||||
doBindService()
|
||||
vpnProto?.let { proto ->
|
||||
if (AmneziaVpnService.isRunning(applicationContext, proto.processName)) {
|
||||
doBindService()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
Log.d(TAG, "Stop Amnezia activity")
|
||||
doUnbindService()
|
||||
QtAndroidController.onServiceDisconnected()
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
@@ -269,10 +284,12 @@ class AmneziaActivity : QtActivity() {
|
||||
@MainThread
|
||||
private fun doBindService() {
|
||||
Log.d(TAG, "Bind service")
|
||||
Intent(this, AmneziaVpnService::class.java).also {
|
||||
bindService(it, serviceConnection, BIND_ABOVE_CLIENT and BIND_AUTO_CREATE)
|
||||
vpnProto?.let { proto ->
|
||||
Intent(this, proto.serviceClass).also {
|
||||
bindService(it, serviceConnection, BIND_ABOVE_CLIENT and BIND_AUTO_CREATE)
|
||||
}
|
||||
isInBoundState = true
|
||||
}
|
||||
isInBoundState = true
|
||||
}
|
||||
|
||||
@MainThread
|
||||
@@ -280,7 +297,6 @@ class AmneziaActivity : QtActivity() {
|
||||
if (isInBoundState) {
|
||||
Log.d(TAG, "Unbind service")
|
||||
isWaitingStatus = true
|
||||
QtAndroidController.onServiceDisconnected()
|
||||
isServiceConnected = false
|
||||
vpnServiceMessenger.send(Action.UNREGISTER_CLIENT, activityMessenger)
|
||||
vpnServiceMessenger.reset()
|
||||
@@ -365,13 +381,31 @@ class AmneziaActivity : QtActivity() {
|
||||
|
||||
@MainThread
|
||||
private fun startVpn(vpnConfig: String) {
|
||||
if (isServiceConnected) {
|
||||
connectToVpn(vpnConfig)
|
||||
} else {
|
||||
getVpnProto(vpnConfig)?.let { proto ->
|
||||
Log.d(TAG, "Proto from config: $proto, current proto: $vpnProto")
|
||||
if (isServiceConnected) {
|
||||
if (proto == vpnProto) {
|
||||
connectToVpn(vpnConfig)
|
||||
return
|
||||
}
|
||||
doUnbindService()
|
||||
}
|
||||
vpnProto = proto
|
||||
isWaitingStatus = false
|
||||
startVpnService(vpnConfig)
|
||||
startVpnService(vpnConfig, proto)
|
||||
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) {
|
||||
@@ -383,15 +417,15 @@ class AmneziaActivity : QtActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun startVpnService(vpnConfig: String) {
|
||||
Log.d(TAG, "Start VPN service")
|
||||
Intent(this, AmneziaVpnService::class.java).apply {
|
||||
private fun startVpnService(vpnConfig: String, proto: VpnProto) {
|
||||
Log.d(TAG, "Start VPN service: $proto")
|
||||
Intent(this, proto.serviceClass).apply {
|
||||
putExtra(MSG_VPN_CONFIG, vpnConfig)
|
||||
}.also {
|
||||
try {
|
||||
ContextCompat.startForegroundService(this, it)
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Failed to start AmneziaVpnService: $e")
|
||||
Log.e(TAG, "Failed to start ${proto.serviceClass.simpleName}: $e")
|
||||
QtAndroidController.onServiceError()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,9 @@ class AmneziaTileService : TileService() {
|
||||
|
||||
@Volatile
|
||||
private var isServiceConnected = false
|
||||
|
||||
@Volatile
|
||||
private var vpnProto: VpnProto? = null
|
||||
private var isInBoundState = false
|
||||
@Volatile
|
||||
private var isVpnConfigExists = false
|
||||
@@ -94,16 +97,21 @@ class AmneziaTileService : TileService() {
|
||||
|
||||
override fun onStartListening() {
|
||||
super.onStartListening()
|
||||
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)
|
||||
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()
|
||||
}
|
||||
vpnStateListeningJob = launchVpnStateListening()
|
||||
}
|
||||
|
||||
override fun onStopListening() {
|
||||
@@ -124,7 +132,7 @@ class AmneziaTileService : TileService() {
|
||||
}
|
||||
|
||||
private fun onClickInternal() {
|
||||
if (isVpnConfigExists) {
|
||||
if (isVpnConfigExists && vpnProto != null) {
|
||||
Log.d(TAG, "Change VPN state")
|
||||
if (qsTile.state == Tile.STATE_INACTIVE) {
|
||||
Log.d(TAG, "Start VPN")
|
||||
@@ -147,10 +155,12 @@ class AmneziaTileService : TileService() {
|
||||
|
||||
private fun doBindService() {
|
||||
Log.d(TAG, "Bind service")
|
||||
Intent(this, AmneziaVpnService::class.java).also {
|
||||
bindService(it, serviceConnection, BIND_ABOVE_CLIENT)
|
||||
vpnProto?.let { proto ->
|
||||
Intent(this, proto.serviceClass).also {
|
||||
bindService(it, serviceConnection, BIND_ABOVE_CLIENT)
|
||||
}
|
||||
isInBoundState = true
|
||||
}
|
||||
isInBoundState = true
|
||||
}
|
||||
|
||||
private fun doUnbindService() {
|
||||
@@ -180,6 +190,7 @@ 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)
|
||||
}
|
||||
@@ -189,14 +200,16 @@ class AmneziaTileService : TileService() {
|
||||
}
|
||||
|
||||
private fun startVpnService() {
|
||||
try {
|
||||
ContextCompat.startForegroundService(
|
||||
applicationContext,
|
||||
Intent(this, AmneziaVpnService::class.java)
|
||||
)
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Failed to start AmneziaVpnService: $e")
|
||||
}
|
||||
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")
|
||||
}
|
||||
|
||||
private fun connectToVpn() = vpnServiceMessenger.send(Action.CONNECT)
|
||||
@@ -220,11 +233,8 @@ 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) }
|
||||
@@ -232,9 +242,10 @@ 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
|
||||
label = (vpnState.serverName ?: DEFAULT_TILE_LABEL) + (vpnProto?.let { " ${it.label}" } ?: "")
|
||||
when (val protocolState = vpnState.protocolState) {
|
||||
CONNECTED -> {
|
||||
state = Tile.STATE_ACTIVE
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.amnezia.vpn
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.ActivityManager
|
||||
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE
|
||||
import android.app.NotificationManager
|
||||
@@ -39,7 +40,6 @@ 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,11 +48,7 @@ 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
|
||||
@@ -63,6 +59,7 @@ 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"
|
||||
@@ -73,19 +70,18 @@ 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
|
||||
|
||||
class AmneziaVpnService : VpnService() {
|
||||
@SuppressLint("Registered")
|
||||
open class AmneziaVpnService : VpnService() {
|
||||
|
||||
private lateinit var mainScope: CoroutineScope
|
||||
private lateinit var connectionScope: CoroutineScope
|
||||
private var isServiceBound = false
|
||||
private var protocol: Protocol? = null
|
||||
private val protocolCache = mutableMapOf<String, Protocol>()
|
||||
private var vpnProto: VpnProto? = null
|
||||
private var protocolState = MutableStateFlow(UNKNOWN)
|
||||
private var serverName: String? = null
|
||||
private var serverIndex: Int = -1
|
||||
@@ -105,7 +101,7 @@ class AmneziaVpnService : VpnService() {
|
||||
// private var statisticsSendingJob: Job? = null
|
||||
private lateinit var networkState: NetworkState
|
||||
private lateinit var trafficStats: TrafficStats
|
||||
private var disconnectReceiver: BroadcastReceiver? = null
|
||||
private var controlReceiver: BroadcastReceiver? = null
|
||||
private var notificationStateReceiver: BroadcastReceiver? = null
|
||||
private var screenOnReceiver: BroadcastReceiver? = null
|
||||
private var screenOffReceiver: BroadcastReceiver? = null
|
||||
@@ -116,7 +112,6 @@ class AmneziaVpnService : VpnService() {
|
||||
|
||||
private val connectionExceptionHandler = CoroutineExceptionHandler { _, e ->
|
||||
protocolState.value = DISCONNECTED
|
||||
protocol = null
|
||||
when (e) {
|
||||
is IllegalArgumentException,
|
||||
is VpnStartException,
|
||||
@@ -227,7 +222,8 @@ class AmneziaVpnService : VpnService() {
|
||||
connect(intent?.getStringExtra(MSG_VPN_CONFIG))
|
||||
}
|
||||
ServiceCompat.startForeground(
|
||||
this, NOTIFICATION_ID, serviceNotification.buildNotification(serverName, protocolState.value),
|
||||
this, NOTIFICATION_ID,
|
||||
serviceNotification.buildNotification(serverName, vpnProto?.label, protocolState.value),
|
||||
foregroundServiceTypeCompat
|
||||
)
|
||||
return START_REDELIVER_INTENT
|
||||
@@ -292,9 +288,17 @@ class AmneziaVpnService : VpnService() {
|
||||
|
||||
private fun registerBroadcastReceivers() {
|
||||
Log.d(TAG, "Register broadcast receivers")
|
||||
disconnectReceiver = registerBroadcastReceiver(ACTION_DISCONNECT, ContextCompat.RECEIVER_NOT_EXPORTED) {
|
||||
Log.d(TAG, "Broadcast request received: $ACTION_DISCONNECT")
|
||||
disconnect()
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notificationStateReceiver = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
@@ -340,10 +344,10 @@ class AmneziaVpnService : VpnService() {
|
||||
|
||||
private fun unregisterBroadcastReceivers() {
|
||||
Log.d(TAG, "Unregister broadcast receivers")
|
||||
unregisterBroadcastReceiver(disconnectReceiver)
|
||||
unregisterBroadcastReceiver(controlReceiver)
|
||||
unregisterBroadcastReceiver(notificationStateReceiver)
|
||||
unregisterScreenStateBroadcastReceivers()
|
||||
disconnectReceiver = null
|
||||
controlReceiver = null
|
||||
notificationStateReceiver = null
|
||||
}
|
||||
|
||||
@@ -356,7 +360,7 @@ class AmneziaVpnService : VpnService() {
|
||||
protocolState.drop(1).collect { protocolState ->
|
||||
Log.d(TAG, "Protocol state changed: $protocolState")
|
||||
|
||||
serviceNotification.updateNotification(serverName, protocolState)
|
||||
serviceNotification.updateNotification(serverName, vpnProto?.label, protocolState)
|
||||
|
||||
clientMessengers.send {
|
||||
ServiceEvent.STATUS_CHANGED.packToMessage {
|
||||
@@ -364,7 +368,7 @@ class AmneziaVpnService : VpnService() {
|
||||
}
|
||||
}
|
||||
|
||||
VpnStateStore.store { VpnState(protocolState, serverName, serverIndex) }
|
||||
VpnStateStore.store { VpnState(protocolState, serverName, serverIndex, vpnProto) }
|
||||
|
||||
when (protocolState) {
|
||||
CONNECTED -> {
|
||||
@@ -421,7 +425,7 @@ class AmneziaVpnService : VpnService() {
|
||||
@MainThread
|
||||
private fun enableNotification() {
|
||||
registerScreenStateBroadcastReceivers()
|
||||
serviceNotification.updateNotification(serverName, protocolState.value)
|
||||
serviceNotification.updateNotification(serverName, vpnProto?.label, protocolState.value)
|
||||
launchTrafficStatsUpdate()
|
||||
}
|
||||
|
||||
@@ -484,8 +488,6 @@ class AmneziaVpnService : VpnService() {
|
||||
|
||||
Log.d(TAG, "Start VPN connection")
|
||||
|
||||
protocolState.value = CONNECTING
|
||||
|
||||
val config = parseConfigToJson(vpnConfig)
|
||||
saveServerData(config)
|
||||
if (config == null) {
|
||||
@@ -494,6 +496,16 @@ 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
|
||||
@@ -503,8 +515,10 @@ class AmneziaVpnService : VpnService() {
|
||||
disconnectionJob?.join()
|
||||
disconnectionJob = null
|
||||
|
||||
protocol = getProtocol(config.getString("protocol"))
|
||||
protocol?.startVpn(config, Builder(), ::protect)
|
||||
vpnProto?.protocol?.let { protocol ->
|
||||
protocol.initialize(applicationContext, protocolState, ::onError)
|
||||
protocol.startVpn(config, Builder(), ::protect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,8 +534,8 @@ class AmneziaVpnService : VpnService() {
|
||||
connectionJob?.join()
|
||||
connectionJob = null
|
||||
|
||||
protocol?.stopVpn()
|
||||
protocol = null
|
||||
vpnProto?.protocol?.stopVpn()
|
||||
|
||||
try {
|
||||
withTimeout(DISCONNECT_TIMEOUT) {
|
||||
// waiting for disconnect state
|
||||
@@ -543,22 +557,10 @@ class AmneziaVpnService : VpnService() {
|
||||
protocolState.value = RECONNECTING
|
||||
|
||||
connectionJob = connectionScope.launch {
|
||||
protocol?.reconnectVpn(Builder())
|
||||
vpnProto?.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
|
||||
*/
|
||||
@@ -603,6 +605,7 @@ 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)
|
||||
}
|
||||
@@ -612,9 +615,9 @@ class AmneziaVpnService : VpnService() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun isRunning(context: Context): Boolean =
|
||||
fun isRunning(context: Context, processName: String): Boolean =
|
||||
context.getSystemService<ActivityManager>()!!.runningAppProcesses.any {
|
||||
it.processName == PROCESS_NAME && it.importance <= IMPORTANCE_FOREGROUND_SERVICE
|
||||
it.processName == processName && it.importance <= IMPORTANCE_FOREGROUND_SERVICE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package org.amnezia.vpn
|
||||
|
||||
class OpenVpnService : AmneziaVpnService()
|
||||
@@ -59,14 +59,14 @@ class ServiceNotification(private val context: Context) {
|
||||
formatSpeedString(rxString, txString)
|
||||
}
|
||||
|
||||
fun buildNotification(serverName: String?, state: ProtocolState): Notification {
|
||||
fun buildNotification(serverName: String?, protocol: 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")
|
||||
.setContentTitle((serverName ?: "AmneziaVPN") + (protocol?.let { " $it" } ?: ""))
|
||||
.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?, state: ProtocolState) {
|
||||
fun updateNotification(serverName: String?, protocol: String?, state: ProtocolState) {
|
||||
if (context.isNotificationPermissionGranted()) {
|
||||
Log.d(TAG, "Update notification: $serverName, $state")
|
||||
notificationManager.notify(NOTIFICATION_ID, buildNotification(serverName, state))
|
||||
notificationManager.notify(NOTIFICATION_ID, buildNotification(serverName, protocol, state))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ class ServiceNotification(private val context: Context) {
|
||||
context,
|
||||
DISCONNECT_REQUEST_CODE,
|
||||
Intent(ACTION_DISCONNECT).apply {
|
||||
setPackage("org.amnezia.vpn")
|
||||
setPackage(context.packageName)
|
||||
},
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
@@ -135,10 +135,12 @@ class ServiceNotification(private val context: Context) {
|
||||
DISCONNECTED -> {
|
||||
Action(
|
||||
0, context.getString(R.string.connect),
|
||||
createServicePendingIntent(
|
||||
PendingIntent.getBroadcast(
|
||||
context,
|
||||
CONNECT_REQUEST_CODE,
|
||||
Intent(context, AmneziaVpnService::class.java),
|
||||
Intent(ACTION_CONNECT).apply {
|
||||
setPackage(context.packageName)
|
||||
},
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
)
|
||||
@@ -148,13 +150,6 @@ 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)) {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
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()
|
||||
};
|
||||
|
||||
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,6 +7,7 @@ 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
|
||||
@@ -18,9 +19,11 @@ 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)
|
||||
@@ -28,6 +31,12 @@ 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) {
|
||||
@@ -66,10 +75,18 @@ class VpnRequestActivity : ComponentActivity() {
|
||||
|
||||
private fun onPermissionGranted() {
|
||||
Toast.makeText(this, resources.getString(R.string.vpnGranted), Toast.LENGTH_LONG).show()
|
||||
Intent(applicationContext, AmneziaVpnService::class.java).apply {
|
||||
putExtra(AFTER_PERMISSION_CHECK, true)
|
||||
}.also {
|
||||
ContextCompat.startForegroundService(this, it)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
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.withContext
|
||||
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 org.amnezia.vpn.protocol.ProtocolState
|
||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||
import org.amnezia.vpn.util.Log
|
||||
@@ -21,13 +24,14 @@ 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
|
||||
) : Serializable {
|
||||
val serverIndex: Int = -1,
|
||||
val vpnProto: VpnProto? = null
|
||||
) {
|
||||
companion object {
|
||||
private const val serialVersionUID: Long = -1760654961004181606
|
||||
val defaultState: VpnState = VpnState(DISCONNECTED)
|
||||
}
|
||||
}
|
||||
@@ -37,7 +41,11 @@ object VpnStateStore {
|
||||
|
||||
private val dataStore = MultiProcessDataStoreFactory.create(
|
||||
serializer = VpnStateSerializer(),
|
||||
produceFile = { app.dataStoreFile(STORE_FILE_NAME) }
|
||||
produceFile = { app.dataStoreFile(STORE_FILE_NAME) },
|
||||
corruptionHandler = ReplaceFileCorruptionHandler { e ->
|
||||
Log.e(TAG, "VpnState DataStore corrupted: $e")
|
||||
VpnState.defaultState
|
||||
}
|
||||
)
|
||||
|
||||
fun init(app: Application) {
|
||||
@@ -45,36 +53,36 @@ object VpnStateStore {
|
||||
this.app = app
|
||||
}
|
||||
|
||||
fun dataFlow(): Flow<VpnState> = dataStore.data
|
||||
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
|
||||
|
||||
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 {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val bios = ByteArrayInputStream(input.readBytes())
|
||||
ObjectInputStream(bios).use {
|
||||
it.readObject() as VpnState
|
||||
}
|
||||
}
|
||||
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 writeTo(t: VpnState, output: OutputStream) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val baos = ByteArrayOutputStream()
|
||||
ObjectOutputStream(baos).use {
|
||||
it.writeObject(t)
|
||||
}
|
||||
output.write(baos.toByteArray())
|
||||
}
|
||||
}
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
override suspend fun writeTo(t: VpnState, output: OutputStream) =
|
||||
output.write(ProtoBuf.encodeToByteArray(t))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package org.amnezia.vpn
|
||||
|
||||
class XrayService : AmneziaVpnService()
|
||||
+2
-6
@@ -1,12 +1,9 @@
|
||||
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
|
||||
@@ -78,9 +75,8 @@ open class Wireguard : Protocol() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun initialize(context: Context, state: MutableStateFlow<ProtocolState>, onError: (String) -> Unit) {
|
||||
super.initialize(context, state, onError)
|
||||
loadSharedLibrary(context, "wg-go")
|
||||
override fun internalInit() {
|
||||
if (!isInitialized) loadSharedLibrary(context, "wg-go")
|
||||
}
|
||||
|
||||
override fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
@file:Suppress("UnstableApiUsage")
|
||||
|
||||
configurations {
|
||||
maybeCreate("default")
|
||||
}
|
||||
artifacts.add("default", file("libxray.aar"))
|
||||
@@ -0,0 +1,237 @@
|
||||
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.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.getJSONObject("xray_config_data")
|
||||
val xrayConfig = parseConfig(config, xrayJsonConfig)
|
||||
|
||||
// for debug
|
||||
// xrayJsonConfig.getJSONObject("log").put("loglevel", "debug")
|
||||
xrayJsonConfig.getJSONObject("log").put("loglevel", "warning")
|
||||
// disable access log
|
||||
xrayJsonConfig.getJSONObject("log").put("access", "none")
|
||||
|
||||
// replace socks address
|
||||
// (xrayJsonConfig.getJSONArray("inbounds")[0] as JSONObject).put("listen", "::1")
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun String?.isNotNullOrBlank(block: (String) -> Unit) {
|
||||
if (!this.isNullOrBlank()) {
|
||||
block(this)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -38,12 +38,12 @@ elseif(APPLE AND NOT IOS)
|
||||
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libssl.a")
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libcrypto.a")
|
||||
elseif(IOS)
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/ios/arm64")
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/ios/arm64/libssh.a")
|
||||
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/ios/arm64/libz.a")
|
||||
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/ios/iphone/include")
|
||||
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/ios/iphone/lib/libssl.a")
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/ios/iphone/lib/libcrypto.a")
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/x86_64")
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libssh.a")
|
||||
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libz.a")
|
||||
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/macos/include")
|
||||
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libssl.a")
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libcrypto.a")
|
||||
elseif(ANDROID)
|
||||
set(abi ${CMAKE_ANDROID_ARCH_ABI})
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/android/${abi}")
|
||||
|
||||
@@ -52,3 +52,6 @@ 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)
|
||||
|
||||
@@ -37,7 +37,7 @@ while(IOS_TARGETS)
|
||||
## I just want to say it's amazing this doesn't explode with syntax errors.
|
||||
message("Patching architectures for ${TARGET_NAME}")
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES
|
||||
XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] "arm64"
|
||||
XCODE_ATTRIBUTE_ARCHS[sdk=macosx*] "x86_64"
|
||||
XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] "x86_64"
|
||||
)
|
||||
endwhile()
|
||||
+63
-16
@@ -1,17 +1,21 @@
|
||||
message("Client iOS build")
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 13.0)
|
||||
|
||||
set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
|
||||
set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE INTERNAL "" FORCE)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
|
||||
|
||||
set(APPLE_PROJECT_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
|
||||
|
||||
enable_language(OBJC)
|
||||
enable_language(OBJCXX)
|
||||
# enable_language(OBJCXX)
|
||||
enable_language(Swift)
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS ShaderTools)
|
||||
set(LIBS ${LIBS} Qt6::ShaderTools)
|
||||
|
||||
find_library(FW_AUTHENTICATIONSERVICES AuthenticationServices)
|
||||
find_library(FW_UIKIT UIKit)
|
||||
#find_library(FW_UIKIT UIKit)
|
||||
find_library(FW_AVFOUNDATION AVFoundation)
|
||||
find_library(FW_FOUNDATION Foundation)
|
||||
find_library(FW_STOREKIT StoreKit)
|
||||
@@ -20,7 +24,7 @@ find_library(FW_NETWORKEXTENSION NetworkExtension)
|
||||
|
||||
set(LIBS ${LIBS}
|
||||
${FW_AUTHENTICATIONSERVICES}
|
||||
${FW_UIKIT}
|
||||
# ${FW_UIKIT}
|
||||
${FW_AVFOUNDATION}
|
||||
${FW_FOUNDATION}
|
||||
${FW_STOREKIT}
|
||||
@@ -48,6 +52,19 @@ set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
||||
)
|
||||
|
||||
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
|
||||
set(MACOSX_BUNDLE_ICON_FILE app.icns)
|
||||
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||
set(SOURCES ${SOURCES} ${ICON_FILE})
|
||||
|
||||
# set(HEADERS ${HEADERS}
|
||||
# ${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.h
|
||||
# )
|
||||
|
||||
# set(SOURCES ${SOURCES}
|
||||
# ${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.mm
|
||||
# )
|
||||
|
||||
|
||||
target_include_directories(${PROJECT} PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
||||
|
||||
@@ -55,10 +72,9 @@ target_include_directories(${PROJECT} PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Info.plist.in
|
||||
MACOSX_BUNDLE_ICON_FILE "AppIcon"
|
||||
#MACOSX_BUNDLE_ICON_FILE "AppIcon"
|
||||
MACOSX_BUNDLE_INFO_STRING "AmneziaVPN"
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPN"
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}"
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||
MACOSX_BUNDLE_LONG_VERSION_STRING "${APPLE_PROJECT_VERSION}-${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
|
||||
@@ -70,17 +86,25 @@ set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_BUNDLE_INFO_STRING "AmneziaVPN"
|
||||
XCODE_GENERATE_SCHEME TRUE
|
||||
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
|
||||
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon"
|
||||
#XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon"
|
||||
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
|
||||
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY ON
|
||||
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY "NO"
|
||||
XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY "YES"
|
||||
|
||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
|
||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../Frameworks"
|
||||
XCODE_EMBED_APP_EXTENSIONS networkextension
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "match AppStore org.amnezia.AmneziaVPN"
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "match Development org.amnezia.AmneziaVPN"
|
||||
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
|
||||
#XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
|
||||
|
||||
#XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution: Privacy Technologies OU (X7UJ388FXK)"
|
||||
#XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
|
||||
|
||||
|
||||
#XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "Mac AppStore AmneziaVPN"
|
||||
#XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "Mac AppStore AmneziaVPN"
|
||||
|
||||
)
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
|
||||
@@ -111,13 +135,13 @@ target_sources(${PROJECT} PRIVATE
|
||||
)
|
||||
|
||||
target_sources(${PROJECT} PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
|
||||
#${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||
)
|
||||
|
||||
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
|
||||
#${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||
)
|
||||
@@ -129,6 +153,29 @@ set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework"
|
||||
)
|
||||
|
||||
|
||||
|
||||
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos)
|
||||
target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework")
|
||||
|
||||
get_target_property(QtCore_location Qt6::Core LOCATION)
|
||||
message("QtCore_location")
|
||||
message(${QtCore_location})
|
||||
|
||||
get_filename_component(QT_BIN_DIR_DETECTED "${QtCore_location}/../../../../../bin" ABSOLUTE)
|
||||
|
||||
|
||||
# add_custom_command(TARGET ${PROJECT} POST_BUILD
|
||||
# COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
|
||||
# )
|
||||
|
||||
# if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
# SET(SIGN_CMD codesign --deep --force --sign 'Apple Distribution: Privacy Technologies OU \(X7UJ388FXK\)' --timestamp --options runtime $<TARGET_BUNDLE_DIR:AmneziaVPN>)
|
||||
# message("Manual signing bundle...")
|
||||
# message(${SIGN_CMD})
|
||||
|
||||
|
||||
# add_custom_command(TARGET ${PROJECT} POST_BUILD
|
||||
# COMMAND ${SIGN_CMD}
|
||||
# )
|
||||
# endif()
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "openvpn_configurator.h"
|
||||
|
||||
#define Q_OS_IOS 1
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "ssh_configurator.h"
|
||||
|
||||
#define Q_OS_IOS 1
|
||||
|
||||
#include <QDebug>
|
||||
#include <QObject>
|
||||
#include <QProcess>
|
||||
@@ -101,8 +103,8 @@ QProcessEnvironment SshConfigurator::prepareEnv()
|
||||
pathEnvVar.clear();
|
||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;");
|
||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn;");
|
||||
#elif defined(Q_OS_MACX)
|
||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS");
|
||||
//#elif defined(Q_OS_MACX)
|
||||
// pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS");
|
||||
#endif
|
||||
|
||||
env.insert("PATH", pathEnvVar);
|
||||
|
||||
@@ -99,8 +99,8 @@ QMap<DockerContainer, QString> ContainerProps::containerHumanNames()
|
||||
{ DockerContainer::SSXray, "ShadowSocks"},
|
||||
|
||||
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
||||
{ DockerContainer::Dns, QObject::tr("Amnezia DNS") },
|
||||
{ DockerContainer::Sftp, QObject::tr("Sftp file sharing service") },
|
||||
{ DockerContainer::Dns, QObject::tr("AmneziaDNS") },
|
||||
{ DockerContainer::Sftp, QObject::tr("SFTP file sharing service") },
|
||||
{ DockerContainer::Socks5Proxy, QObject::tr("SOCKS5 proxy server") } };
|
||||
}
|
||||
|
||||
@@ -110,7 +110,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,7 +127,7 @@ 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 - Modern stable protocol, a bit faster than others, restores connection after "
|
||||
QObject::tr("IKEv2/IPsec - 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.") },
|
||||
@@ -164,7 +164,6 @@ 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.") },
|
||||
@@ -286,6 +285,7 @@ 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::ShadowSocks: return true;
|
||||
@@ -305,6 +305,7 @@ 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;
|
||||
default: return false;
|
||||
}
|
||||
|
||||
@@ -388,3 +389,18 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,8 @@ 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()
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#define Q_OS_IOS 1
|
||||
|
||||
#include "apiController.h"
|
||||
|
||||
#include <QEventLoop>
|
||||
|
||||
@@ -106,7 +106,7 @@ ErrorCode ServerController::runContainerScript(const ServerCredentials &credenti
|
||||
if (e)
|
||||
return e;
|
||||
|
||||
QString runner = QString("sudo docker exec -i $CONTAINER_NAME sh %1 ").arg(fileName);
|
||||
QString runner = QString("sudo docker exec -i $CONTAINER_NAME %2 %1 ").arg(fileName, (container == DockerContainer::Socks5Proxy ? "sh" : "bash"));
|
||||
e = runScript(credentials, replaceVars(runner, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
||||
|
||||
QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -5,10 +5,12 @@
|
||||
#include <QObject>
|
||||
|
||||
#include "ipc.h"
|
||||
#include "rep_ipc_interface_replica.h"
|
||||
//#include "rep_ipc_interface_replica.h"
|
||||
|
||||
#include "privileged_process.h"
|
||||
|
||||
/*
|
||||
|
||||
class IpcClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -46,6 +48,6 @@ private:
|
||||
bool m_isSocketConnected {false};
|
||||
|
||||
static IpcClient *m_instance;
|
||||
};
|
||||
}; */
|
||||
|
||||
#endif // IPCCLIENT_H
|
||||
|
||||
@@ -3,21 +3,21 @@
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "rep_ipc_process_interface_replica.h"
|
||||
// #include "rep_ipc_process_interface_replica.h"
|
||||
// This class is dangerous - instance of this class casted from base class,
|
||||
// so it support only functions
|
||||
// Do not add any members into it
|
||||
//
|
||||
class PrivilegedProcess : public IpcProcessInterfaceReplica
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
PrivilegedProcess();
|
||||
~PrivilegedProcess() override;
|
||||
//class PrivilegedProcess : public IpcProcessInterfaceReplica
|
||||
//{
|
||||
// Q_OBJECT
|
||||
//public:
|
||||
// PrivilegedProcess();
|
||||
// ~PrivilegedProcess() override;
|
||||
|
||||
void waitForFinished(int msecs);
|
||||
// void waitForFinished(int msecs);
|
||||
|
||||
};
|
||||
//};
|
||||
|
||||
#endif // PRIVILEGED_PROCESS_H
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
|
||||
<string>org.amnezia.AmneziaVPN</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
@@ -24,23 +24,10 @@
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.utilities</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<false/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>AmneziaVPNLaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array/>
|
||||
<key>UIRequiresFullScreen</key>
|
||||
<true/>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array/>
|
||||
<key>UIUserInterfaceStyle</key>
|
||||
|
||||
@@ -2,19 +2,29 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.application-identifier</key>
|
||||
<string>X7UJ388FXK.org.amnezia.AmneziaVPN</string>
|
||||
<key>com.apple.developer.networking.networkextension</key>
|
||||
<array>
|
||||
<string>packet-tunnel-provider</string>
|
||||
</array>
|
||||
<key>com.apple.developer.team-identifier</key>
|
||||
<string>X7UJ388FXK</string>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.org.amnezia.AmneziaVPN</string>
|
||||
</array>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>$(AppIdentifierPrefix)group.org.amnezia.AmneziaVPN</string>
|
||||
<string>$(AppIdentifierPrefix)org.amnezia.AmneziaVPN</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -2,17 +2,27 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.application-identifier</key>
|
||||
<string>X7UJ388FXK.org.amnezia.AmneziaVPN.network-extension</string>
|
||||
<key>com.apple.developer.networking.networkextension</key>
|
||||
<array>
|
||||
<string>packet-tunnel-provider</string>
|
||||
</array>
|
||||
<key>com.apple.developer.team-identifier</key>
|
||||
<string>X7UJ388FXK</string>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.org.amnezia.AmneziaVPN</string>
|
||||
</array>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>$(AppIdentifierPrefix)group.org.amnezia.AmneziaVPN</string>
|
||||
<string>$(AppIdentifierPrefix)org.amnezia.AmneziaVPN.network-extension</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -3,19 +3,22 @@ enable_language(Swift)
|
||||
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
|
||||
|
||||
add_executable(networkextension)
|
||||
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_LIST_DIR}/Info.plist.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/Info.plist
|
||||
)
|
||||
|
||||
set_target_properties(networkextension PROPERTIES
|
||||
XCODE_PRODUCT_TYPE com.apple.product-type.app-extension
|
||||
BUNDLE_EXTENSION appex
|
||||
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
|
||||
MACOSX_BUNDLE_INFO_STRING "AmneziaVPNNetworkExtension"
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPNNetworkExtension"
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||
MACOSX_BUNDLE_LONG_VERSION_STRING "${APPLE_PROJECT_VERSION}-${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||
#MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_BINARY_DIR}/Info.plist
|
||||
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
|
||||
|
||||
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
|
||||
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_NAME "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${CMAKE_CURRENT_SOURCE_DIR}/AmneziaVPNNetworkExtension.entitlements
|
||||
XCODE_ATTRIBUTE_MARKETING_VERSION "${APP_MAJOR_VERSION}"
|
||||
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${BUILD_ID}"
|
||||
@@ -27,14 +30,41 @@ set_target_properties(networkextension PROPERTIES
|
||||
|
||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../Frameworks"
|
||||
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
|
||||
# XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
|
||||
# #XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
|
||||
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "match AppStore org.amnezia.AmneziaVPN.network-extension"
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "match Development org.amnezia.AmneziaVPN.network-extension"
|
||||
# #XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution: Privacy Technologies OU (X7UJ388FXK)"
|
||||
# #XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
|
||||
|
||||
|
||||
# #XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "Mac AppStore AmneziaVPN.network-extension"
|
||||
# #XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "Mac AppStore AmneziaVPN.network-extension"
|
||||
|
||||
XCODE_ATTRIBUTE_INFOPLIST_FILE "${CMAKE_CURRENT_BINARY_DIR}/Info.plist"
|
||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../../../Frameworks @loader_path/../../../../Frameworks"
|
||||
)
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set_target_properties(networkextension PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
|
||||
)
|
||||
endif()
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
set_target_properties(networkextension PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
|
||||
#XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
|
||||
|
||||
#XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution: Privacy Technologies OU (X7UJ388FXK)"
|
||||
#XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
|
||||
|
||||
|
||||
#XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "Mac AppStore AmneziaVPN.network-extension"
|
||||
#XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "Mac AppStore AmneziaVPN.network-extension"
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
set_target_properties(networkextension PROPERTIES
|
||||
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
|
||||
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
|
||||
@@ -50,10 +80,42 @@ 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})
|
||||
# set(OpenVPNAdapter_DIR "${CLIENT_ROOT_DIR}/3rd/")
|
||||
|
||||
# find_library(OPENVPN_ADAPTER_LIBRARY OpenVPNAdapter PATHS ${OpenVPNAdapter_DIR})
|
||||
# target_link_libraries(networkextension PRIVATE ${OPENVPN_ADAPTER_LIBRARY})
|
||||
|
||||
# add_custom_command(TARGET networkextension PRE_BUILD
|
||||
# COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_FILE_DIR:networkextension>/../Frameworks
|
||||
# )
|
||||
|
||||
|
||||
# add_custom_command(TARGET networkextension POST_BUILD
|
||||
# COMMAND ${CMAKE_COMMAND} -E echo "Copying ${OPENVPN_ADAPTER_LIBRARY} to $<TARGET_FILE_DIR:networkextension>/../Frameworks/"
|
||||
# COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
# ${OPENVPN_ADAPTER_LIBRARY}
|
||||
# $<TARGET_FILE_DIR:networkextension>/../Frameworks/
|
||||
# COMMAND ${CMAKE_COMMAND} -E echo "Copy complete"
|
||||
# )
|
||||
|
||||
|
||||
|
||||
|
||||
# set_property(TARGET networkextension PROPERTY XCODE_EMBED_FRAMEWORKS
|
||||
# "${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework"
|
||||
# )
|
||||
|
||||
# set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos)
|
||||
# target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework")
|
||||
|
||||
|
||||
|
||||
#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)
|
||||
@@ -80,12 +142,14 @@ 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/iosglue.mm
|
||||
)
|
||||
@@ -114,3 +178,5 @@ 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)
|
||||
|
||||
@@ -5,20 +5,20 @@
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
||||
<string>AmneziaVPNNetworkExtension</string>
|
||||
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
|
||||
<string>${BUILD_IOS_APP_IDENTIFIER}.network-extension</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
||||
<string>AmneziaVPNNetworkExtension</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
||||
<string>${APPLE_PROJECT_VERSION}</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
||||
<string>${CMAKE_PROJECT_VERSION_TWEAK}</string>
|
||||
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
@@ -27,7 +27,7 @@
|
||||
<string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
|
||||
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
|
||||
<string>AmneziaVPNNetworkExtension</string>
|
||||
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
@@ -38,9 +38,9 @@
|
||||
</dict>
|
||||
|
||||
<key>com.wireguard.ios.app_group_id</key>
|
||||
<string>group.${BUILD_IOS_APP_IDENTIFIER}</string>
|
||||
<string>group.org.amnezia.AmneziaVPN</string>
|
||||
|
||||
<key>com.wireguard.macos.app_group_id</key>
|
||||
<string>${BUILD_VPN_DEVELOPMENT_TEAM}.group.${BUILD_OSX_APP_IDENTIFIER}</string>
|
||||
<string>${BUILD_VPN_DEVELOPMENT_TEAM}.group.org.amnezia.AmneziaVPN</string>
|
||||
</dict>
|
||||
</plist>
|
||||
</plist>
|
||||
|
||||
@@ -11,9 +11,11 @@ EOF
|
||||
|
||||
|
||||
cd 3rd/OpenVPNAdapter
|
||||
if $XCODEBUILD -scheme OpenVPNAdapter -configuration Release -xcconfig Configuration/amnezia.xcconfig -sdk iphoneos -destination 'generic/platform=iOS' -project OpenVPNAdapter.xcodeproj ; then
|
||||
if $XCODEBUILD -scheme OpenVPNAdapter -configuration Release -xcconfig Configuration/amnezia.xcconfig -sdk macosx14.5 -destination 'generic/platform=MacOS' -project OpenVPNAdapter.xcodeproj ; then
|
||||
echo "OpenVPNAdapter built successfully"
|
||||
else
|
||||
echo "OpenVPNAdapter build failed"
|
||||
fi
|
||||
|
||||
rm -rf ./build/Release-iphoneos/OpenVPNAdapter.framework/Versions/A/_CodeSignature
|
||||
cd ../../
|
||||
|
||||
@@ -4,7 +4,12 @@
|
||||
<dict>
|
||||
<key>com.apple.application-identifier</key>
|
||||
<string>$(DEVELOPMENT_TEAM).$(NETEXT_ID_MACOS)</string>
|
||||
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.networking.networkextension</key>
|
||||
<array>
|
||||
<string>packet-tunnel-provider</string>
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
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,6 +13,10 @@ 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)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
import Foundation
|
||||
import NetworkExtension
|
||||
import WireGuardKitGo
|
||||
|
||||
enum XrayErrors: Error {
|
||||
case noXrayConfig
|
||||
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 xrayConfigData = providerConfiguration[Constants.xrayConfigKey] as? Data else {
|
||||
xrayLog(.error, message: "Can't get xray configuration")
|
||||
completionHandler(XrayErrors.noXrayConfig)
|
||||
return
|
||||
}
|
||||
|
||||
// Tunnel settings
|
||||
let ipv6Enabled = true
|
||||
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
|
||||
}()
|
||||
|
||||
let dns = ["8.8.4.4","1.1.1.1"]
|
||||
settings.dnsSettings = NEDNSSettings(servers: dns)
|
||||
|
||||
do {
|
||||
let port = 10808
|
||||
let address = "::1"
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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,7 +5,8 @@ import Darwin
|
||||
import OpenVPNAdapter
|
||||
|
||||
enum TunnelProtoType: String {
|
||||
case wireguard, openvpn
|
||||
case wireguard, openvpn, xray
|
||||
|
||||
}
|
||||
|
||||
struct Constants {
|
||||
@@ -13,6 +14,7 @@ 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"
|
||||
|
||||
@@ -91,6 +93,8 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +111,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
completionHandler: completionHandler)
|
||||
case .openvpn:
|
||||
startOpenVPN(completionHandler: completionHandler)
|
||||
case .xray:
|
||||
startXray(completionHandler: completionHandler)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,6 +131,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
case .openvpn:
|
||||
stopOpenVPN(with: reason,
|
||||
completionHandler: completionHandler)
|
||||
case .xray:
|
||||
stopXray(completionHandler: completionHandler)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +147,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
handleWireguardStatusMessage(messageData, completionHandler: completionHandler)
|
||||
case .openvpn:
|
||||
handleOpenVPNStatusMessage(messageData, completionHandler: completionHandler)
|
||||
case .xray:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,14 +8,6 @@ class QRCodeReader: public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QRCodeReader();
|
||||
|
||||
QRect cameraSize();
|
||||
|
||||
public slots:
|
||||
void startReading();
|
||||
void stopReading();
|
||||
void setCameraSize(QRect value);
|
||||
|
||||
signals:
|
||||
void codeReaded(QString code);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#include "QRCodeReaderBase.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
//#import <UIKit/UIKit.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
@interface QRCodeReaderImpl : UIViewController
|
||||
@end
|
||||
//@interface QRCodeReaderImpl : UIViewController
|
||||
//@end
|
||||
|
||||
@interface QRCodeReaderImpl () <AVCaptureMetadataOutputObjectsDelegate>
|
||||
/*@interface QRCodeReaderImpl () <AVCaptureMetadataOutputObjectsDelegate>
|
||||
@property (nonatomic) QRCodeReader* qrCodeReader;
|
||||
@property (nonatomic, strong) AVCaptureSession *captureSession;
|
||||
@property (nonatomic, strong) AVCaptureVideoPreviewLayer *videoPreviewPlayer;
|
||||
@@ -15,15 +15,15 @@
|
||||
|
||||
@implementation QRCodeReaderImpl
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
//- (void)viewDidLoad {
|
||||
// [super viewDidLoad];
|
||||
|
||||
_captureSession = nil;
|
||||
}
|
||||
// _captureSession = nil;
|
||||
//}
|
||||
|
||||
- (void)setQrCodeReader: (QRCodeReader*)value {
|
||||
_qrCodeReader = value;
|
||||
}
|
||||
//- (void)setQrCodeReader: (QRCodeReader*)value {
|
||||
// _qrCodeReader = value;
|
||||
//}
|
||||
|
||||
- (BOOL)startReading {
|
||||
NSError *error;
|
||||
@@ -49,7 +49,7 @@
|
||||
|
||||
_videoPreviewPlayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession: _captureSession];
|
||||
|
||||
CGFloat statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height;
|
||||
//CGFloat statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height;
|
||||
|
||||
QRect cameraRect = _qrCodeReader->cameraSize();
|
||||
CGRect cameraCGRect = CGRectMake(cameraRect.x(),
|
||||
@@ -60,7 +60,7 @@
|
||||
[_videoPreviewPlayer setVideoGravity: AVLayerVideoGravityResizeAspectFill];
|
||||
[_videoPreviewPlayer setFrame: cameraCGRect];
|
||||
|
||||
CALayer* layer = [UIApplication sharedApplication].keyWindow.layer;
|
||||
// CALayer* layer = [UIApplication sharedApplication].keyWindow.layer;
|
||||
[layer addSublayer: _videoPreviewPlayer];
|
||||
|
||||
[_captureSession startRunning];
|
||||
@@ -107,4 +107,4 @@ void QRCodeReader::startReading() {
|
||||
|
||||
void QRCodeReader::stopReading() {
|
||||
[m_qrCodeReader stopReading];
|
||||
}
|
||||
}*/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
//#import <UIKit/UIKit.h>
|
||||
|
||||
@interface QIOSApplicationDelegate
|
||||
@end
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
@implementation QIOSApplicationDelegate (AmneziaVPNDelegate)
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
/*- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
[application setMinimumBackgroundFetchInterval: UIApplicationBackgroundFetchIntervalMinimum];
|
||||
// Override point for customization after application launch.
|
||||
@@ -56,6 +56,6 @@
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
}*/
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,87 +1,10 @@
|
||||
import UIKit
|
||||
//import UIKit
|
||||
|
||||
public func toggleScreenshots(_ isEnabled: Bool) {
|
||||
let window = UIApplication.shared.keyWindows.first!
|
||||
|
||||
if isEnabled {
|
||||
ScreenProtection.shared.disable(for: window.rootViewController!.view)
|
||||
} else {
|
||||
ScreenProtection.shared.enable(for: window.rootViewController!.view)
|
||||
}
|
||||
}
|
||||
|
||||
extension UIApplication {
|
||||
var keyWindows: [UIWindow] {
|
||||
connectedScenes
|
||||
.compactMap {
|
||||
if #available(iOS 15.0, *) {
|
||||
($0 as? UIWindowScene)?.keyWindow
|
||||
} else {
|
||||
($0 as? UIWindowScene)?.windows.first { $0.isKeyWindow }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ScreenProtection {
|
||||
public static let shared = ScreenProtection()
|
||||
|
||||
var pairs = [ProtectionPair]()
|
||||
|
||||
private var blurView: UIVisualEffectView?
|
||||
private var recordingObservation: NSKeyValueObservation?
|
||||
|
||||
public func enable(for view: UIView) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
|
||||
view.subviews.forEach {
|
||||
self.pairs.append(ProtectionPair(from: $0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func disable(for view: UIView) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
|
||||
self.pairs.forEach {
|
||||
$0.removeProtection()
|
||||
}
|
||||
|
||||
self.pairs.removeAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ProtectionPair {
|
||||
let textField: UITextField
|
||||
let layer: CALayer
|
||||
|
||||
init(from view: UIView) {
|
||||
let secureTextField = UITextField()
|
||||
secureTextField.backgroundColor = .clear
|
||||
secureTextField.translatesAutoresizingMaskIntoConstraints = false
|
||||
secureTextField.isSecureTextEntry = true
|
||||
|
||||
view.insertSubview(secureTextField, at: 0)
|
||||
secureTextField.isUserInteractionEnabled = false
|
||||
|
||||
view.layer.superlayer?.addSublayer(secureTextField.layer)
|
||||
secureTextField.layer.sublayers?.last?.addSublayer(view.layer)
|
||||
|
||||
secureTextField.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
|
||||
secureTextField.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
|
||||
secureTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
|
||||
secureTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
|
||||
|
||||
self.init(textField: secureTextField, layer: view.layer)
|
||||
}
|
||||
|
||||
init(textField: UITextField, layer: CALayer) {
|
||||
self.textField = textField
|
||||
self.layer = layer
|
||||
}
|
||||
|
||||
func removeProtection() {
|
||||
textField.superview?.superview?.layer.addSublayer(layer)
|
||||
textField.layer.removeFromSuperlayer()
|
||||
textField.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = \(presharedKey)
|
||||
\(presharedKey == nil ? "" : "PresharedKey = \(presharedKey!)")
|
||||
AllowedIPs = \(allowedIPs.joined(separator: ", "))
|
||||
Endpoint = \(hostName):\(port)
|
||||
PersistentKeepalive = \(persistentKeepAlive)
|
||||
|
||||
@@ -72,9 +72,11 @@ private:
|
||||
bool setupCloak();
|
||||
bool setupWireGuard();
|
||||
bool setupAwg();
|
||||
bool setupXray();
|
||||
|
||||
bool startOpenVPN(const QString &config);
|
||||
bool startWireGuard(const QString &jsonConfig);
|
||||
bool startXray(const QString &jsonConfig);
|
||||
|
||||
void startTunnel();
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include "../protocols/vpnprotocol.h"
|
||||
#import "ios_controller_wrapper.h"
|
||||
|
||||
#include <Security/Security.h>
|
||||
|
||||
const char* Action::start = "start";
|
||||
const char* Action::restart = "restart";
|
||||
const char* Action::stop = "stop";
|
||||
@@ -27,14 +29,34 @@ const char* MessageKey::isOnDemand = "is-on-demand";
|
||||
const char* MessageKey::SplitTunnelType = "SplitTunnelType";
|
||||
const char* MessageKey::SplitTunnelSites = "SplitTunnelSites";
|
||||
|
||||
static UIViewController* getViewController() {
|
||||
NSArray *windows = [[UIApplication sharedApplication]windows];
|
||||
for (UIWindow *window in windows) {
|
||||
if (window.isKeyWindow) {
|
||||
return window.rootViewController;
|
||||
}
|
||||
//static UIViewController* getViewController() {
|
||||
// NSArray *windows = [[UIApplication sharedApplication]windows];
|
||||
// for (UIWindow *window in windows) {
|
||||
// if (window.isKeyWindow) {
|
||||
// return window.rootViewController;
|
||||
// }
|
||||
// }
|
||||
// return nil;
|
||||
//}
|
||||
|
||||
OSStatus requestAuthorization() {
|
||||
AuthorizationRef authRef;
|
||||
OSStatus status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authRef);
|
||||
if (status != errAuthorizationSuccess) {
|
||||
qDebug() << "Authorization failed with status:" << status;
|
||||
return status;
|
||||
}
|
||||
return nil;
|
||||
|
||||
AuthorizationItem authItem = {kAuthorizationRightExecute, 0, NULL, 0};
|
||||
AuthorizationRights authRights = {1, &authItem};
|
||||
AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights;
|
||||
|
||||
status = AuthorizationCopyRights(authRef, &authRights, NULL, flags, NULL);
|
||||
if (status != errAuthorizationSuccess) {
|
||||
qDebug() << "Authorization rights copy failed with status:" << status;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
Vpn::ConnectionState iosStatusToState(NEVPNStatus status) {
|
||||
@@ -84,6 +106,11 @@ IosController* IosController::Instance() {
|
||||
|
||||
bool IosController::initialize()
|
||||
{
|
||||
if (requestAuthorization() != errAuthorizationSuccess) {
|
||||
emit connectionStateChanged(Vpn::ConnectionState::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
__block bool ok = true;
|
||||
[NETunnelProviderManager loadAllFromPreferencesWithCompletionHandler:^(NSArray<NETunnelProviderManager *> * _Nullable managers, NSError * _Nullable error) {
|
||||
@try {
|
||||
@@ -216,6 +243,9 @@ bool IosController::connectVpn(amnezia::Proto proto, const QJsonObject& configur
|
||||
if (proto == amnezia::Proto::Awg) {
|
||||
return setupAwg();
|
||||
}
|
||||
if (proto == amnezia::Proto::Xray) {
|
||||
return setupXray();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -501,6 +531,15 @@ 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));
|
||||
|
||||
return startXray(xrayConfigStr);
|
||||
}
|
||||
|
||||
bool IosController::setupAwg()
|
||||
{
|
||||
QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::Awg)].toObject();
|
||||
@@ -590,6 +629,20 @@ 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";
|
||||
@@ -722,24 +775,24 @@ bool IosController::shareText(const QStringList& filesToSend) {
|
||||
[sharingItems addObject:logFileUrl];
|
||||
}
|
||||
|
||||
UIViewController *qtController = getViewController();
|
||||
if (!qtController) return;
|
||||
// UIViewController *qtController = getViewController();
|
||||
// if (!qtController) return;
|
||||
|
||||
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil];
|
||||
// UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil];
|
||||
|
||||
__block bool isAccepted = false;
|
||||
|
||||
[activityController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
|
||||
isAccepted = completed;
|
||||
emit finished();
|
||||
}];
|
||||
// [activityController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
|
||||
// isAccepted = completed;
|
||||
// emit finished();
|
||||
// }];
|
||||
|
||||
[qtController presentViewController:activityController animated:YES completion:nil];
|
||||
UIPopoverPresentationController *popController = activityController.popoverPresentationController;
|
||||
if (popController) {
|
||||
popController.sourceView = qtController.view;
|
||||
popController.sourceRect = CGRectMake(100, 100, 100, 100);
|
||||
}
|
||||
// [qtController presentViewController:activityController animated:YES completion:nil];
|
||||
// UIPopoverPresentationController *popController = activityController.popoverPresentationController;
|
||||
// if (popController) {
|
||||
// popController.sourceView = qtController.view;
|
||||
// popController.sourceRect = CGRectMake(100, 100, 100, 100);
|
||||
// }
|
||||
|
||||
QEventLoop wait;
|
||||
QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit);
|
||||
@@ -749,26 +802,26 @@ bool IosController::shareText(const QStringList& filesToSend) {
|
||||
}
|
||||
|
||||
QString IosController::openFile() {
|
||||
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen];
|
||||
// UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen];
|
||||
|
||||
DocumentPickerDelegate *documentPickerDelegate = [[DocumentPickerDelegate alloc] init];
|
||||
documentPicker.delegate = documentPickerDelegate;
|
||||
// DocumentPickerDelegate *documentPickerDelegate = [[DocumentPickerDelegate alloc] init];
|
||||
// documentPicker.delegate = documentPickerDelegate;
|
||||
|
||||
UIViewController *qtController = getViewController();
|
||||
if (!qtController) return;
|
||||
// UIViewController *qtController = getViewController();
|
||||
// if (!qtController) return;
|
||||
|
||||
[qtController presentViewController:documentPicker animated:YES completion:nil];
|
||||
// [qtController presentViewController:documentPicker animated:YES completion:nil];
|
||||
|
||||
__block QString filePath;
|
||||
|
||||
documentPickerDelegate.documentPickerClosedCallback = ^(NSString *path) {
|
||||
if (path) {
|
||||
filePath = QString::fromUtf8(path.UTF8String);
|
||||
} else {
|
||||
filePath = QString();
|
||||
}
|
||||
emit finished();
|
||||
};
|
||||
// documentPickerDelegate.documentPickerClosedCallback = ^(NSString *path) {
|
||||
// if (path) {
|
||||
// filePath = QString::fromUtf8(path.UTF8String);
|
||||
// } else {
|
||||
// filePath = QString();
|
||||
// }
|
||||
// emit finished();
|
||||
// };
|
||||
|
||||
QEventLoop wait;
|
||||
QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#import <NetworkExtension/NetworkExtension.h>
|
||||
#import <NetworkExtension/NETunnelProviderSession.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <UIKit/UIKit.h>
|
||||
//#include <UIKit/UIKit.h>
|
||||
#include <Security/Security.h>
|
||||
|
||||
class IosController;
|
||||
@@ -18,8 +18,8 @@ class IosController;
|
||||
|
||||
typedef void (^DocumentPickerClosedCallback)(NSString *path);
|
||||
|
||||
@interface DocumentPickerDelegate : NSObject <UIDocumentPickerDelegate>
|
||||
//@interface DocumentPickerDelegate : NSObject <UIDocumentPickerDelegate>
|
||||
|
||||
@property (nonatomic, copy) DocumentPickerClosedCallback documentPickerClosedCallback;
|
||||
//@property (nonatomic, copy) DocumentPickerClosedCallback documentPickerClosedCallback;
|
||||
|
||||
@end
|
||||
//@end
|
||||
|
||||
@@ -26,20 +26,20 @@
|
||||
|
||||
@end
|
||||
|
||||
@implementation DocumentPickerDelegate
|
||||
//@implementation DocumentPickerDelegate
|
||||
|
||||
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
|
||||
for (NSURL *url in urls) {
|
||||
if (self.documentPickerClosedCallback) {
|
||||
self.documentPickerClosedCallback([url path]);
|
||||
}
|
||||
}
|
||||
}
|
||||
//- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
|
||||
// for (NSURL *url in urls) {
|
||||
// if (self.documentPickerClosedCallback) {
|
||||
// self.documentPickerClosedCallback([url path]);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
|
||||
if (self.documentPickerClosedCallback) {
|
||||
self.documentPickerClosedCallback(nil);
|
||||
}
|
||||
}
|
||||
//- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
|
||||
// if (self.documentPickerClosedCallback) {
|
||||
// self.documentPickerClosedCallback(nil);
|
||||
// }
|
||||
//}
|
||||
|
||||
@end
|
||||
//@end
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
|
||||
#import <UserNotifications/UserNotifications.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
//#import <UIKit/UIKit.h>
|
||||
|
||||
/*
|
||||
@interface IOSNotificationDelegate
|
||||
: UIResponder <UIApplicationDelegate, UNUserNotificationCenterDelegate> {
|
||||
IOSNotificationHandler* m_iosNotificationHandler;
|
||||
@@ -86,4 +87,4 @@ void IOSNotificationHandler::notify(NotificationHandler::Message type, const QSt
|
||||
NSLog(@"Local Notification failed");
|
||||
}
|
||||
}];
|
||||
}
|
||||
}*/
|
||||
|
||||
@@ -26,7 +26,7 @@ private:
|
||||
#ifndef Q_OS_IOS
|
||||
QProcess m_ckProcess;
|
||||
#endif
|
||||
QTemporaryFile m_cloakCfgFile;
|
||||
// QTemporaryFile m_cloakCfgFile;
|
||||
QMetaObject::Connection m_errorHandlerConnection;
|
||||
};
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ private:
|
||||
ManagementServer m_managementServer;
|
||||
QString m_configFileName;
|
||||
QJsonObject m_configData;
|
||||
QTemporaryFile m_configFile;
|
||||
// QTemporaryFile m_configFile;
|
||||
|
||||
uint selectMgmtPort();
|
||||
|
||||
@@ -53,7 +53,7 @@ private:
|
||||
void updateRouteGateway(QString line);
|
||||
void updateVpnGateway(const QString &line);
|
||||
|
||||
QSharedPointer<PrivilegedProcess> m_openVpnProcess;
|
||||
// QSharedPointer<PrivilegedProcess> m_openVpnProcess;
|
||||
};
|
||||
|
||||
#endif // OPENVPNPROTOCOL_H
|
||||
|
||||
@@ -77,7 +77,7 @@ QMap<amnezia::Proto, QString> ProtocolProps::protocolHumanNames()
|
||||
|
||||
{ Proto::TorWebSite, "Website in Tor network" },
|
||||
{ Proto::Dns, "DNS Service" },
|
||||
{ Proto::Sftp, QObject::tr("Sftp service") },
|
||||
{ Proto::Sftp, QObject::tr("SFTP service") },
|
||||
{ Proto::Socks5Proxy, QObject::tr("SOCKS5 proxy server") } };
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "QProcess"
|
||||
#include "containers/containers_defs.h"
|
||||
|
||||
#define Q_OS_IOS 1
|
||||
|
||||
class ShadowSocksVpnProtocol : public OpenVpnProtocol
|
||||
{
|
||||
public:
|
||||
@@ -27,7 +29,7 @@ private:
|
||||
#ifndef Q_OS_IOS
|
||||
QProcess m_ssProcess;
|
||||
#endif
|
||||
QTemporaryFile m_shadowSocksCfgFile;
|
||||
// QTemporaryFile m_shadowSocksCfgFile;
|
||||
};
|
||||
|
||||
#endif // SHADOWSOCKSVPNPROTOCOL_H
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
#include <QDebug>
|
||||
#include <QTimer>
|
||||
|
||||
#define Q_OS_IOS 1
|
||||
|
||||
#include "core/errorstrings.h"
|
||||
#include "vpnprotocol.h"
|
||||
|
||||
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
|
||||
#include "openvpnovercloakprotocol.h"
|
||||
#include "openvpnprotocol.h"
|
||||
#include "shadowsocksvpnprotocol.h"
|
||||
#include "wireguardprotocol.h"
|
||||
#include "xrayprotocol.h"
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
#include "ikev2_vpn_protocol_windows.h"
|
||||
@@ -108,15 +103,6 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &
|
||||
switch (container) {
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
case DockerContainer::Ipsec: return new Ikev2Protocol(configuration);
|
||||
#endif
|
||||
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
|
||||
case DockerContainer::OpenVpn: return new OpenVpnProtocol(configuration);
|
||||
case DockerContainer::Cloak: return new OpenVpnOverCloakProtocol(configuration);
|
||||
case DockerContainer::ShadowSocks: return new ShadowSocksVpnProtocol(configuration);
|
||||
case DockerContainer::WireGuard: return new WireguardProtocol(configuration);
|
||||
case DockerContainer::Awg: return new WireguardProtocol(configuration);
|
||||
case DockerContainer::Xray: return new XrayProtocol(configuration);
|
||||
case DockerContainer::SSXray: return new XrayProtocol(configuration);
|
||||
#endif
|
||||
default: return nullptr;
|
||||
}
|
||||
|
||||
+1116
-590
File diff suppressed because it is too large
Load Diff
+1115
-592
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1116
-594
File diff suppressed because it is too large
Load Diff
+1358
-850
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1117
-595
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
||||
#include "connectionController.h"
|
||||
|
||||
#define Q_OS_IOS 1
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
#include <QGuiApplication>
|
||||
#else
|
||||
@@ -123,7 +125,7 @@ void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state)
|
||||
void ConnectionController::onCurrentContainerUpdated()
|
||||
{
|
||||
if (m_isConnected || m_isConnectionInProgress) {
|
||||
emit reconnectWithUpdatedContainer(tr("Settings updated successfully, Reconnnection..."));
|
||||
emit reconnectWithUpdatedContainer(tr("Settings updated successfully, reconnnection..."));
|
||||
openConnection();
|
||||
} else {
|
||||
emit reconnectWithUpdatedContainer(tr("Settings updated successfully"));
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#include "utils/converter.h"
|
||||
#include "core/errorstrings.h"
|
||||
|
||||
#define Q_OS_IOS 1
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
#include <QGuiApplication>
|
||||
#else
|
||||
@@ -37,8 +39,8 @@ PageController::PageController(const QSharedPointer<ServersModel> &serversModel,
|
||||
#endif
|
||||
|
||||
#if defined Q_OS_MACX
|
||||
connect(this, &PageController::raiseMainWindow, []() { setDockIconVisible(true); });
|
||||
connect(this, &PageController::hideMainWindow, []() { setDockIconVisible(false); });
|
||||
// connect(this, &PageController::raiseMainWindow, []() { setDockIconVisible(true); });
|
||||
// connect(this, &PageController::hideMainWindow, []() { setDockIconVisible(false); });
|
||||
#endif
|
||||
|
||||
connect(this, qOverload<ErrorCode>(&PageController::showErrorMessage), this, &PageController::onShowErrorMessage);
|
||||
@@ -134,7 +136,7 @@ void PageController::showOnStartup()
|
||||
#ifdef Q_OS_WIN
|
||||
emit hideMainWindow();
|
||||
#elif defined Q_OS_MACX
|
||||
setDockIconVisible(false);
|
||||
// setDockIconVisible(false);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const
|
||||
case IsCurrentlyProcessedRole: return container == static_cast<DockerContainer>(m_processedContainerIndex);
|
||||
case IsSupportedRole: return ContainerProps::isSupportedByCurrentPlatform(container);
|
||||
case IsShareableRole: return ContainerProps::isShareable(container);
|
||||
case InstallPageOrderRole: return ContainerProps::installPageOrder(container);
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
@@ -112,5 +113,7 @@ QHash<int, QByteArray> ContainersModel::roleNames() const
|
||||
roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed";
|
||||
roles[IsSupportedRole] = "isSupported";
|
||||
roles[IsShareableRole] = "isShareable";
|
||||
|
||||
roles[InstallPageOrderRole] = "installPageOrder";
|
||||
return roles;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,9 @@ public:
|
||||
IsCurrentlyProcessedRole,
|
||||
IsDefaultRole,
|
||||
IsSupportedRole,
|
||||
IsShareableRole
|
||||
IsShareableRole,
|
||||
|
||||
InstallPageOrderRole
|
||||
};
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <QDebug>
|
||||
#include "notificationhandler.h"
|
||||
|
||||
#define Q_OS_IOS 1
|
||||
|
||||
#if defined(Q_OS_IOS)
|
||||
# include "platforms/ios/iosnotificationhandler.h"
|
||||
#else
|
||||
@@ -14,7 +16,7 @@
|
||||
// static
|
||||
NotificationHandler* NotificationHandler::create(QObject* parent) {
|
||||
#if defined(Q_OS_IOS)
|
||||
return new IOSNotificationHandler(parent);
|
||||
return nullptr;//new IOSNotificationHandler(parent);
|
||||
#else
|
||||
|
||||
# if defined(Q_OS_LINUX)
|
||||
|
||||
@@ -92,7 +92,7 @@ PageType {
|
||||
HeaderType {
|
||||
Layout.fillWidth: true
|
||||
|
||||
headerText: qsTr("ShadowSocks settings")
|
||||
headerText: qsTr("Shadowsocks settings")
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
|
||||
@@ -137,7 +137,7 @@ PageType {
|
||||
id: githubButton
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Github")
|
||||
text: qsTr("GitHub")
|
||||
leftImageSource: "qrc:/images/controls/github.svg"
|
||||
|
||||
KeyNavigation.tab: websiteButton
|
||||
|
||||
@@ -43,7 +43,7 @@ PageType {
|
||||
|
||||
Component.onCompleted: {
|
||||
if (isServerFromApi) {
|
||||
PageController.showNotificationMessage(qsTr("Default server does not support custom dns"))
|
||||
PageController.showNotificationMessage(qsTr("Default server does not support custom DNS"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ disabled after 14 days, and all log files will be deleted.")
|
||||
Layout.fillWidth: true
|
||||
|
||||
headerText: qsTr("Logging")
|
||||
descriptionText: qsTr("Enabling this function will save application's logs automatically, " +
|
||||
descriptionText: qsTr("Enabling this function will save application's logs automatically. " +
|
||||
"By default, logging functionality is disabled. Enable log saving in case of application malfunction.")
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,8 @@ PageType {
|
||||
id: proxyContainersModel
|
||||
sourceModel: ContainersModel
|
||||
sorters: [
|
||||
RoleSorter { roleName: "isInstalled"; sortOrder: Qt.DescendingOrder }
|
||||
RoleSorter { roleName: "isInstalled"; sortOrder: Qt.DescendingOrder },
|
||||
RoleSorter { roleName: "installPageOrder"; sortOrder: Qt.AscendingOrder }
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ PageType {
|
||||
Layout.fillWidth: true
|
||||
visible: SettingsController.isCameraPresent()
|
||||
|
||||
text: qsTr("QR-code")
|
||||
text: qsTr("QR code")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
leftImageSource: "qrc:/images/controls/qr-code.svg"
|
||||
|
||||
|
||||
@@ -261,7 +261,8 @@ PageType {
|
||||
Keys.onTabPressed: lastItemTabClicked(focusItem)
|
||||
|
||||
clickedFunc: function() {
|
||||
if (!port.textField.acceptableInput) {
|
||||
if (!port.textField.acceptableInput &&
|
||||
ContainerProps.containerTypeToString(dockerContainer) !== "torwebsite") {
|
||||
port.errorText = qsTr("The port must be in the range of 1 to 65535")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@ PageType {
|
||||
value: true
|
||||
}
|
||||
]
|
||||
sorters: RoleSorter {
|
||||
roleName: "installPageOrder"
|
||||
sortOrder: Qt.AscendingOrder
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
|
||||
@@ -4,7 +4,6 @@ import QtQuick.Layouts
|
||||
import QtQuick.Dialogs
|
||||
|
||||
import PageEnum 1.0
|
||||
import QRCodeReader 1.0
|
||||
|
||||
import "./"
|
||||
import "../Controls2"
|
||||
@@ -63,23 +62,5 @@ PageType {
|
||||
color: "transparent"
|
||||
//radius: 16
|
||||
|
||||
QRCodeReader {
|
||||
id: qrCodeReader
|
||||
|
||||
onCodeReaded: function(code) {
|
||||
ImportController.parseQrCodeChunk(code)
|
||||
progressBar.value = ImportController.getQrCodeScanProgressBarValue()
|
||||
header.progressString = ImportController.getQrCodeScanProgressString()
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
qrCodeReader.setCameraSize(Qt.rect(qrCodeRectange.x,
|
||||
qrCodeRectange.y,
|
||||
qrCodeRectange.width,
|
||||
qrCodeRectange.height))
|
||||
qrCodeReader.startReading()
|
||||
}
|
||||
Component.onDestruction: qrCodeReader.stopReading()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ PageType {
|
||||
}
|
||||
case PageShare.ConfigType.ShadowSocks: {
|
||||
ExportController.generateShadowSocksConfig()
|
||||
shareConnectionDrawer.configCaption = qsTr("Save ShadowSocks config")
|
||||
shareConnectionDrawer.configCaption = qsTr("Save Shadowsocks config")
|
||||
shareConnectionDrawer.configExtension = ".json"
|
||||
shareConnectionDrawer.configFileName = "amnezia_for_shadowsocks"
|
||||
break
|
||||
@@ -138,7 +138,7 @@ PageType {
|
||||
}
|
||||
QtObject {
|
||||
id: shadowSocksConnectionFormat
|
||||
property string name: qsTr("ShadowSocks native format")
|
||||
property string name: qsTr("Shadowsocks native format")
|
||||
property var type: PageShare.ConfigType.ShadowSocks
|
||||
}
|
||||
QtObject {
|
||||
|
||||
@@ -213,8 +213,8 @@ PageType {
|
||||
startY: 0
|
||||
|
||||
PathLine { x: width; y: 0 }
|
||||
PathLine { x: width; y: height - 1 }
|
||||
PathLine { x: 0; y: height - 1 }
|
||||
PathLine { x: width; y: tabBar.height - 1 }
|
||||
PathLine { x: 0; y: tabBar.height - 1 }
|
||||
PathLine { x: 0; y: 0 }
|
||||
|
||||
strokeWidth: 1
|
||||
|
||||
@@ -7,9 +7,6 @@
|
||||
|
||||
#include "notificationhandler.h"
|
||||
|
||||
#include <QMenu>
|
||||
#include <QSystemTrayIcon>
|
||||
|
||||
class SystemTrayNotificationHandler : public NotificationHandler {
|
||||
Q_OBJECT
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include <configurators/wireguard_configurator.h>
|
||||
#include "core/controllers/serverController.h"
|
||||
|
||||
#define Q_OS_IOS 1
|
||||
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
#include "core/ipcclient.h"
|
||||
#include "ipc.h"
|
||||
|
||||
+13
-2
@@ -22,6 +22,7 @@ Options:
|
||||
-b, --build-platform <platform> The SDK platform used for building the Java code of the application
|
||||
By default, the latest available platform is used
|
||||
-m, --move Move the build result to the root of the build directory
|
||||
-f, --fdroid Build for F-Droid
|
||||
-h, --help Display this help
|
||||
|
||||
EOT
|
||||
@@ -29,7 +30,7 @@ EOT
|
||||
|
||||
BUILD_TYPE="release"
|
||||
|
||||
opts=$(getopt -l debug,aab,apk:,build-platform:,move,help -o "dua:b:mh" -- "$@")
|
||||
opts=$(getopt -l debug,aab,apk:,build-platform:,move,fdroid,help -o "dua:b:mfh" -- "$@")
|
||||
eval set -- "$opts"
|
||||
while true; do
|
||||
case "$1" in
|
||||
@@ -38,6 +39,7 @@ while true; do
|
||||
-a | --apk) ABIS=$2; shift 2;;
|
||||
-b | --build-platform) ANDROID_BUILD_PLATFORM=$2; shift 2;;
|
||||
-m | --move) MOVE_RESULT=1; shift;;
|
||||
-f | --fdroid) FDROID=1; shift;;
|
||||
-h | --help) usage; exit 0;;
|
||||
--) shift; break;;
|
||||
esac
|
||||
@@ -143,6 +145,10 @@ $QT_HOST_PATH/bin/androiddeployqt \
|
||||
# run gradle
|
||||
gradle_opts=()
|
||||
|
||||
if [ -v FDROID ]; then
|
||||
BUILD_TYPE="fdroid"
|
||||
fi
|
||||
|
||||
if [ -v AAB ]; then
|
||||
gradle_opts+=(bundle"${BUILD_TYPE^}")
|
||||
fi
|
||||
@@ -167,10 +173,15 @@ if [[ -v CI || -v MOVE_RESULT ]]; then
|
||||
ABIS="x86;x86_64;armeabi-v7a;arm64-v8a"
|
||||
fi
|
||||
|
||||
suffix=$BUILD_TYPE
|
||||
if [ -v FDROID ]; then
|
||||
suffix+="-unsigned"
|
||||
fi
|
||||
|
||||
IFS=';' read -r -a abi_array <<< "$ABIS"
|
||||
for ABI in "${abi_array[@]}"
|
||||
do
|
||||
mv -u $OUT_APP_DIR/android-build/build/outputs/apk/$BUILD_TYPE/AmneziaVPN-$ABI-$BUILD_TYPE.apk \
|
||||
mv -u $OUT_APP_DIR/android-build/build/outputs/apk/$BUILD_TYPE/AmneziaVPN-$ABI-$suffix.apk \
|
||||
$PROJECT_DIR/deploy/build/
|
||||
done
|
||||
fi
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
What's Changed:
|
||||
* Added XRay protocol support
|
||||
* Added app split tunneling
|
||||
* Added a notification to the status bar when VPN is running
|
||||
* Added a tile to the quick settings panel
|
||||
* Bug fixes and improvements
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user