mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-21 02:01:03 +07:00
Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aef20430f2 | |||
| b818de3378 | |||
| cf86c6e912 | |||
| 483263171b | |||
| 5fb91d8f5b | |||
| 100f8ad95d | |||
| 6429ff0603 | |||
| f5057dfac4 | |||
| 00d61def0b | |||
| f5a26c7116 | |||
| 54fba99bed | |||
| 7216a8b923 | |||
| 97e322ba22 | |||
| fc603f11ce | |||
| 10bca290c3 | |||
| 3dabaeb2c9 | |||
| cf74b879c6 | |||
| 0ae2a1f177 | |||
| e43041572e | |||
| 4b103a1622 | |||
| af29637163 | |||
| 7351fe9633 | |||
| 1a6b4a1188 | |||
| 8751dd3797 | |||
| 9a6df25280 | |||
| ada8912a1f | |||
| a5e5c3d941 | |||
| b6c7ef415d | |||
| b000eda126 | |||
| 4171afe275 | |||
| 25829451c8 | |||
| 45016b76e7 | |||
| 8ea80a616e | |||
| c5df7f9bb7 | |||
| 891f990e35 | |||
| d6d3bf6943 | |||
| f6e8346841 | |||
| 3a210c5bab | |||
| 3f99c52349 | |||
| 599910daea | |||
| bee42ea2fb | |||
| f24df9fb05 | |||
| ce2a122d51 | |||
| 24ea686e4d | |||
| a7030cdcb9 | |||
| 8c137ecc52 | |||
| a42beb86c0 | |||
| 7d09d41a7d |
+3
-3
@@ -1,6 +1,3 @@
|
||||
[submodule "3rd/QtSsh"]
|
||||
path = 3rd/QtSsh
|
||||
url = https://github.com/amnezia-vpn/QtSsh.git
|
||||
[submodule "client/3rd/wireguard-tools"]
|
||||
path = client/3rd/wireguard-tools
|
||||
url = https://github.com/WireGuard/wireguard-tools/
|
||||
@@ -37,6 +34,9 @@
|
||||
[submodule "client/3rd/SortFilterProxyModel"]
|
||||
path = client/3rd/SortFilterProxyModel
|
||||
url = https://github.com/mitchcurtis/SortFilterProxyModel.git
|
||||
[submodule "client/3rd/strongswan/sources"]
|
||||
path = client/3rd/strongswan/sources
|
||||
url = https://github.com/kolobchanin/strongswan.git
|
||||
[submodule "client/3rd/mbedtls"]
|
||||
path = client/3rd/mbedtls
|
||||
url = https://github.com/Mbed-TLS/mbedtls.git
|
||||
|
||||
Submodule
+1
Submodule 3rd/QtSsh added at a34ded6e69
@@ -87,7 +87,7 @@ Error 1
|
||||
Add a user defined variable to both AmneziaVPN and WireGuardNetworkExtension targets' build settings with
|
||||
key `PATH` and value `${PATH}/path/to/bin/folder/with/go/executable`, e.g. `${PATH}:/usr/local/go/bin`.
|
||||
|
||||
if above error still persists on you M1 Mac, then most proably you need to install arch based cmake
|
||||
if above error still persists on you M1 Mac, then most probably you need to install arch based cmake
|
||||
```
|
||||
arch -arm64 brew install cmake
|
||||
```
|
||||
@@ -112,15 +112,15 @@ In case you get errors regarding missing SDK or 'sdkmanager not running', you ca
|
||||
|
||||
Double check that the right cmake version is configured: Click on `QT Creator` -> `Preferences` and click on the side menu on `Kits`. Under the center content view's `Kits` tab you'll find an entry `CMake Tool`. If the default selected CMake version is lower than 3.25.0, install on your system CMake >= 3.25.0 and choose `System CMake at <path>` from the drop down list. If this entry is missing, you either have not installed CMake yet or QT Creator hasn't found the path to it. In that case click in the preferences window on the side menu item `CMake`, then on the tab `Tools`in the center content view and finally on the Button `Add` to set the path to your installed CMake.
|
||||
|
||||
Please make sure that you have selected Android Platform SDK 33 for your project: click in the main view's side menu on on `Projects`, on the left you'll see a section `Build & Run` showing different Android build targets. You can select any of them, Amnezia VPN's project setup is designed in a way that always all Android targets will be build. Click on the targets submenu item `Build` and scroll in the center content view to `Build Steps`. Click on `Details` at the end of the headline `Build Android APK` (The `Details` button might be hidden in case QT Creator Window is not running in full screen!). Here we are: choose `android-33` as `Android Build platfrom SDK`.
|
||||
Please make sure that you have selected Android Platform SDK 33 for your project: click in the main view's side menu on on `Projects`, on the left you'll see a section `Build & Run` showing different Android build targets. You can select any of them, Amnezia VPN's project setup is designed in a way that always all Android targets will be build. Click on the targets submenu item `Build` and scroll in the center content view to `Build Steps`. Click on `Details` at the end of the headline `Build Android APK` (The `Details` button might be hidden in case QT Creator Window is not running in full screen!). Here we are: choose `android-33` as `Android Build platform SDK`.
|
||||
|
||||
That's it you should be ready to compile the project from QT Creator!
|
||||
|
||||
### Development flow
|
||||
After you've hit the build button, QT-Creator copies the whole project to a folder in the repositories parent directory. The folder should look something like `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>`.
|
||||
If you want to develop Amnezia VPNs Android components written in Kotlin, such as components using system APIs, you need to import the generated project in Android Studio with `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>/client/android-build` as the projects root directory. While you should be able to compile the generated project from Android Studio, you cannot work directly in the repository's Android project. So whenever you are confident with your work in the generated proejct, you'll need to copy and paste the affected files to the corresponding path in the repositories Android project so that you can add and commit your changes!
|
||||
If you want to develop Amnezia VPNs Android components written in Kotlin, such as components using system APIs, you need to import the generated project in Android Studio with `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>/client/android-build` as the projects root directory. While you should be able to compile the generated project from Android Studio, you cannot work directly in the repository's Android project. So whenever you are confident with your work in the generated project, you'll need to copy and paste the affected files to the corresponding path in the repositories Android project so that you can add and commit your changes!
|
||||
|
||||
You may face compiling issues in QT Creator after you've worked in Android Studio on the generated project. Just do a `./gradlew clean` in the geneated project's root directory (`<path>/client/android-build/.`) and you should be good to continue.
|
||||
You may face compiling issues in QT Creator after you've worked in Android Studio on the generated project. Just do a `./gradlew clean` in the generated project's root directory (`<path>/client/android-build/.`) and you should be good to continue.
|
||||
|
||||
## License
|
||||
GPL v.3
|
||||
|
||||
Submodule
+1
Submodule client/3rd/strongswan/sources added at ec99ee03ce
@@ -0,0 +1,20 @@
|
||||
include(ExternalProject)
|
||||
|
||||
set(STRONGSWAN_SOURCES_ROOT ${CMAKE_CURRENT_LIST_DIR}/sources)
|
||||
|
||||
ExternalProject_Add(
|
||||
strongswan
|
||||
UPDATE_DISCONNECTED true
|
||||
CONFIGURE_HANDLED_BY_BUILD true
|
||||
PREFIX ${STRONGSWAN_SOURCES_ROOT}
|
||||
SOURCE_DIR ${STRONGSWAN_SOURCES_ROOT}
|
||||
BINARY_DIR ${STRONGSWAN_SOURCES_ROOT}
|
||||
INSTALL_DIR ${STRONGSWAN_SOURCES_ROOT}
|
||||
STAMP_DIR ${STRONGSWAN_SOURCES_ROOT}/stamp
|
||||
LOG_DIR ${STRONGSWAN_SOURCES_ROOT}/log
|
||||
TMP_DIR ${STRONGSWAN_SOURCES_ROOT}/tmp
|
||||
CONFIGURE_COMMAND ./autogen.sh
|
||||
COMMAND ./configure --disable-kernel-netlink
|
||||
BUILD_COMMAND make #dist
|
||||
INSTALL_COMMAND ""
|
||||
)
|
||||
@@ -273,6 +273,8 @@ if(ANDROID)
|
||||
${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidvpnactivity.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/android_vpnprotocol.cpp
|
||||
)
|
||||
|
||||
file(COPY ${CMAKE_CURRENT_LIST_DIR}/3rd/strongswan DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/3rd)
|
||||
endif()
|
||||
|
||||
if(IOS)
|
||||
@@ -341,7 +343,7 @@ if(IOS)
|
||||
enable_language(OBJCXX)
|
||||
enable_language(Swift)
|
||||
|
||||
#disbale in cicd
|
||||
#disable in cicd
|
||||
include(cmake/osxtools.cmake)
|
||||
# set(CMAKE_XCODE_GENERATE_TOP_LEVEL_PROJECT_ONLY TRUE)
|
||||
|
||||
@@ -353,7 +355,7 @@ if(IOS)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_LIST_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos)
|
||||
|
||||
|
||||
#need to change for debug and relase
|
||||
#need to change for debug and release
|
||||
set_target_properties(${PROJECT}
|
||||
PROPERTIES XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "org.amnezia.${PROJECT}"
|
||||
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1"
|
||||
@@ -507,7 +509,6 @@ if(ANDROID)
|
||||
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/VPNService.kt
|
||||
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/VPNServiceBinder.kt
|
||||
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/AmneziaApp.kt
|
||||
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/PackageManagerHelper.java
|
||||
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/VPNActivity.kt
|
||||
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/VPNApplication.java
|
||||
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/VPNClientBinder.kt
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
apply plugin: 'com.github.ben-manes.versions'
|
||||
|
||||
buildscript {
|
||||
ext{
|
||||
ext {
|
||||
kotlin_version = "1.7.22"
|
||||
// for libwg
|
||||
appcompatVersion = '1.1.0'
|
||||
@@ -11,6 +11,7 @@ buildscript {
|
||||
streamsupportVersion = '1.7.0'
|
||||
threetenabpVersion = '1.1.1'
|
||||
groupName = 'org.amnezia.vpn'
|
||||
relativePathToStrongswan = '../3rd/strongswan/sources/src/frontends/android/app/src/main/jni'
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -105,7 +106,7 @@ android {
|
||||
resources.srcDirs = ['resources']
|
||||
renderscript.srcDirs = ['src']
|
||||
assets.srcDirs = ['assets']
|
||||
jniLibs.srcDirs = ['libs']
|
||||
jniLibs.srcDirs = ['libs', relativePathToStrongswan]
|
||||
androidTest.assets.srcDirs += files("${qtAndroidDir}/schemas".toString())
|
||||
}
|
||||
}
|
||||
@@ -138,33 +139,65 @@ android {
|
||||
targetSdkVersion = 31
|
||||
versionCode 10 // Change to a higher number
|
||||
versionName "2.0.10" // Change to a higher number
|
||||
multiDexEnabled true
|
||||
|
||||
javaCompileOptions.annotationProcessorOptions.arguments = [
|
||||
"room.schemaLocation": "${qtAndroidDir}/schemas".toString()
|
||||
]
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
buildTypes {
|
||||
release {
|
||||
// That would enable treeshaking and remove java code that is just called from qt
|
||||
minifyEnabled false
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DANDROID_PACKAGE_NAME=${groupName}", "-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}"
|
||||
arguments "-DANDROID_PACKAGE_NAME=${groupName}",
|
||||
"-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug {
|
||||
//applicationIdSuffix ".debug"
|
||||
//versionNameSuffix "-debug"
|
||||
minifyEnabled false
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DANDROID_PACKAGE_NAME=${groupName}", "-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}"
|
||||
arguments "-DANDROID_PACKAGE_NAME=${groupName}",
|
||||
"-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}"
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task buildStrongSwanNative(type: Exec) {
|
||||
environment = System.getenv() + ['JNI_PACKAGE': 'org_amnezia_vpn', 'JNI_PACKAGE_STRING': 'org/amnezia/vpn']
|
||||
workingDir "${relativePathToStrongswan}"
|
||||
commandLine "${android.ndkDirectory}/ndk-build", '-j', Runtime.runtime.availableProcessors()
|
||||
|
||||
doLast {
|
||||
copy {
|
||||
from "${workingDir} + /../libs"
|
||||
into 'libs'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task cleanStrongSwanNative(type: Exec) {
|
||||
workingDir "${relativePathToStrongswan}"
|
||||
commandLine "${android.ndkDirectory}/ndk-build", 'clean'
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
compileTask -> compileTask.dependsOn buildStrongSwanNative
|
||||
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
|
||||
}
|
||||
|
||||
clean.dependsOn 'cleanStrongSwanNative'
|
||||
|
||||
// externalNativeBuild {
|
||||
// cmake {
|
||||
|
||||
+1
-1
@@ -261,7 +261,7 @@ object BaseService {
|
||||
|
||||
fun stopRunner(restart: Boolean = false, msg: String? = null) {
|
||||
if (data.state == State.Stopping) return
|
||||
// channge the state
|
||||
// change the state
|
||||
data.changeState(State.Stopping)
|
||||
GlobalScope.launch(Dispatchers.Main.immediate) {
|
||||
data.connectingJob?.cancelAndJoin() // ensure stop connecting first
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ object Key {
|
||||
const val id = "profileId"
|
||||
const val name = "profileName"
|
||||
|
||||
const val individual = "Proxyed"
|
||||
const val individual = "Proxied"
|
||||
|
||||
const val serviceMode = "serviceMode"
|
||||
const val modeProxy = "proxy"
|
||||
|
||||
@@ -835,7 +835,7 @@ public final class Ed25519 {
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes {@code s} into an extented projective point.
|
||||
* Decodes {@code s} into an extended projective point.
|
||||
* See Section 5.1.3 Decoding in https://tools.ietf.org/html/rfc8032#section-5.1.3
|
||||
*/
|
||||
private static XYZT fromBytesNegateVarTime(byte[] s) throws GeneralSecurityException {
|
||||
@@ -973,7 +973,7 @@ public final class Ed25519 {
|
||||
*
|
||||
* <p>NOTE that this function requires that {@code icopy} be 1 or 0; other values give wrong
|
||||
* results. Also, the two limb arrays must be in reduced-coefficient, reduced-degree form: the
|
||||
* values in a[10..19] or b[10..19] aren't swapped, and all all values in a[0..9],b[0..9] must
|
||||
* values in a[10..19] or b[10..19] aren't swapped, and all values in a[0..9],b[0..9] must
|
||||
* have magnitude less than Integer.MAX_VALUE.
|
||||
*/
|
||||
static void copyConditional(long[] a, long[] b, int icopy) {
|
||||
|
||||
@@ -0,0 +1,761 @@
|
||||
package org.amnezia.vpn
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.VpnService
|
||||
import android.net.VpnService.Builder
|
||||
import android.os.Build
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.system.OsConstants
|
||||
|
||||
import java.lang.Runnable
|
||||
|
||||
import org.amnezia.vpn.ikev2.VpnProfile
|
||||
import org.amnezia.vpn.ikev2.VpnProfile.SelectedAppsHandling
|
||||
import org.amnezia.vpn.ikev2.VpnType
|
||||
import org.amnezia.vpn.ikev2.VpnType.VpnTypeFeature
|
||||
import org.amnezia.vpn.ikev2.utils.IPRange
|
||||
import org.amnezia.vpn.ikev2.utils.IPRangeSet
|
||||
import org.amnezia.vpn.ikev2.utils.Utils
|
||||
import org.amnezia.vpn.ikev2.utils.Constants
|
||||
import org.amnezia.vpn.ikev2.utils.SettingsWriter
|
||||
import org.amnezia.vpn.ikev2.utils.SimpleFetcher
|
||||
|
||||
const val PACKAGE_NAME = "org.amnezia.vpn"
|
||||
const val LOG_FILE = "charon.log"
|
||||
const val TAG = "amnezia_ikev2"
|
||||
|
||||
class IKEv2Thread(
|
||||
val vpnServiceBuilder: android.net.VpnService.Builder,
|
||||
val filesDirAbsolutePath: String
|
||||
): Runnable {
|
||||
|
||||
private val mBuilderAdapter: BuilderAdapter = BuilderAdapter(vpnServiceBuilder)
|
||||
|
||||
private var mCurrentProfile: VpnProfile? = null
|
||||
private var mNextProfile: VpnProfile? = null
|
||||
|
||||
@kotlin.jvm.Volatile
|
||||
private var mCurrentCertificateAlias: String? = null
|
||||
|
||||
@kotlin.jvm.Volatile
|
||||
private var mCurrentUserCertificateAlias: String? = null
|
||||
|
||||
@kotlin.jvm.Volatile
|
||||
private var mProfileUpdated = false
|
||||
|
||||
@kotlin.jvm.Volatile
|
||||
private var mTerminate = false
|
||||
|
||||
@kotlin.jvm.Volatile
|
||||
private var mIsDisconnecting = false
|
||||
|
||||
private val mLogFile: String
|
||||
private val mAppDir: String
|
||||
|
||||
init {
|
||||
mLogFile = filesDirAbsolutePath + java.io.File.separator + LOG_FILE
|
||||
mAppDir = filesDirAbsolutePath
|
||||
}
|
||||
|
||||
fun setNextProfile(profile: VpnProfile) {
|
||||
|
||||
// TODO: take a look at "vpnprofileimportactivity" in starongswan repo
|
||||
// to understand how to pass the profile object before starting of ikev2 tunnel
|
||||
|
||||
synchronized(this) {
|
||||
mNextProfile = profile
|
||||
mProfileUpdated = true
|
||||
notifyAll()
|
||||
}
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
while (true) {
|
||||
synchronized(this) {
|
||||
try {
|
||||
while (!mProfileUpdated) {
|
||||
Log.i(TAG, "charon contunue")
|
||||
continue
|
||||
}
|
||||
|
||||
mProfileUpdated = false
|
||||
stopCurrentConnection()
|
||||
|
||||
if (mNextProfile == null) {
|
||||
setState(State.DISABLED)
|
||||
if (mTerminate) {
|
||||
return@synchronized
|
||||
}
|
||||
} else {
|
||||
mCurrentProfile = mNextProfile
|
||||
mNextProfile = null
|
||||
|
||||
val currentProfile = mCurrentProfile
|
||||
|
||||
/* store this in a separate (volatile) variable to avoid
|
||||
* a possible deadlock during deinitialization */
|
||||
mCurrentCertificateAlias = currentProfile!!.certificateAlias
|
||||
mCurrentUserCertificateAlias = currentProfile!!.userCertificateAlias
|
||||
|
||||
startConnection(currentProfile)
|
||||
|
||||
mIsDisconnecting = false
|
||||
SimpleFetcher.enable()
|
||||
addNotification()
|
||||
mBuilderAdapter.setProfile(currentProfile)
|
||||
|
||||
val flags: Int = currentProfile!!.flags
|
||||
|
||||
if (initializeCharon(
|
||||
mBuilderAdapter,
|
||||
mLogFile,
|
||||
mAppDir,
|
||||
currentProfile.vpnType!!.has(VpnTypeFeature.BYOD),
|
||||
flags and VpnProfile.FLAGS_IPv6_TRANSPORT !== 0
|
||||
)
|
||||
) {
|
||||
Log.i(TAG, "charon started")
|
||||
|
||||
if (currentProfile.vpnType!!.has(VpnTypeFeature.USER_PASS) &&
|
||||
currentProfile.password == null
|
||||
) { /* this can happen if Always-on VPN is enabled with an incomplete profile */
|
||||
setError(ErrorState.PASSWORD_MISSING)
|
||||
return@synchronized
|
||||
}
|
||||
|
||||
val writer = SettingsWriter()
|
||||
writer.setValue("global.language", java.util.Locale.getDefault().getLanguage())
|
||||
writer.setValue("global.mtu", currentProfile.MTU)
|
||||
writer.setValue("global.nat_keepalive", currentProfile.NATKeepAlive)
|
||||
writer.setValue("global.rsa_pss", flags and VpnProfile.FLAGS_RSA_PSS !== 0)
|
||||
writer.setValue("global.crl", flags and VpnProfile.FLAGS_DISABLE_CRL === 0)
|
||||
writer.setValue("global.ocsp", flags and VpnProfile.FLAGS_DISABLE_OCSP === 0)
|
||||
writer.setValue("connection.type", currentProfile.vpnType!!.identifier)
|
||||
writer.setValue("connection.server", currentProfile.gateway)
|
||||
writer.setValue("connection.port", currentProfile.port)
|
||||
writer.setValue("connection.username", currentProfile.username)
|
||||
writer.setValue("connection.password", currentProfile.password)
|
||||
writer.setValue("connection.local_id", currentProfile.localId)
|
||||
writer.setValue("connection.remote_id", currentProfile.remoteId)
|
||||
writer.setValue("connection.certreq", flags and VpnProfile.FLAGS_SUPPRESS_CERT_REQS === 0)
|
||||
writer.setValue("connection.strict_revocation", flags and VpnProfile.FLAGS_STRICT_REVOCATION !== 0)
|
||||
writer.setValue("connection.ike_proposal", currentProfile.ikeProposal)
|
||||
writer.setValue("connection.esp_proposal", currentProfile.espProposal)
|
||||
initiate(writer.serialize())
|
||||
} else {
|
||||
Log.e(TAG, "failed to start charon")
|
||||
setError(ErrorState.GENERIC_ERROR)
|
||||
setState(State.DISABLED)
|
||||
mCurrentProfile = null
|
||||
}
|
||||
}
|
||||
} catch (ex: java.lang.InterruptedException) {
|
||||
stopCurrentConnection()
|
||||
setState(State.DISABLED)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the state service about a new connection attempt.
|
||||
* Called by the handler thread.
|
||||
*
|
||||
* @param profile currently active VPN profile
|
||||
*/
|
||||
private fun startConnection(profile: VpnProfile) {
|
||||
// synchronized(mServiceLock) {
|
||||
// if (mService != null) {
|
||||
// mService.startConnection(profile)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop any existing connection by deinitializing charon.
|
||||
*/
|
||||
private fun stopCurrentConnection() {
|
||||
synchronized(this) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
mNextProfile?.let {
|
||||
mBuilderAdapter.setProfile(it)
|
||||
mBuilderAdapter.establishBlocking()
|
||||
}
|
||||
}
|
||||
|
||||
if (mCurrentProfile != null) {
|
||||
setState(State.DISCONNECTING)
|
||||
mIsDisconnecting = true
|
||||
SimpleFetcher.disable()
|
||||
deinitializeCharon()
|
||||
Log.i(TAG, "charon stopped")
|
||||
mCurrentProfile = null
|
||||
if (mNextProfile == null) { /* only do this if we are not connecting to another profile */
|
||||
removeNotification()
|
||||
mBuilderAdapter.closeBlocking()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current VPN state on the state service. Called by the handler
|
||||
* thread and any of charon's threads.
|
||||
*
|
||||
* @param state current state
|
||||
*/
|
||||
private fun setState(state: State) {
|
||||
// synchronized(mServiceLock) {
|
||||
// if (mService != null) {
|
||||
// mService.setState(state)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization of charon, provided by libandroidbridge.so
|
||||
*
|
||||
* @param builder BuilderAdapter for this connection
|
||||
* @param logfile absolute path to the logfile
|
||||
* @param appdir absolute path to the data directory of the app
|
||||
* @param byod enable BYOD features
|
||||
* @param ipv6 enable IPv6 transport
|
||||
* @return TRUE if initialization was successful
|
||||
*/
|
||||
external fun initializeCharon(
|
||||
builder: BuilderAdapter?,
|
||||
logfile: String?,
|
||||
appdir: String?,
|
||||
byod: Boolean,
|
||||
ipv6: Boolean
|
||||
): Boolean
|
||||
|
||||
/**
|
||||
* Deinitialize charon, provided by libandroidbridge.so
|
||||
*/
|
||||
external fun deinitializeCharon()
|
||||
|
||||
/**
|
||||
* Initiate VPN, provided by libandroidbridge.so
|
||||
*/
|
||||
external fun initiate(config: String?)
|
||||
|
||||
/**
|
||||
* Adapter for VpnService.Builder which is used to access it safely via JNI.
|
||||
* There is a corresponding C object to access it from native code.
|
||||
*/
|
||||
class BuilderAdapter(val builder: VpnService.Builder) {
|
||||
private lateinit var mProfile: VpnProfile
|
||||
private lateinit var mBuilder: VpnService.Builder
|
||||
private lateinit var mCache: BuilderCache
|
||||
private lateinit var mEstablishedCache: BuilderCache
|
||||
private val mDropper: PacketDropper = PacketDropper()
|
||||
|
||||
init {
|
||||
mBuilder = builder
|
||||
}
|
||||
|
||||
@kotlin.jvm.Synchronized
|
||||
fun setProfile(profile: VpnProfile) {
|
||||
mProfile = profile
|
||||
mBuilder = createBuilder(mProfile.name ?: "")
|
||||
mCache = BuilderCache(mProfile)
|
||||
}
|
||||
|
||||
private fun createBuilder(name: String): VpnService.Builder {
|
||||
// val builder: VpnService.Builder = Builder()
|
||||
|
||||
mBuilder.setSession(name)
|
||||
|
||||
/* even though the option displayed in the system dialog says "Configure"
|
||||
* we just use our main Activity */
|
||||
// val context: Context = getApplicationContext()
|
||||
// val intent = Intent(context, MainActivity::class.java)
|
||||
// var flags: Int = PendingIntent.FLAG_UPDATE_CURRENT
|
||||
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
// flags = flags or PendingIntent.FLAG_IMMUTABLE
|
||||
// }
|
||||
// val pending: PendingIntent = PendingIntent.getActivity(context, 0, intent, flags)
|
||||
// builder.setConfigureIntent(pending)
|
||||
|
||||
/* mark all VPN connections as unmetered (default changed for Android 10) */
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
mBuilder.setMetered(false)
|
||||
}
|
||||
return mBuilder
|
||||
}
|
||||
|
||||
@kotlin.jvm.Synchronized
|
||||
fun addAddress(address: String?, prefixLength: Int): Boolean {
|
||||
try {
|
||||
mCache.addAddress(address, prefixLength)
|
||||
} catch (ex: java.lang.IllegalArgumentException) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@kotlin.jvm.Synchronized
|
||||
fun addDnsServer(address: String?): Boolean {
|
||||
try {
|
||||
mCache.addDnsServer(address)
|
||||
} catch (ex: java.lang.IllegalArgumentException) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@kotlin.jvm.Synchronized
|
||||
fun addRoute(address: String?, prefixLength: Int): Boolean {
|
||||
try {
|
||||
mCache.addRoute(address, prefixLength)
|
||||
} catch (ex: java.lang.IllegalArgumentException) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@kotlin.jvm.Synchronized
|
||||
fun addSearchDomain(domain: String): Boolean {
|
||||
try {
|
||||
mBuilder.addSearchDomain(domain)
|
||||
} catch (ex: java.lang.IllegalArgumentException) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@kotlin.jvm.Synchronized
|
||||
fun setMtu(mtu: Int): Boolean {
|
||||
try {
|
||||
mCache.setMtu(mtu)
|
||||
} catch (ex: java.lang.IllegalArgumentException) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@kotlin.jvm.Synchronized
|
||||
private fun establishIntern(): ParcelFileDescriptor? {
|
||||
val fd: ParcelFileDescriptor?
|
||||
try {
|
||||
mCache.applyData(mBuilder)
|
||||
fd = mBuilder.establish()
|
||||
if (fd != null) {
|
||||
closeBlocking()
|
||||
}
|
||||
} catch (ex: java.lang.Exception) {
|
||||
ex.printStackTrace()
|
||||
return null
|
||||
}
|
||||
if (fd == null) {
|
||||
return null
|
||||
}
|
||||
/* now that the TUN device is created we don't need the current
|
||||
* builder anymore, but we might need another when reestablishing */
|
||||
mBuilder = createBuilder(mProfile.name ?: "")
|
||||
mEstablishedCache = mCache
|
||||
mCache = BuilderCache(mProfile)
|
||||
return fd
|
||||
}
|
||||
|
||||
@kotlin.jvm.Synchronized
|
||||
fun establish(): Int {
|
||||
val fd: ParcelFileDescriptor? = establishIntern()
|
||||
return if (fd != null) fd.detachFd() else -1
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
@kotlin.jvm.Synchronized
|
||||
fun establishBlocking() {
|
||||
/* just choose some arbitrary values to block all traffic (except for what's configured in the profile) */
|
||||
mCache.addAddress("172.16.252.1", 32)
|
||||
mCache.addAddress("fd00::fd02:1", 128)
|
||||
mCache.addRoute("0.0.0.0", 0)
|
||||
mCache.addRoute("::", 0)
|
||||
/* set DNS servers to avoid DNS leak later */
|
||||
mBuilder.addDnsServer("8.8.8.8")
|
||||
mBuilder.addDnsServer("2001:4860:4860::8888")
|
||||
/* use blocking mode to simplify packet dropping */
|
||||
mBuilder.setBlocking(true)
|
||||
val fd: ParcelFileDescriptor? = establishIntern()
|
||||
if (fd != null) {
|
||||
mDropper.start(fd)
|
||||
}
|
||||
}
|
||||
|
||||
@kotlin.jvm.Synchronized
|
||||
fun closeBlocking() {
|
||||
mDropper.stop()
|
||||
}
|
||||
|
||||
@kotlin.jvm.Synchronized
|
||||
fun establishNoDns(): Int {
|
||||
val fd: ParcelFileDescriptor?
|
||||
if (mEstablishedCache == null) {
|
||||
return -1
|
||||
}
|
||||
fd = try {
|
||||
val builder: VpnService.Builder = createBuilder(mProfile.name ?: "")
|
||||
mEstablishedCache.applyData(builder)
|
||||
builder.establish()
|
||||
} catch (ex: java.lang.Exception) {
|
||||
ex.printStackTrace()
|
||||
return -1
|
||||
}
|
||||
return if (fd == null) {
|
||||
-1
|
||||
} else fd.detachFd()
|
||||
}
|
||||
|
||||
private inner class PacketDropper : java.lang.Runnable {
|
||||
private var mFd: ParcelFileDescriptor? = null
|
||||
private lateinit var mThread: java.lang.Thread
|
||||
|
||||
fun start(fd: ParcelFileDescriptor) {
|
||||
mFd = fd
|
||||
mThread = java.lang.Thread(this)
|
||||
mThread.start()
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
if (mFd != null) {
|
||||
try {
|
||||
mThread.interrupt()
|
||||
mThread.join()
|
||||
mFd?.close()
|
||||
} catch (e: java.lang.InterruptedException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: java.io.IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
mFd = null
|
||||
}
|
||||
}
|
||||
|
||||
@kotlin.jvm.Synchronized
|
||||
override fun run() {
|
||||
try {
|
||||
val plain: java.io.FileInputStream = java.io.FileInputStream(mFd?.getFileDescriptor())
|
||||
val packet: java.nio.ByteBuffer = java.nio.ByteBuffer.allocate(mCache.mMtu)
|
||||
while (true) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { /* just read and ignore all data, regular read() is not interruptible */
|
||||
val len: Int = plain.getChannel().read(packet)
|
||||
packet.clear()
|
||||
if (len < 0) {
|
||||
break
|
||||
}
|
||||
} else { /* this is rather ugly but on older platforms not even the NIO version of read() is interruptible */
|
||||
var wait = true
|
||||
if (plain.available() > 0) {
|
||||
val len: Int = plain.read(packet.array())
|
||||
packet.clear()
|
||||
if (len < 0 || java.lang.Thread.interrupted()) {
|
||||
break
|
||||
}
|
||||
/* check again right away, there may be another packet */wait = false
|
||||
}
|
||||
if (wait) {
|
||||
java.lang.Thread.sleep(250)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: java.nio.channels.ClosedByInterruptException) {
|
||||
/* regular interruption */
|
||||
} catch (e: java.lang.InterruptedException) {
|
||||
} catch (e: java.io.IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache non DNS related information so we can recreate the builder without
|
||||
* that information when reestablishing IKE_SAs
|
||||
*/
|
||||
class BuilderCache(profile: VpnProfile) {
|
||||
val mAddresses: MutableList<IPRange> = java.util.ArrayList<IPRange>()
|
||||
val mRoutesIPv4: MutableList<IPRange> = java.util.ArrayList<IPRange>()
|
||||
val mRoutesIPv6: MutableList<IPRange> = java.util.ArrayList<IPRange>()
|
||||
val mIncludedSubnetsv4: IPRangeSet = IPRangeSet()
|
||||
val mIncludedSubnetsv6: IPRangeSet = IPRangeSet()
|
||||
val mExcludedSubnets: IPRangeSet
|
||||
val mSplitTunneling: Int
|
||||
val mAppHandling: SelectedAppsHandling
|
||||
val mSelectedApps: java.util.SortedSet<String>
|
||||
val mDnsServers: MutableList<java.net.InetAddress> = java.util.ArrayList<java.net.InetAddress>()
|
||||
var mMtu: Int
|
||||
var mIPv4Seen = false
|
||||
var mIPv6Seen = false
|
||||
var mDnsServersConfigured = false
|
||||
|
||||
init {
|
||||
val included: IPRangeSet = IPRangeSet.fromString(profile?.includedSubnets)
|
||||
for (range in included) {
|
||||
if (range.getFrom() is java.net.Inet4Address) {
|
||||
mIncludedSubnetsv4.add(range)
|
||||
} else if (range.getFrom() is java.net.Inet6Address) {
|
||||
mIncludedSubnetsv6.add(range)
|
||||
}
|
||||
}
|
||||
mExcludedSubnets = IPRangeSet.fromString(profile.excludedSubnets ?: "")
|
||||
val splitTunneling: Int? = profile.splitTunneling
|
||||
mSplitTunneling = splitTunneling ?: 0
|
||||
var appHandling: SelectedAppsHandling = profile.selectedAppsHandling
|
||||
mSelectedApps = profile.selectedAppsSet
|
||||
|
||||
when (appHandling) {
|
||||
SelectedAppsHandling.SELECTED_APPS_DISABLE -> {
|
||||
appHandling = SelectedAppsHandling.SELECTED_APPS_EXCLUDE
|
||||
mSelectedApps.clear()
|
||||
mSelectedApps.add(PACKAGE_NAME)
|
||||
}
|
||||
|
||||
SelectedAppsHandling.SELECTED_APPS_EXCLUDE -> {
|
||||
mSelectedApps.add(PACKAGE_NAME)
|
||||
}
|
||||
|
||||
SelectedAppsHandling.SELECTED_APPS_ONLY -> {
|
||||
mSelectedApps.remove(PACKAGE_NAME)
|
||||
}
|
||||
}
|
||||
mAppHandling = appHandling
|
||||
|
||||
val dnsServers = profile.dnsServers
|
||||
if (dnsServers != null) {
|
||||
for (server in dnsServers.split("\\s+")) {
|
||||
try {
|
||||
mDnsServers.add(Utils.parseInetAddress(server))
|
||||
recordAddressFamily(server)
|
||||
mDnsServersConfigured = true
|
||||
} catch (e: java.net.UnknownHostException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* set a default MTU, will be set by the daemon for regular interfaces */
|
||||
val mtu: Int? = profile.MTU
|
||||
mMtu = mtu ?: Constants.MTU_MAX
|
||||
}
|
||||
|
||||
fun addAddress(address: String?, prefixLength: Int) {
|
||||
try {
|
||||
mAddresses.add(IPRange(address, prefixLength))
|
||||
recordAddressFamily(address)
|
||||
} catch (ex: java.net.UnknownHostException) {
|
||||
ex.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun addDnsServer(address: String?) {
|
||||
/* ignore received DNS servers if any were configured */
|
||||
if (mDnsServersConfigured) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
mDnsServers.add(Utils.parseInetAddress(address))
|
||||
recordAddressFamily(address)
|
||||
} catch (e: java.net.UnknownHostException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun addRoute(address: String?, prefixLength: Int) {
|
||||
try {
|
||||
if (isIPv6(address)) {
|
||||
mRoutesIPv6.add(IPRange(address, prefixLength))
|
||||
} else {
|
||||
mRoutesIPv4.add(IPRange(address, prefixLength))
|
||||
}
|
||||
} catch (ex: java.net.UnknownHostException) {
|
||||
ex.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun setMtu(mtu: Int) {
|
||||
mMtu = mtu
|
||||
}
|
||||
|
||||
fun recordAddressFamily(address: String?) {
|
||||
try {
|
||||
if (isIPv6(address)) {
|
||||
mIPv6Seen = true
|
||||
} else {
|
||||
mIPv4Seen = true
|
||||
}
|
||||
} catch (ex: java.net.UnknownHostException) {
|
||||
ex.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
fun applyData(builder: VpnService.Builder) {
|
||||
for (address in mAddresses) {
|
||||
builder.addAddress(address.getFrom(), address.getPrefix())
|
||||
}
|
||||
for (server in mDnsServers) {
|
||||
builder.addDnsServer(server)
|
||||
}
|
||||
/* add routes depending on whether split tunneling is allowed or not,
|
||||
* that is, whether we have to handle and block non-VPN traffic */
|
||||
if (mSplitTunneling and VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4 === 0) {
|
||||
if (mIPv4Seen) { /* split tunneling is used depending on the routes and configuration */
|
||||
val ranges = IPRangeSet()
|
||||
if (mIncludedSubnetsv4.size() > 0) {
|
||||
ranges.add(mIncludedSubnetsv4)
|
||||
} else {
|
||||
ranges.addAll(mRoutesIPv4)
|
||||
}
|
||||
ranges.remove(mExcludedSubnets)
|
||||
for (subnet in ranges.subnets()) {
|
||||
try {
|
||||
builder.addRoute(subnet.getFrom(), subnet.getPrefix())
|
||||
} catch (e: java.lang.IllegalArgumentException) { /* some Android versions don't seem to like multicast addresses here,
|
||||
* ignore it for now */
|
||||
if (!subnet.getFrom().isMulticastAddress()) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { /* allow traffic that would otherwise be blocked to bypass the VPN */
|
||||
builder.allowFamily(OsConstants.AF_INET)
|
||||
}
|
||||
} else if (mIPv4Seen) { /* only needed if we've seen any addresses. otherwise, traffic
|
||||
* is blocked by default (we also install no routes in that case) */
|
||||
builder.addRoute("0.0.0.0", 0)
|
||||
}
|
||||
/* same thing for IPv6 */
|
||||
if (mSplitTunneling and VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6 === 0) {
|
||||
if (mIPv6Seen) {
|
||||
val ranges = IPRangeSet()
|
||||
if (mIncludedSubnetsv6.size() > 0) {
|
||||
ranges.add(mIncludedSubnetsv6)
|
||||
} else {
|
||||
ranges.addAll(mRoutesIPv6)
|
||||
}
|
||||
ranges.remove(mExcludedSubnets)
|
||||
for (subnet in ranges.subnets()) {
|
||||
try {
|
||||
builder.addRoute(subnet.getFrom(), subnet.getPrefix())
|
||||
} catch (e: java.lang.IllegalArgumentException) {
|
||||
if (!subnet.getFrom().isMulticastAddress()) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
builder.allowFamily(OsConstants.AF_INET6)
|
||||
}
|
||||
} else if (mIPv6Seen) {
|
||||
builder.addRoute("::", 0)
|
||||
}
|
||||
/* apply selected applications */
|
||||
if (mSelectedApps.size > 0 &&
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
|
||||
) {
|
||||
when (mAppHandling) {
|
||||
SelectedAppsHandling.SELECTED_APPS_EXCLUDE -> for (app in mSelectedApps) {
|
||||
try {
|
||||
builder.addDisallowedApplication(app)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
// possible if not configured via GUI or app was uninstalled
|
||||
}
|
||||
}
|
||||
|
||||
SelectedAppsHandling.SELECTED_APPS_ONLY -> for (app in mSelectedApps) {
|
||||
try {
|
||||
builder.addAllowedApplication(app)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
// possible if not configured via GUI or app was uninstalled
|
||||
}
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
builder.setMtu(mMtu)
|
||||
}
|
||||
|
||||
@kotlin.Throws(java.net.UnknownHostException::class)
|
||||
private fun isIPv6(address: String?): Boolean {
|
||||
val addr: java.net.InetAddress = Utils.parseInetAddress(address)
|
||||
if (addr is java.net.Inet4Address) {
|
||||
return false
|
||||
} else if (addr is java.net.Inet6Address) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called via JNI to determine information about the Android version.
|
||||
*/
|
||||
private fun getAndroidVersion(): String? {
|
||||
var version = "Android " + Build.VERSION.RELEASE + " - " + Build.DISPLAY
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
version += "/" + Build.VERSION.SECURITY_PATCH
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called via JNI to determine information about the device.
|
||||
*/
|
||||
private fun getDeviceString(): String? {
|
||||
return ((Build.MODEL + " - " + Build.BRAND).toString() + "/" + Build.PRODUCT).toString() + "/" + Build.MANUFACTURER
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a permanent notification while we are connected to avoid the service getting killed by
|
||||
* the system when low on memory.
|
||||
*/
|
||||
private fun addNotification() {
|
||||
// mHandler.post(object : java.lang.Runnable {
|
||||
// fun run() {
|
||||
// mShowNotification = true
|
||||
// startForeground(
|
||||
// org.strongswan.android.logic.CharonVpnService.VPN_STATE_NOTIFICATION_ID,
|
||||
// buildNotification(false)
|
||||
// )
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the permanent notification.
|
||||
*/
|
||||
private fun removeNotification() {
|
||||
// mHandler.post(object : java.lang.Runnable {
|
||||
// fun run() {
|
||||
// mShowNotification = false
|
||||
// stopForeground(true)
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an error on the state service. Called by the handler thread and any
|
||||
* of charon's threads.
|
||||
*
|
||||
* @param error error state
|
||||
*/
|
||||
private fun setError(error: ErrorState) {
|
||||
// synchronized(mServiceLock) {
|
||||
// if (mService != null) {
|
||||
// mService.setError(error)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
enum class State {
|
||||
DISABLED, CONNECTING, CONNECTED, DISCONNECTING
|
||||
}
|
||||
|
||||
enum class ErrorState {
|
||||
NO_ERROR, AUTH_FAILED, PEER_AUTH_FAILED, LOOKUP_FAILED, UNREACHABLE, GENERIC_ERROR, PASSWORD_MISSING, CERTIFICATE_UNAVAILABLE
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ object NotificationUtil {
|
||||
* Parcel - Gets called from AndroidController.cpp
|
||||
*/
|
||||
fun update(data: Parcel) {
|
||||
// [data] is here a json containing the noification content
|
||||
// [data] is here a json containing the notification content
|
||||
val buffer = data.createByteArray()
|
||||
val json = buffer?.let { String(it) }
|
||||
val content = JSONObject(json)
|
||||
|
||||
@@ -35,10 +35,10 @@ class OpenVPNThreadv3(var service: VPNService): ClientAPI_OpenVPNClient(), Runna
|
||||
private var bytesOutIndex = -1
|
||||
|
||||
init {
|
||||
findConfigIndicies()
|
||||
findConfigIndices()
|
||||
}
|
||||
|
||||
private fun findConfigIndicies() {
|
||||
private fun findConfigIndices() {
|
||||
val n: Int = stats_n()
|
||||
|
||||
for (i in 0 until n) {
|
||||
|
||||
@@ -29,7 +29,7 @@ object Prefs {
|
||||
return sharedPreferences
|
||||
} catch (e: Exception) {
|
||||
Log.e("Android-Prefs", "Getting Encryption Storage failed, plaintext fallback")
|
||||
return context.getSharedPreferences("com.amnezia.vpn.prefrences", Context.MODE_PRIVATE)
|
||||
return context.getSharedPreferences("com.amnezia.vpn.preferences", Context.MODE_PRIVATE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,14 +11,14 @@ import android.util.Log as nativeLog
|
||||
|
||||
/*
|
||||
* Drop in replacement for android.util.Log
|
||||
* Also stores a copy of all logs in tmp/mozilla_deamon_logs.txt
|
||||
* Also stores a copy of all logs in tmp/mozilla_daemon_logs.txt
|
||||
*/
|
||||
class Log {
|
||||
val LOG_MAX_FILE_SIZE = 204800
|
||||
private var file: File
|
||||
private constructor(context: Context) {
|
||||
val tempDIR = context.cacheDir
|
||||
file = File(tempDIR, "mozilla_deamon_logs.txt")
|
||||
file = File(tempDIR, "mozilla_daemon_logs.txt")
|
||||
if (file.length() > LOG_MAX_FILE_SIZE) {
|
||||
file.writeText("")
|
||||
}
|
||||
@@ -46,7 +46,7 @@ class Log {
|
||||
if (!BuildConfig.DEBUG) { return; }
|
||||
nativeLog.e(tag, message)
|
||||
}
|
||||
// Only Prints && Loggs when in debug, noop in release.
|
||||
// Only Prints && Logs when in debug, noop in release.
|
||||
fun sensitive(tag: String, message: String?) {
|
||||
if (!BuildConfig.DEBUG) { return; }
|
||||
if (message == null) { return; }
|
||||
|
||||
@@ -150,6 +150,8 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
|
||||
private var mOpenVPNThreadv3: OpenVPNThreadv3? = null
|
||||
var currentTunnelHandle = -1
|
||||
|
||||
private var ikev2VpnThread: IKEv2Thread? = null
|
||||
|
||||
private var intent: Intent? = null
|
||||
private var flags = 0
|
||||
private var startId = 0
|
||||
@@ -165,6 +167,7 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
|
||||
Log.e(tag, "Wireguard Version ${wgVersion()}")
|
||||
mOpenVPNThreadv3 = OpenVPNThreadv3(this)
|
||||
mAlreadyInitialised = true
|
||||
ikev2VpnThread = IKEv2Thread(mbuilder, getFilesDir().getAbsolutePath())
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
@@ -384,8 +387,11 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
|
||||
startShadowsocks()
|
||||
startTest()
|
||||
}
|
||||
"ikev2" -> {
|
||||
startIPSEC()
|
||||
}
|
||||
else -> {
|
||||
Log.e(tag, "No protocol")
|
||||
Log.e(tag, "Unknown protocol ($mProtocol)")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -393,6 +399,18 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
|
||||
return 1
|
||||
}
|
||||
|
||||
private fun startIPSEC() {
|
||||
Log.v(tag, "start ipsec")
|
||||
ikev2VpnThread = IKEv2Thread(mbuilder, getFilesDir().getAbsolutePath())
|
||||
|
||||
// TODO: pass the "vpnprofile" instance as a parameter
|
||||
// ikev2VpnThread.setNextProfile()
|
||||
|
||||
Thread({
|
||||
ikev2VpnThread?.run()
|
||||
}).start()
|
||||
}
|
||||
|
||||
fun establish(): ParcelFileDescriptor? {
|
||||
Log.v(tag, "Aman: establish....................")
|
||||
mbuilder.allowFamily(OsConstants.AF_INET)
|
||||
@@ -548,7 +566,7 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
|
||||
* Create a Wireguard [Config] from a [json] string -
|
||||
* The [json] will be created in AndroidVpnProtocol.cpp
|
||||
*/
|
||||
private fun buildWireugardConfig(obj: JSONObject): Config {
|
||||
private fun buildWireguardConfig(obj: JSONObject): Config {
|
||||
val confBuilder = Config.Builder()
|
||||
val wireguardConfigData = obj.getJSONObject("wireguard_config_data")
|
||||
val config = parseConfigData(wireguardConfigData.getString("config"))
|
||||
@@ -697,7 +715,7 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
|
||||
}
|
||||
|
||||
private fun startWireGuard() {
|
||||
val wireguard_conf = buildWireugardConfig(mConfig!!)
|
||||
val wireguard_conf = buildWireguardConfig(mConfig!!)
|
||||
Log.i(tag, "startWireGuard: wireguard_conf : $wireguard_conf")
|
||||
if (currentTunnelHandle != -1) {
|
||||
Log.e(tag, "Tunnel already up")
|
||||
|
||||
@@ -50,7 +50,7 @@ class VPNServiceBinder(service: VPNService) : Binder() {
|
||||
when (code) {
|
||||
ACTIONS.activate -> {
|
||||
try {
|
||||
Log.i(tag, "Activiation Requested, parsing Config")
|
||||
Log.i(tag, "Activation Requested, parsing Config")
|
||||
// [data] is here a json containing the wireguard/openvpn conf
|
||||
val buffer = data.createByteArray()
|
||||
val json = buffer?.let { String(it) }
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
package org.amnezia.vpn.ikev2
|
||||
|
||||
import android.text.TextUtils
|
||||
|
||||
class VpnProfile : kotlin.Cloneable {
|
||||
var name: String? = null
|
||||
var gateway: String? = null
|
||||
var username: String? = null
|
||||
var password: String? = null
|
||||
var certificateAlias: String? = null
|
||||
var userCertificateAlias: String? = null
|
||||
var remoteId: String? = null
|
||||
var localId: String? = null
|
||||
var excludedSubnets: String? = null
|
||||
var includedSubnets: String? = null
|
||||
var selectedApps: String? = null
|
||||
var ikeProposal: String? = null
|
||||
var espProposal: String? = null
|
||||
var dnsServers: String? = null
|
||||
var MTU: Int? = null
|
||||
var port: Int? = null
|
||||
var splitTunneling: Int? = null
|
||||
var NATKeepAlive: Int? = null
|
||||
private var mFlags: Int? = null
|
||||
var selectedAppsHandling = SelectedAppsHandling.SELECTED_APPS_DISABLE
|
||||
private var mVpnType: VpnType? = null
|
||||
private var mUUID: java.util.UUID?
|
||||
var id: Long = -1
|
||||
|
||||
enum class SelectedAppsHandling(val value: Int) {
|
||||
SELECTED_APPS_DISABLE(0), SELECTED_APPS_EXCLUDE(1), SELECTED_APPS_ONLY(2);
|
||||
|
||||
}
|
||||
|
||||
init {
|
||||
mUUID = java.util.UUID.randomUUID()
|
||||
}
|
||||
|
||||
var uUID: java.util.UUID?
|
||||
get() = mUUID
|
||||
set(uuid) {
|
||||
mUUID = uuid
|
||||
}
|
||||
var vpnType: VpnType?
|
||||
get() = mVpnType
|
||||
set(type) {
|
||||
mVpnType = type
|
||||
}
|
||||
|
||||
fun setSelectedApps(selectedApps: java.util.SortedSet<String?>) {
|
||||
this.selectedApps = if (selectedApps.size > 0) TextUtils.join(" ", selectedApps) else null
|
||||
}
|
||||
|
||||
val selectedAppsSet: java.util.SortedSet<String>
|
||||
get() {
|
||||
val set: java.util.TreeSet<String> = java.util.TreeSet<String>()
|
||||
if (!TextUtils.isEmpty(selectedApps)) {
|
||||
set.addAll(
|
||||
java.util.Arrays.asList<String>(
|
||||
*selectedApps!!.split("\\s+".toRegex()).dropLastWhile { it.isEmpty() }
|
||||
.toTypedArray()))
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
fun setSelectedAppsHandling(value: Int) {
|
||||
selectedAppsHandling = SelectedAppsHandling.SELECTED_APPS_DISABLE
|
||||
for (handling in VpnProfile.SelectedAppsHandling.values()) {
|
||||
if (handling.value == value) {
|
||||
selectedAppsHandling = handling
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var flags: Int = 0
|
||||
|
||||
override fun toString(): String {
|
||||
return name!!
|
||||
}
|
||||
|
||||
override fun equals(o: Any?): Boolean {
|
||||
if (o != null && o is VpnProfile) {
|
||||
val other = o
|
||||
return if (mUUID != null && other.uUID != null) {
|
||||
mUUID == other.uUID
|
||||
} else id == other.id
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun clone(): VpnProfile {
|
||||
return try {
|
||||
super.clone() as VpnProfile
|
||||
} catch (e: java.lang.CloneNotSupportedException) {
|
||||
throw java.lang.AssertionError()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/* While storing this as EnumSet would be nicer this simplifies storing it in a database */
|
||||
const val SPLIT_TUNNELING_BLOCK_IPV4 = 1
|
||||
const val SPLIT_TUNNELING_BLOCK_IPV6 = 2
|
||||
const val FLAGS_SUPPRESS_CERT_REQS = 1 shl 0
|
||||
const val FLAGS_DISABLE_CRL = 1 shl 1
|
||||
const val FLAGS_DISABLE_OCSP = 1 shl 2
|
||||
const val FLAGS_STRICT_REVOCATION = 1 shl 3
|
||||
const val FLAGS_RSA_PSS = 1 shl 4
|
||||
const val FLAGS_IPv6_TRANSPORT = 1 shl 5
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package org.amnezia.vpn.ikev2
|
||||
|
||||
enum class VpnType(
|
||||
/**
|
||||
* The identifier used to store this value in the database
|
||||
* @return identifier
|
||||
*/
|
||||
val identifier: String, features: java.util.EnumSet<VpnTypeFeature>
|
||||
) {
|
||||
/* the order here must match the items in R.array.vpn_types */
|
||||
IKEV2_EAP("ikev2-eap", java.util.EnumSet.of(VpnTypeFeature.USER_PASS)), IKEV2_CERT(
|
||||
"ikev2-cert", java.util.EnumSet.of(
|
||||
VpnTypeFeature.CERTIFICATE
|
||||
)
|
||||
),
|
||||
IKEV2_CERT_EAP(
|
||||
"ikev2-cert-eap",
|
||||
java.util.EnumSet.of(VpnTypeFeature.USER_PASS, VpnTypeFeature.CERTIFICATE)
|
||||
),
|
||||
IKEV2_EAP_TLS(
|
||||
"ikev2-eap-tls", java.util.EnumSet.of(
|
||||
VpnTypeFeature.CERTIFICATE
|
||||
)
|
||||
),
|
||||
IKEV2_BYOD_EAP("ikev2-byod-eap", java.util.EnumSet.of(VpnTypeFeature.USER_PASS, VpnTypeFeature.BYOD));
|
||||
|
||||
/**
|
||||
* Features of a VPN type.
|
||||
*/
|
||||
enum class VpnTypeFeature {
|
||||
/** client certificate is required */
|
||||
CERTIFICATE,
|
||||
|
||||
/** username and password are required */
|
||||
USER_PASS,
|
||||
|
||||
/** enable BYOD features */
|
||||
BYOD
|
||||
}
|
||||
|
||||
private val mFeatures: java.util.EnumSet<VpnTypeFeature>
|
||||
|
||||
/**
|
||||
* Enum which provides additional information about the supported VPN types.
|
||||
*
|
||||
* @param id identifier used to store and transmit this specific type
|
||||
* @param features of the given VPN type
|
||||
* @param certificate true if a client certificate is required
|
||||
*/
|
||||
init {
|
||||
mFeatures = features
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a feature is supported/required by this type of VPN.
|
||||
*
|
||||
* @return true if the feature is supported/required
|
||||
*/
|
||||
fun has(feature: VpnTypeFeature?): Boolean {
|
||||
return mFeatures.contains(feature)
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Get the enum entry with the given identifier.
|
||||
*
|
||||
* @param identifier get the enum entry with this identifier
|
||||
* @return the enum entry, or the default if not found
|
||||
*/
|
||||
fun fromIdentifier(identifier: String): VpnType {
|
||||
for (type in VpnType.values()) {
|
||||
if (identifier == type.identifier) {
|
||||
return type
|
||||
}
|
||||
}
|
||||
return IKEV2_EAP
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
*
|
||||
* Copyright (C) secunet Security Networks AG
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
package org.amnezia.vpn.ikev2.utils;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Very similar to ByteBuffer (although with a stripped interface) but it
|
||||
* automatically resizes the underlying buffer.
|
||||
*/
|
||||
public class BufferedByteWriter
|
||||
{
|
||||
/**
|
||||
* The underlying byte buffer
|
||||
*/
|
||||
private byte[] mBuffer;
|
||||
|
||||
/**
|
||||
* ByteBuffer used as wrapper around the buffer to easily convert values
|
||||
*/
|
||||
private ByteBuffer mWriter;
|
||||
|
||||
/**
|
||||
* Create a writer with a default initial capacity
|
||||
*/
|
||||
public BufferedByteWriter()
|
||||
{
|
||||
this(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a writer with the given initial capacity (helps avoid expensive
|
||||
* resizing if known).
|
||||
* @param capacity initial capacity
|
||||
*/
|
||||
public BufferedByteWriter(int capacity)
|
||||
{
|
||||
capacity = capacity > 4 ? capacity : 32;
|
||||
mBuffer = new byte[capacity];
|
||||
mWriter = ByteBuffer.wrap(mBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that there is enough space available to write the requested
|
||||
* number of bytes. If necessary the internal buffer is resized.
|
||||
* @param required required number of bytes
|
||||
*/
|
||||
private void ensureCapacity(int required)
|
||||
{
|
||||
if (mWriter.remaining() >= required)
|
||||
{
|
||||
return;
|
||||
}
|
||||
byte[] buffer = new byte[(mBuffer.length + required) * 2];
|
||||
System.arraycopy(mBuffer, 0, buffer, 0, mWriter.position());
|
||||
mBuffer = buffer;
|
||||
ByteBuffer writer = ByteBuffer.wrap(buffer);
|
||||
writer.position(mWriter.position());
|
||||
mWriter = writer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given byte array to the buffer
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public BufferedByteWriter put(byte[] value)
|
||||
{
|
||||
ensureCapacity(value.length);
|
||||
mWriter.put(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given byte to the buffer
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public BufferedByteWriter put(byte value)
|
||||
{
|
||||
ensureCapacity(1);
|
||||
mWriter.put(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the 8-bit length of the given data followed by the data itself
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public BufferedByteWriter putLen8(byte[] value)
|
||||
{
|
||||
ensureCapacity(1 + value.length);
|
||||
mWriter.put((byte)value.length);
|
||||
mWriter.put(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the 16-bit length of the given data followed by the data itself
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public BufferedByteWriter putLen16(byte[] value)
|
||||
{
|
||||
ensureCapacity(2 + value.length);
|
||||
mWriter.putShort((short)value.length);
|
||||
mWriter.put(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given short value (16-bit) in big-endian order to the buffer
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public BufferedByteWriter put16(byte value)
|
||||
{
|
||||
return this.put16((short)(value & 0xFF));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given short value (16-bit) in big-endian order to the buffer
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public BufferedByteWriter put16(short value)
|
||||
{
|
||||
ensureCapacity(2);
|
||||
mWriter.putShort(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write 24-bit of the given value in big-endian order to the buffer
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public BufferedByteWriter put24(byte value)
|
||||
{
|
||||
ensureCapacity(3);
|
||||
mWriter.putShort((short)0);
|
||||
mWriter.put(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write 24-bit of the given value in big-endian order to the buffer
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public BufferedByteWriter put24(short value)
|
||||
{
|
||||
ensureCapacity(3);
|
||||
mWriter.put((byte)0);
|
||||
mWriter.putShort(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write 24-bit of the given value in big-endian order to the buffer
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public BufferedByteWriter put24(int value)
|
||||
{
|
||||
ensureCapacity(3);
|
||||
mWriter.put((byte)(value >> 16));
|
||||
mWriter.putShort((short)value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given int value (32-bit) in big-endian order to the buffer
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public BufferedByteWriter put32(byte value)
|
||||
{
|
||||
return put32(value & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given int value (32-bit) in big-endian order to the buffer
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public BufferedByteWriter put32(short value)
|
||||
{
|
||||
return put32(value & 0xFFFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given int value (32-bit) in big-endian order to the buffer
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public BufferedByteWriter put32(int value)
|
||||
{
|
||||
ensureCapacity(4);
|
||||
mWriter.putInt(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given long value (64-bit) in big-endian order to the buffer
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public BufferedByteWriter put64(byte value)
|
||||
{
|
||||
return put64(value & 0xFFL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given long value (64-bit) in big-endian order to the buffer
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public BufferedByteWriter put64(short value)
|
||||
{
|
||||
return put64(value & 0xFFFFL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given long value (64-bit) in big-endian order to the buffer
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public BufferedByteWriter put64(int value)
|
||||
{
|
||||
return put64(value & 0xFFFFFFFFL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given long value (64-bit) in big-endian order to the buffer
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public BufferedByteWriter put64(long value)
|
||||
{
|
||||
ensureCapacity(8);
|
||||
mWriter.putLong(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the internal buffer to a new byte array.
|
||||
* @return byte array
|
||||
*/
|
||||
public byte[] toByteArray()
|
||||
{
|
||||
int length = mWriter.position();
|
||||
byte[] bytes = new byte[length];
|
||||
System.arraycopy(mBuffer, 0, bytes, 0, length);
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2020 Tobias Brunner
|
||||
*
|
||||
* Copyright (C) secunet Security Networks AG
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
package org.amnezia.vpn.ikev2.utils;
|
||||
|
||||
public final class Constants
|
||||
{
|
||||
/**
|
||||
* Intent action used to notify about changes to the VPN profiles
|
||||
*/
|
||||
public static final String VPN_PROFILES_CHANGED = "org.strongswan.android.VPN_PROFILES_CHANGED";
|
||||
|
||||
/**
|
||||
* Used in the intent above to notify about edits or inserts of a VPN profile (long)
|
||||
*/
|
||||
public static final String VPN_PROFILES_SINGLE = "org.strongswan.android.VPN_PROFILES_SINGLE";
|
||||
|
||||
/**
|
||||
* Used in the intent above to notify about the deletion of multiple VPN profiles (array of longs)
|
||||
*/
|
||||
public static final String VPN_PROFILES_MULTIPLE = "org.strongswan.android.VPN_PROFILES_MULTIPLE";
|
||||
|
||||
/**
|
||||
* Limits for MTU
|
||||
*/
|
||||
public static final int MTU_MAX = 1500;
|
||||
public static final int MTU_MIN = 1280;
|
||||
|
||||
/**
|
||||
* Limits for NAT-T keepalive
|
||||
*/
|
||||
public static final int NAT_KEEPALIVE_MAX = 120;
|
||||
public static final int NAT_KEEPALIVE_MIN = 10;
|
||||
|
||||
/**
|
||||
* Preference key for default VPN profile
|
||||
*/
|
||||
public static final String PREF_DEFAULT_VPN_PROFILE = "pref_default_vpn_profile";
|
||||
|
||||
/**
|
||||
* Value used to signify that the most recently used profile should be used as default
|
||||
*/
|
||||
public static final String PREF_DEFAULT_VPN_PROFILE_MRU = "pref_default_vpn_profile_mru";
|
||||
|
||||
/**
|
||||
* Preference key to store the most recently used VPN profile
|
||||
*/
|
||||
public static final String PREF_MRU_VPN_PROFILE = "pref_mru_vpn_profile";
|
||||
|
||||
/**
|
||||
* Preference key to store whether the user permanently dismissed our warning to add the app to the power whitelist
|
||||
*/
|
||||
public static final String PREF_IGNORE_POWER_WHITELIST = "pref_ignore_power_whitelist";
|
||||
}
|
||||
@@ -0,0 +1,510 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 Tobias Brunner
|
||||
*
|
||||
* Copyright (C) secunet Security Networks AG
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
package org.amnezia.vpn.ikev2.utils;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Class that represents a range of IP addresses. This range could be a proper subnet, but that's
|
||||
* not necessarily the case (see {@code getPrefix} and {@code toSubnets}).
|
||||
*/
|
||||
public class IPRange implements Comparable<IPRange>
|
||||
{
|
||||
private final byte[] mBitmask = { (byte)0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
|
||||
private byte[] mFrom;
|
||||
private byte[] mTo;
|
||||
private Integer mPrefix;
|
||||
|
||||
/**
|
||||
* Determine if the range is a proper subnet and, if so, what the network prefix is.
|
||||
*/
|
||||
private void determinePrefix()
|
||||
{
|
||||
boolean matching = true;
|
||||
|
||||
mPrefix = mFrom.length * 8;
|
||||
for (int i = 0; i < mFrom.length; i++)
|
||||
{
|
||||
for (int bit = 0; bit < 8; bit++)
|
||||
{
|
||||
if (matching)
|
||||
{
|
||||
if ((mFrom[i] & mBitmask[bit]) != (mTo[i] & mBitmask[bit]))
|
||||
{
|
||||
mPrefix = (i * 8) + bit;
|
||||
matching = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((mFrom[i] & mBitmask[bit]) != 0 || (mTo[i] & mBitmask[bit]) == 0)
|
||||
{
|
||||
mPrefix = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IPRange(byte[] from, byte[] to)
|
||||
{
|
||||
mFrom = from;
|
||||
mTo = to;
|
||||
determinePrefix();
|
||||
}
|
||||
|
||||
public IPRange(String from, String to) throws UnknownHostException
|
||||
{
|
||||
this(Utils.parseInetAddress(from), Utils.parseInetAddress(to));
|
||||
}
|
||||
|
||||
public IPRange(InetAddress from, InetAddress to)
|
||||
{
|
||||
initializeFromRange(from, to);
|
||||
}
|
||||
|
||||
private void initializeFromRange(InetAddress from, InetAddress to)
|
||||
{
|
||||
byte[] fa = from.getAddress(), ta = to.getAddress();
|
||||
if (fa.length != ta.length)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid range");
|
||||
}
|
||||
if (compareAddr(fa, ta) < 0)
|
||||
{
|
||||
mFrom = fa;
|
||||
mTo = ta;
|
||||
}
|
||||
else
|
||||
{
|
||||
mTo = fa;
|
||||
mFrom = ta;
|
||||
}
|
||||
determinePrefix();
|
||||
}
|
||||
|
||||
public IPRange(String base, int prefix) throws UnknownHostException
|
||||
{
|
||||
this(Utils.parseInetAddress(base), prefix);
|
||||
}
|
||||
|
||||
public IPRange(InetAddress base, int prefix)
|
||||
{
|
||||
this(base.getAddress(), prefix);
|
||||
}
|
||||
|
||||
private IPRange(byte[] from, int prefix)
|
||||
{
|
||||
initializeFromCIDR(from, prefix);
|
||||
}
|
||||
|
||||
private void initializeFromCIDR(byte[] from, int prefix)
|
||||
{
|
||||
if (from.length != 4 && from.length != 16)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid address");
|
||||
}
|
||||
if (prefix < 0 || prefix > from.length * 8)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid prefix");
|
||||
}
|
||||
byte[] to = from.clone();
|
||||
byte mask = (byte)(0xff << (8 - prefix % 8));
|
||||
int i = prefix / 8;
|
||||
|
||||
if (i < from.length)
|
||||
{
|
||||
from[i] = (byte)(from[i] & mask);
|
||||
to[i] = (byte)(to[i] | ~mask);
|
||||
Arrays.fill(from, i+1, from.length, (byte)0);
|
||||
Arrays.fill(to, i+1, to.length, (byte)0xff);
|
||||
}
|
||||
mFrom = from;
|
||||
mTo = to;
|
||||
mPrefix = prefix;
|
||||
}
|
||||
|
||||
public IPRange(String cidr) throws UnknownHostException
|
||||
{
|
||||
/* only verify the basic structure */
|
||||
if (!cidr.matches("(?i)^(([0-9.]+)|([0-9a-f:]+))(-(([0-9.]+)|([0-9a-f:]+))|(/\\d+))?$"))
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid CIDR or range notation");
|
||||
}
|
||||
if (cidr.contains("-"))
|
||||
{
|
||||
String[] parts = cidr.split("-");
|
||||
InetAddress from = InetAddress.getByName(parts[0]);
|
||||
InetAddress to = InetAddress.getByName(parts[1]);
|
||||
initializeFromRange(from, to);
|
||||
}
|
||||
else
|
||||
{
|
||||
String[] parts = cidr.split("/");
|
||||
InetAddress addr = InetAddress.getByName(parts[0]);
|
||||
byte[] base = addr.getAddress();
|
||||
int prefix = base.length * 8;
|
||||
if (parts.length > 1)
|
||||
{
|
||||
prefix = Integer.parseInt(parts[1]);
|
||||
}
|
||||
initializeFromCIDR(base, prefix);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first address of the range. The network ID in case this is a proper subnet.
|
||||
*/
|
||||
public InetAddress getFrom()
|
||||
{
|
||||
try
|
||||
{
|
||||
return InetAddress.getByAddress(mFrom);
|
||||
}
|
||||
catch (UnknownHostException ignored)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last address of the range.
|
||||
*/
|
||||
public InetAddress getTo()
|
||||
{
|
||||
try
|
||||
{
|
||||
return InetAddress.getByAddress(mTo);
|
||||
}
|
||||
catch (UnknownHostException ignored)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If this range is a proper subnet returns its prefix, otherwise returns null.
|
||||
*/
|
||||
public Integer getPrefix()
|
||||
{
|
||||
return mPrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NonNull IPRange other)
|
||||
{
|
||||
int cmp = compareAddr(mFrom, other.mFrom);
|
||||
if (cmp == 0)
|
||||
{ /* smaller ranges first */
|
||||
cmp = compareAddr(mTo, other.mTo);
|
||||
}
|
||||
return cmp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (o == null || !(o instanceof IPRange))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return this == o || compareTo((IPRange)o) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (mPrefix != null)
|
||||
{
|
||||
return InetAddress.getByAddress(mFrom).getHostAddress() + "/" + mPrefix;
|
||||
}
|
||||
return InetAddress.getByAddress(mFrom).getHostAddress() + "-" +
|
||||
InetAddress.getByAddress(mTo).getHostAddress();
|
||||
}
|
||||
catch (UnknownHostException ignored)
|
||||
{
|
||||
return super.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private int compareAddr(byte a[], byte b[])
|
||||
{
|
||||
if (a.length != b.length)
|
||||
{
|
||||
return (a.length < b.length) ? -1 : 1;
|
||||
}
|
||||
for (int i = 0; i < a.length; i++)
|
||||
{
|
||||
if (a[i] != b[i])
|
||||
{
|
||||
if (((int)a[i] & 0xff) < ((int)b[i] & 0xff))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this range fully contains the given range.
|
||||
*/
|
||||
public boolean contains(IPRange range)
|
||||
{
|
||||
return compareAddr(mFrom, range.mFrom) <= 0 && compareAddr(range.mTo, mTo) <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this and the given range overlap.
|
||||
*/
|
||||
public boolean overlaps(IPRange range)
|
||||
{
|
||||
return !(compareAddr(mTo, range.mFrom) < 0 || compareAddr(range.mTo, mFrom) < 0);
|
||||
}
|
||||
|
||||
private byte[] dec(byte[] addr)
|
||||
{
|
||||
for (int i = addr.length - 1; i >= 0; i--)
|
||||
{
|
||||
if (--addr[i] != (byte)0xff)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
|
||||
private byte[] inc(byte[] addr)
|
||||
{
|
||||
for (int i = addr.length - 1; i >= 0; i--)
|
||||
{
|
||||
if (++addr[i] != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given range from the current range. Returns a list of resulting ranges (these are
|
||||
* not proper subnets). At most two ranges are returned, in case the given range is contained in
|
||||
* this but does not equal it, which would result in an empty list (which is also the case if
|
||||
* this range is fully contained in the given range).
|
||||
*/
|
||||
public List<IPRange> remove(IPRange range)
|
||||
{
|
||||
ArrayList<IPRange> list = new ArrayList<>();
|
||||
if (!overlaps(range))
|
||||
{ /* | this | or | this |
|
||||
* | range | | range | */
|
||||
list.add(this);
|
||||
}
|
||||
else if (!range.contains(this))
|
||||
{ /* we are not completely removed, so none of these cases applies:
|
||||
* | this | or | this | or | this |
|
||||
* | range | | range | | range | */
|
||||
if (compareAddr(mFrom, range.mFrom) < 0 && compareAddr(range.mTo, mTo) < 0)
|
||||
{ /* the removed range is completely within our boundaries:
|
||||
* | this |
|
||||
* | range | */
|
||||
list.add(new IPRange(mFrom, dec(range.mFrom.clone())));
|
||||
list.add(new IPRange(inc(range.mTo.clone()), mTo));
|
||||
}
|
||||
else
|
||||
{ /* one end is within our boundaries the other at or outside it:
|
||||
* | this | or | this | or | this | or | this |
|
||||
* | range | | range | | range | | range | */
|
||||
byte[] from = compareAddr(mFrom, range.mFrom) < 0 ? mFrom : inc(range.mTo.clone());
|
||||
byte[] to = compareAddr(mTo, range.mTo) > 0 ? mTo : dec(range.mFrom.clone());
|
||||
list.add(new IPRange(from, to));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private boolean adjacent(IPRange range)
|
||||
{
|
||||
if (compareAddr(mTo, range.mFrom) < 0)
|
||||
{
|
||||
byte[] to = inc(mTo.clone());
|
||||
return compareAddr(to, range.mFrom) == 0;
|
||||
}
|
||||
byte[] from = dec(mFrom.clone());
|
||||
return compareAddr(from, range.mTo) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge two adjacent or overlapping ranges, returns null if it's not possible to merge them.
|
||||
*/
|
||||
public IPRange merge(IPRange range)
|
||||
{
|
||||
if (overlaps(range))
|
||||
{
|
||||
if (contains(range))
|
||||
{
|
||||
return this;
|
||||
}
|
||||
else if (range.contains(this))
|
||||
{
|
||||
return range;
|
||||
}
|
||||
}
|
||||
else if (!adjacent(range))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
byte[] from = compareAddr(mFrom, range.mFrom) < 0 ? mFrom : range.mFrom;
|
||||
byte[] to = compareAddr(mTo, range.mTo) > 0 ? mTo : range.mTo;
|
||||
return new IPRange(from, to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Split the given range into a sorted list of proper subnets.
|
||||
*/
|
||||
public List<IPRange> toSubnets()
|
||||
{
|
||||
ArrayList<IPRange> list = new ArrayList<>();
|
||||
if (mPrefix != null)
|
||||
{
|
||||
list.add(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
int i = 0, bit = 0, prefix, netmask, common_byte, common_bit;
|
||||
int from_cur, from_prev = 0, to_cur, to_prev = 1;
|
||||
boolean from_full = true, to_full = true;
|
||||
|
||||
byte[] from = mFrom.clone();
|
||||
byte[] to = mTo.clone();
|
||||
|
||||
/* find a common prefix */
|
||||
while (i < from.length && (from[i] & mBitmask[bit]) == (to[i] & mBitmask[bit]))
|
||||
{
|
||||
if (++bit == 8)
|
||||
{
|
||||
bit = 0;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
prefix = i * 8 + bit;
|
||||
|
||||
/* at this point we know that the addresses are either equal, or that the
|
||||
* current bits in the 'from' and 'to' addresses are 0 and 1, respectively.
|
||||
* we now look at the rest of the bits as two binary trees (0=left, 1=right)
|
||||
* where 'from' and 'to' are both leaf nodes. all leaf nodes between these
|
||||
* nodes are addresses contained in the range. to collect them as subnets
|
||||
* we follow the trees from both leaf nodes to their root node and record
|
||||
* all complete subtrees (right for from, left for to) we come across as
|
||||
* subnets. in that process host bits are zeroed out. if both addresses
|
||||
* are equal we won't enter the loop below.
|
||||
* 0_____|_____1 for the 'from' address we assume we start on a
|
||||
* 0__|__ 1 0__|__1 left subtree (0) and follow the left edges until
|
||||
* _|_ _|_ _|_ _|_ we reach the root of this subtree, which is
|
||||
* | | | | | | | | either the root of this whole 'from'-subtree
|
||||
* 0 1 0 1 0 1 0 1 (causing us to leave the loop) or the root node
|
||||
* of the right subtree (1) of another node (which actually could be the
|
||||
* leaf node we start from). that whole subtree gets recorded as subnet.
|
||||
* next we follow the right edges to the root of that subtree which again is
|
||||
* either the 'from'-root or the root node in the left subtree (0) of
|
||||
* another node. the complete right subtree of that node is the next subnet
|
||||
* we record. from there we assume that we are in that right subtree and
|
||||
* recursively follow right edges to its root. for the 'to' address the
|
||||
* procedure is exactly the same but with left and right reversed.
|
||||
*/
|
||||
if (++bit == 8)
|
||||
{
|
||||
bit = 0;
|
||||
i++;
|
||||
}
|
||||
common_byte = i;
|
||||
common_bit = bit;
|
||||
netmask = from.length * 8;
|
||||
for (i = from.length - 1; i >= common_byte; i--)
|
||||
{
|
||||
int bit_min = (i == common_byte) ? common_bit : 0;
|
||||
for (bit = 7; bit >= bit_min; bit--)
|
||||
{
|
||||
byte mask = mBitmask[bit];
|
||||
|
||||
from_cur = from[i] & mask;
|
||||
if (from_prev == 0 && from_cur != 0)
|
||||
{ /* 0 -> 1: subnet is the whole current (right) subtree */
|
||||
list.add(new IPRange(from.clone(), netmask));
|
||||
from_full = false;
|
||||
}
|
||||
else if (from_prev != 0 && from_cur == 0)
|
||||
{ /* 1 -> 0: invert bit to switch to right subtree and add it */
|
||||
from[i] ^= mask;
|
||||
list.add(new IPRange(from.clone(), netmask));
|
||||
from_cur = 1;
|
||||
}
|
||||
/* clear the current bit */
|
||||
from[i] &= ~mask;
|
||||
from_prev = from_cur;
|
||||
|
||||
to_cur = to[i] & mask;
|
||||
if (to_prev != 0 && to_cur == 0)
|
||||
{ /* 1 -> 0: subnet is the whole current (left) subtree */
|
||||
list.add(new IPRange(to.clone(), netmask));
|
||||
to_full = false;
|
||||
}
|
||||
else if (to_prev == 0 && to_cur != 0)
|
||||
{ /* 0 -> 1: invert bit to switch to left subtree and add it */
|
||||
to[i] ^= mask;
|
||||
list.add(new IPRange(to.clone(), netmask));
|
||||
to_cur = 0;
|
||||
}
|
||||
/* clear the current bit */
|
||||
to[i] &= ~mask;
|
||||
to_prev = to_cur;
|
||||
netmask--;
|
||||
}
|
||||
}
|
||||
|
||||
if (from_full && to_full)
|
||||
{ /* full subnet (from=to or from=0.. and to=1.. after common prefix) - not reachable
|
||||
* due to the shortcut at the top */
|
||||
list.add(new IPRange(from.clone(), prefix));
|
||||
}
|
||||
else if (from_full)
|
||||
{ /* full from subnet (from=0.. after prefix) */
|
||||
list.add(new IPRange(from.clone(), prefix + 1));
|
||||
}
|
||||
else if (to_full)
|
||||
{ /* full to subnet (to=1.. after prefix) */
|
||||
list.add(new IPRange(to.clone(), prefix + 1));
|
||||
}
|
||||
}
|
||||
Collections.sort(list);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 Tobias Brunner
|
||||
*
|
||||
* Copyright (C) secunet Security Networks AG
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
package org.amnezia.vpn.ikev2.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* Class that represents a set of IP address ranges (not necessarily proper subnets) and allows
|
||||
* modifying the set and enumerating the resulting subnets.
|
||||
*/
|
||||
public class IPRangeSet implements Iterable<IPRange>
|
||||
{
|
||||
private TreeSet<IPRange> mRanges = new TreeSet<>();
|
||||
|
||||
/**
|
||||
* Parse the given string (space separated ranges in CIDR or range notation) and return the
|
||||
* resulting set or {@code null} if the string was invalid. An empty set is returned if the given string
|
||||
* is {@code null}.
|
||||
*/
|
||||
public static IPRangeSet fromString(String ranges)
|
||||
{
|
||||
IPRangeSet set = new IPRangeSet();
|
||||
if (ranges != null)
|
||||
{
|
||||
for (String range : ranges.split("\\s+"))
|
||||
{
|
||||
try
|
||||
{
|
||||
set.add(new IPRange(range));
|
||||
}
|
||||
catch (Exception unused)
|
||||
{ /* besides due to invalid strings exceptions might get thrown if the string
|
||||
* contains a hostname (NetworkOnMainThreadException) */
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a range to this set. Automatically gets merged with existing ranges.
|
||||
*/
|
||||
public void add(IPRange range)
|
||||
{
|
||||
if (mRanges.contains(range))
|
||||
{
|
||||
return;
|
||||
}
|
||||
reinsert:
|
||||
while (true)
|
||||
{
|
||||
Iterator<IPRange> iterator = mRanges.iterator();
|
||||
while (iterator.hasNext())
|
||||
{
|
||||
IPRange existing = iterator.next();
|
||||
IPRange replacement = existing.merge(range);
|
||||
if (replacement != null)
|
||||
{
|
||||
iterator.remove();
|
||||
range = replacement;
|
||||
continue reinsert;
|
||||
}
|
||||
}
|
||||
mRanges.add(range);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all ranges from the given set.
|
||||
*/
|
||||
public void add(IPRangeSet ranges)
|
||||
{
|
||||
if (ranges == this)
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (IPRange range : ranges.mRanges)
|
||||
{
|
||||
add(range);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all ranges from the given collection to this set.
|
||||
*/
|
||||
public void addAll(Collection<? extends IPRange> coll)
|
||||
{
|
||||
for (IPRange range : coll)
|
||||
{
|
||||
add(range);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given range from this set. Existing ranges are automatically adjusted.
|
||||
*/
|
||||
public void remove(IPRange range)
|
||||
{
|
||||
ArrayList <IPRange> additions = new ArrayList<>();
|
||||
Iterator<IPRange> iterator = mRanges.iterator();
|
||||
while (iterator.hasNext())
|
||||
{
|
||||
IPRange existing = iterator.next();
|
||||
List<IPRange> result = existing.remove(range);
|
||||
if (result.size() == 0)
|
||||
{
|
||||
iterator.remove();
|
||||
}
|
||||
else if (!result.get(0).equals(existing))
|
||||
{
|
||||
iterator.remove();
|
||||
additions.addAll(result);
|
||||
}
|
||||
}
|
||||
mRanges.addAll(additions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given ranges from ranges in this set.
|
||||
*/
|
||||
public void remove(IPRangeSet ranges)
|
||||
{
|
||||
if (ranges == this)
|
||||
{
|
||||
mRanges.clear();
|
||||
return;
|
||||
}
|
||||
for (IPRange range : ranges.mRanges)
|
||||
{
|
||||
remove(range);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the subnets derived from all the ranges in this set.
|
||||
*/
|
||||
public Iterable<IPRange> subnets()
|
||||
{
|
||||
return new Iterable<IPRange>()
|
||||
{
|
||||
@Override
|
||||
public Iterator<IPRange> iterator()
|
||||
{
|
||||
return new Iterator<IPRange>()
|
||||
{
|
||||
private Iterator<IPRange> mIterator = mRanges.iterator();
|
||||
private List<IPRange> mSubnets;
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return (mSubnets != null && mSubnets.size() > 0) || mIterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPRange next()
|
||||
{
|
||||
if (mSubnets == null || mSubnets.size() == 0)
|
||||
{
|
||||
IPRange range = mIterator.next();
|
||||
mSubnets = range.toSubnets();
|
||||
}
|
||||
return mSubnets.remove(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<IPRange> iterator()
|
||||
{
|
||||
return mRanges.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of ranges, not subnets.
|
||||
*/
|
||||
public int size()
|
||||
{
|
||||
return mRanges.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{ /* we could use TextUtils, but that causes the unit tests to fail */
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (IPRange range : mRanges)
|
||||
{
|
||||
if (sb.length() > 0)
|
||||
{
|
||||
sb.append(" ");
|
||||
}
|
||||
sb.append(range.toString());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Tobias Brunner
|
||||
*
|
||||
* Copyright (C) secunet Security Networks AG
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
package org.amnezia.vpn.ikev2.utils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
/**
|
||||
* Simple generator for data/files that may be parsed by libstrongswan's
|
||||
* settings_t class.
|
||||
*/
|
||||
public class SettingsWriter
|
||||
{
|
||||
/**
|
||||
* Top-level section
|
||||
*/
|
||||
private final SettingsSection mTop = new SettingsSection();
|
||||
|
||||
/**
|
||||
* Set a string value
|
||||
* @param key
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public SettingsWriter setValue(String key, String value)
|
||||
{
|
||||
Pattern pattern = Pattern.compile("[^#{}=\"\\n\\t ]+");
|
||||
if (key == null || !pattern.matcher(key).matches())
|
||||
{
|
||||
return this;
|
||||
}
|
||||
String[] keys = key.split("\\.");
|
||||
SettingsSection section = mTop;
|
||||
section = findOrCreateSection(Arrays.copyOfRange(keys, 0, keys.length-1));
|
||||
section.Settings.put(keys[keys.length-1], value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an integer value
|
||||
* @param key
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public SettingsWriter setValue(String key, Integer value)
|
||||
{
|
||||
return setValue(key, value == null ? null : value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a boolean value
|
||||
* @param key
|
||||
* @param value
|
||||
* @return the writer
|
||||
*/
|
||||
public SettingsWriter setValue(String key, Boolean value)
|
||||
{
|
||||
return setValue(key, value == null ? null : value ? "1" : "0");
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the settings to a string in the format understood by
|
||||
* libstrongswan's settings_t parser.
|
||||
* @return serialized settings
|
||||
*/
|
||||
public String serialize()
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
serializeSection(mTop, builder);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the settings in a section and recursively serialize sub-sections
|
||||
* @param section
|
||||
* @param builder
|
||||
*/
|
||||
private void serializeSection(SettingsSection section, StringBuilder builder)
|
||||
{
|
||||
for (Entry<String, String> setting : section.Settings.entrySet())
|
||||
{
|
||||
builder.append(setting.getKey()).append('=');
|
||||
if (setting.getValue() != null)
|
||||
{
|
||||
builder.append("\"").append(escapeValue(setting.getValue())).append("\"");
|
||||
}
|
||||
builder.append('\n');
|
||||
}
|
||||
|
||||
for (Entry<String, SettingsSection> subsection : section.Sections.entrySet())
|
||||
{
|
||||
builder.append(subsection.getKey()).append(" {\n");
|
||||
serializeSection(subsection.getValue(), builder);
|
||||
builder.append("}\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape value so it may be wrapped in "
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
private String escapeValue(String value)
|
||||
{
|
||||
return value.replace("\\", "\\\\").replace("\"", "\\\"");
|
||||
}
|
||||
|
||||
/**
|
||||
* Find or create the nested sections with the given names
|
||||
* @param sections list of section names
|
||||
* @return final section
|
||||
*/
|
||||
private SettingsSection findOrCreateSection(String[] sections)
|
||||
{
|
||||
SettingsSection section = mTop;
|
||||
for (String name : sections)
|
||||
{
|
||||
SettingsSection subsection = section.Sections.get(name);
|
||||
if (subsection == null)
|
||||
{
|
||||
subsection = new SettingsSection();
|
||||
section.Sections.put(name, subsection);
|
||||
}
|
||||
section = subsection;
|
||||
}
|
||||
return section;
|
||||
}
|
||||
|
||||
/**
|
||||
* A section containing sub-sections and settings.
|
||||
*/
|
||||
private class SettingsSection
|
||||
{
|
||||
/**
|
||||
* Assigned key/value pairs
|
||||
*/
|
||||
LinkedHashMap<String,String> Settings = new LinkedHashMap<String, String>();
|
||||
|
||||
/**
|
||||
* Assigned sub-sections
|
||||
*/
|
||||
LinkedHashMap<String,SettingsSection> Sections = new LinkedHashMap<String, SettingsWriter.SettingsSection>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright (C) 2017-2018 Tobias Brunner
|
||||
*
|
||||
* Copyright (C) secunet Security Networks AG
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
package org.amnezia.vpn.ikev2.utils;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
public class SimpleFetcher
|
||||
{
|
||||
private static ExecutorService mExecutor = Executors.newCachedThreadPool();
|
||||
private static Object mLock = new Object();
|
||||
private static ArrayList<Future> mFutures = new ArrayList<>();
|
||||
private static boolean mDisabled;
|
||||
|
||||
public static byte[] fetch(String uri, byte[] data, String contentType)
|
||||
{
|
||||
Future<byte[]> future;
|
||||
|
||||
synchronized (mLock)
|
||||
{
|
||||
if (mDisabled)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
future = mExecutor.submit(() -> {
|
||||
URL url = new URL(uri);
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setConnectTimeout(10000);
|
||||
conn.setReadTimeout(10000);
|
||||
conn.setRequestProperty("Connection", "close");
|
||||
try
|
||||
{
|
||||
if (contentType != null)
|
||||
{
|
||||
conn.setRequestProperty("Content-Type", contentType);
|
||||
}
|
||||
if (data != null)
|
||||
{
|
||||
conn.setDoOutput(true);
|
||||
conn.setFixedLengthStreamingMode(data.length);
|
||||
OutputStream out = new BufferedOutputStream(conn.getOutputStream());
|
||||
out.write(data);
|
||||
out.close();
|
||||
}
|
||||
return streamToArray(conn.getInputStream());
|
||||
}
|
||||
catch (SocketTimeoutException e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
conn.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
mFutures.add(future);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
/* this enforces a timeout as the ones set on HttpURLConnection might not work reliably */
|
||||
return future.get(10000, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
catch (InterruptedException|ExecutionException|TimeoutException|CancellationException e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
synchronized (mLock)
|
||||
{
|
||||
mFutures.remove(future);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable fetching after it has been disabled.
|
||||
*/
|
||||
public static void enable()
|
||||
{
|
||||
synchronized (mLock)
|
||||
{
|
||||
mDisabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the fetcher and abort any future requests.
|
||||
*
|
||||
* The native thread is not cancelable as it is working on an IKE_SA (canceling the methods of
|
||||
* HttpURLConnection is not reliably possible anyway), so to abort while fetching we cancel the
|
||||
* Future (causing a return from fetch() immediately) and let the executor thread continue its
|
||||
* thing in the background.
|
||||
*
|
||||
* Also prevents future fetches until enabled again (e.g. if we aborted OCSP but would then
|
||||
* block in the subsequent fetch for a CRL).
|
||||
*/
|
||||
public static void disable()
|
||||
{
|
||||
synchronized (mLock)
|
||||
{
|
||||
mDisabled = true;
|
||||
for (Future future : mFutures)
|
||||
{
|
||||
future.cancel(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] streamToArray(InputStream in) throws IOException
|
||||
{
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
byte[] buf = new byte[1024];
|
||||
int len;
|
||||
|
||||
try
|
||||
{
|
||||
while ((len = in.read(buf)) != -1)
|
||||
{
|
||||
out.write(buf, 0, len);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
finally
|
||||
{
|
||||
in.close();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2014-2019 Tobias Brunner
|
||||
*
|
||||
* Copyright (C) secunet Security Networks AG
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
package org.amnezia.vpn.ikev2.utils;
|
||||
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
public class Utils
|
||||
{
|
||||
static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();
|
||||
|
||||
/**
|
||||
* Converts the given byte array to a hexadecimal string encoding.
|
||||
*
|
||||
* @param bytes byte array to convert
|
||||
* @return hex string
|
||||
*/
|
||||
public static String bytesToHex(byte[] bytes)
|
||||
{
|
||||
char[] hex = new char[bytes.length * 2];
|
||||
for (int i = 0; i < bytes.length; i++)
|
||||
{
|
||||
int value = bytes[i];
|
||||
hex[i*2] = HEXDIGITS[(value & 0xf0) >> 4];
|
||||
hex[i*2+1] = HEXDIGITS[ value & 0x0f];
|
||||
}
|
||||
return new String(hex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the given proposal string
|
||||
*
|
||||
* @param ike true for IKE, false for ESP
|
||||
* @param proposal proposal string
|
||||
* @return true if valid
|
||||
*/
|
||||
public native static boolean isProposalValid(boolean ike, String proposal);
|
||||
|
||||
/**
|
||||
* Parse an IP address without doing a name lookup
|
||||
*
|
||||
* @param address IP address string
|
||||
* @return address bytes if valid
|
||||
*/
|
||||
private native static byte[] parseInetAddressBytes(String address);
|
||||
|
||||
/**
|
||||
* Parse an IP address without doing a name lookup (as compared to InetAddress.fromName())
|
||||
*
|
||||
* @param address IP address string
|
||||
* @return address if valid
|
||||
* @throws UnknownHostException if address is invalid
|
||||
*/
|
||||
public static InetAddress parseInetAddress(String address) throws UnknownHostException
|
||||
{
|
||||
byte[] bytes = parseInetAddressBytes(address);
|
||||
if (bytes == null)
|
||||
{
|
||||
throw new UnknownHostException();
|
||||
}
|
||||
return InetAddress.getByAddress(bytes);
|
||||
}
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.amnezia.vpn.qt;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.Manifest.permission;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
// Gets used by /platforms/android/androidAppListProvider.cpp
|
||||
public class PackageManagerHelper {
|
||||
final static String TAG = "PackageManagerHelper";
|
||||
final static int MIN_CHROME_VERSION = 65;
|
||||
|
||||
final static List<String> CHROME_BROWSERS = Arrays.asList(
|
||||
new String[] {"com.google.android.webview", "com.android.webview", "com.google.chrome"});
|
||||
|
||||
private static String getAllAppNames(Context ctx) {
|
||||
JSONObject output = new JSONObject();
|
||||
PackageManager pm = ctx.getPackageManager();
|
||||
List<String> browsers = getBrowserIDs(pm);
|
||||
List<PackageInfo> packs = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS);
|
||||
for (int i = 0; i < packs.size(); i++) {
|
||||
PackageInfo p = packs.get(i);
|
||||
// Do not add ourselves and System Apps to the list, unless it might be a browser
|
||||
if ((!isSystemPackage(p,pm) || browsers.contains(p.packageName))
|
||||
&& !isSelf(p)) {
|
||||
String appid = p.packageName;
|
||||
String appName = p.applicationInfo.loadLabel(pm).toString();
|
||||
try {
|
||||
output.put(appid, appName);
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
private static Drawable getAppIcon(Context ctx, String id) {
|
||||
try {
|
||||
return ctx.getPackageManager().getApplicationIcon(id);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return new ColorDrawable(Color.TRANSPARENT);
|
||||
}
|
||||
|
||||
private static boolean isSystemPackage(PackageInfo pkgInfo, PackageManager pm) {
|
||||
if( (pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0){
|
||||
// no system app
|
||||
return false;
|
||||
}
|
||||
// For Systems Packages there are Cases where we want to add it anyway:
|
||||
// Has the use Internet permission (otherwise makes no sense)
|
||||
// Had at least 1 update (this means it's probably on any AppStore)
|
||||
// Has a a launch activity (has a ui and is not just a system service)
|
||||
|
||||
if(!usesInternet(pkgInfo)){
|
||||
return true;
|
||||
}
|
||||
if(!hadUpdate(pkgInfo)){
|
||||
return true;
|
||||
}
|
||||
if(pm.getLaunchIntentForPackage(pkgInfo.packageName) == null){
|
||||
// If there is no way to launch this from a homescreen, def a sys package
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private static boolean isSelf(PackageInfo pkgInfo) {
|
||||
return pkgInfo.packageName.equals("org.amnezia.vpn")
|
||||
|| pkgInfo.packageName.equals("org.amnezia.vpn.debug");
|
||||
}
|
||||
private static boolean usesInternet(PackageInfo pkgInfo){
|
||||
if(pkgInfo.requestedPermissions == null){
|
||||
return false;
|
||||
}
|
||||
for(int i=0; i < pkgInfo.requestedPermissions.length; i++) {
|
||||
String permission = pkgInfo.requestedPermissions[i];
|
||||
if(Manifest.permission.INTERNET.equals(permission)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private static boolean hadUpdate(PackageInfo pkgInfo){
|
||||
return pkgInfo.lastUpdateTime > pkgInfo.firstInstallTime;
|
||||
}
|
||||
|
||||
// Returns List of all Packages that can classify themselves as browsers
|
||||
private static List<String> getBrowserIDs(PackageManager pm) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.mozilla.org/"));
|
||||
intent.addCategory(Intent.CATEGORY_BROWSABLE);
|
||||
// We've tried using PackageManager.MATCH_DEFAULT_ONLY flag and found that browsers that
|
||||
// are not set as the default browser won't be matched even if they had CATEGORY_DEFAULT set
|
||||
// in the intent filter
|
||||
|
||||
List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, PackageManager.MATCH_ALL);
|
||||
List<String> browsers = new ArrayList<String>();
|
||||
for (int i = 0; i < resolveInfos.size(); i++) {
|
||||
ResolveInfo info = resolveInfos.get(i);
|
||||
String browserID = info.activityInfo.packageName;
|
||||
browsers.add(browserID);
|
||||
}
|
||||
return browsers;
|
||||
}
|
||||
|
||||
// Gets called in AndroidAuthenticationListener;
|
||||
public static boolean isWebViewSupported(Context ctx) {
|
||||
Log.v(TAG, "Checking if installed Webview is compatible with FxA");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
// The default Webview is able do to FXA
|
||||
return true;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
PackageInfo pi = WebView.getCurrentWebViewPackage();
|
||||
if (CHROME_BROWSERS.contains(pi.packageName)) {
|
||||
return isSupportedChromeBrowser(pi);
|
||||
}
|
||||
return isNotAncientBrowser(pi);
|
||||
}
|
||||
|
||||
// Before O the webview is hardcoded, but we dont know which package it is.
|
||||
// Check if com.google.android.webview is installed
|
||||
PackageManager pm = ctx.getPackageManager();
|
||||
try {
|
||||
PackageInfo pi = pm.getPackageInfo("com.google.android.webview", 0);
|
||||
return isSupportedChromeBrowser(pi);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
}
|
||||
// Otherwise check com.android.webview
|
||||
try {
|
||||
PackageInfo pi = pm.getPackageInfo("com.android.webview", 0);
|
||||
return isSupportedChromeBrowser(pi);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
}
|
||||
Log.e(TAG, "Android System WebView is not found");
|
||||
// Giving up :(
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isSupportedChromeBrowser(PackageInfo pi) {
|
||||
Log.d(TAG, "Checking Chrome Based Browser: " + pi.packageName);
|
||||
Log.d(TAG, "version name: " + pi.versionName);
|
||||
Log.d(TAG, "version code: " + pi.versionCode);
|
||||
try {
|
||||
String versionCode = pi.versionName.split(Pattern.quote(" "))[0];
|
||||
String majorVersion = versionCode.split(Pattern.quote("."))[0];
|
||||
int version = Integer.parseInt(majorVersion);
|
||||
return version >= MIN_CHROME_VERSION;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to check Chrome Version Code " + pi.versionName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isNotAncientBrowser(PackageInfo pi) {
|
||||
// Not a google chrome - So the version name is worthless
|
||||
// Lets just make sure the WebView
|
||||
// used is not ancient ==> Was updated in at least the last 365 days
|
||||
Log.d(TAG, "Checking Chrome Based Browser: " + pi.packageName);
|
||||
Log.d(TAG, "version name: " + pi.versionName);
|
||||
Log.d(TAG, "version code: " + pi.versionCode);
|
||||
double oneYearInMillis = 31536000000L;
|
||||
return pi.lastUpdateTime > (System.currentTimeMillis() - oneYearInMillis);
|
||||
}
|
||||
}
|
||||
@@ -114,7 +114,7 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() {
|
||||
// QT will always attempt to read the clipboard if content is there.
|
||||
// since we have no use of the clipboard in android 10+
|
||||
// we _can_ return null
|
||||
// And we defnitly should since android 12 displays clipboard access.
|
||||
// And we definitely should since android 12 displays clipboard access.
|
||||
null
|
||||
} else {
|
||||
super.getSystemService(name)
|
||||
@@ -259,13 +259,13 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() {
|
||||
private val ACTION_REGISTER_LISTENER = 3
|
||||
private val ACTION_RESUME_ACTIVATE = 7
|
||||
private val ACTION_IMPORT_CONFIG = 11
|
||||
private val EVENT_PERMISSION_REQURED = 6
|
||||
private val EVENT_PERMISSION_REQUIRED = 6
|
||||
private val EVENT_DISCONNECTED = 2
|
||||
|
||||
private val UI_EVENT_QR_CODE_RECEIVED = 0
|
||||
|
||||
fun onPermissionRequest(code: Int, data: Parcel?) {
|
||||
if (code != EVENT_PERMISSION_REQURED) {
|
||||
if (code != EVENT_PERMISSION_REQUIRED) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+13
-10
@@ -12,16 +12,16 @@ set(LIBS ${LIBS} SortFilterProxyModel)
|
||||
include(${CLIENT_ROOT_DIR}/3rd/qrcodegen/qrcodegen.cmake)
|
||||
include(${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/QSimpleCrypto.cmake)
|
||||
|
||||
set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE)
|
||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/zlib)
|
||||
if(WIN32)
|
||||
set(ZLIB_LIBRARY $<IF:$<CONFIG:Debug>,zlibd,zlib>)
|
||||
else()
|
||||
set(ZLIB_LIBRARY z)
|
||||
endif()
|
||||
set(ZLIB_INCLUDE_DIR "${CLIENT_ROOT_DIR}/3rd/zlib" "${CMAKE_CURRENT_BINARY_DIR}/3rd/zlib")
|
||||
link_directories(${CMAKE_CURRENT_BINARY_DIR}/3rd/zlib)
|
||||
link_libraries(${ZLIB_LIBRARY})
|
||||
#set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE)
|
||||
#add_subdirectory(${CLIENT_ROOT_DIR}/3rd/zlib)
|
||||
#if(WIN32)
|
||||
# set(ZLIB_LIBRARY $<IF:$<CONFIG:Debug>,zlibd,zlib>)
|
||||
#else()
|
||||
# set(ZLIB_LIBRARY z)
|
||||
#endif()
|
||||
#set(ZLIB_INCLUDE_DIR "${CLIENT_ROOT_DIR}/3rd/zlib" "${CMAKE_CURRENT_BINARY_DIR}/3rd/zlib")
|
||||
#link_directories(${CMAKE_CURRENT_BINARY_DIR}/3rd/zlib)
|
||||
#link_libraries(${ZLIB_LIBRARY})
|
||||
|
||||
if(IOS)
|
||||
set(ENABLE_PROGRAMS OFF CACHE BOOL "" FORCE)
|
||||
@@ -105,6 +105,9 @@ set(BUILD_WITH_QT6 ON)
|
||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain)
|
||||
set(LIBS ${LIBS} qt6keychain)
|
||||
|
||||
|
||||
include(${CLIENT_ROOT_DIR}/3rd/strongswan/strongswan.cmake)
|
||||
|
||||
include_directories(
|
||||
${CLIENT_ROOT_DIR}/3rd/OpenSSL/include
|
||||
${CLIENT_ROOT_DIR}/3rd/libssh/include
|
||||
|
||||
@@ -13,7 +13,7 @@ endif()
|
||||
if(CODE_SIGN_IDENTITY)
|
||||
find_program(CODESIGN_BIN NAMES codesign)
|
||||
if(NOT CODESIGN_BIN)
|
||||
messsage(FATAL_ERROR "Cannot sign code, could not find 'codesign' executable")
|
||||
message(FATAL_ERROR "Cannot sign code, could not find 'codesign' executable")
|
||||
endif()
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY ${CODE_SIGN_IDENTITY})
|
||||
endif()
|
||||
|
||||
@@ -30,7 +30,7 @@ public:
|
||||
QString &processConfigWithLocalSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
|
||||
QString &processConfigWithExportSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
|
||||
|
||||
// workaround for containers which is not support normal configaration
|
||||
// workaround for containers which is not support normal configuration
|
||||
void updateContainerConfigAfterInstallation(DockerContainer container,
|
||||
QJsonObject &containerConfig, const QString &stdOut);
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
||||
ErrorCode e = ErrorCode::NoError;
|
||||
ServerController serverController(m_settings);
|
||||
|
||||
// Get list of already created clients (only IP addreses)
|
||||
// Get list of already created clients (only IP addresses)
|
||||
QString nextIpNumber;
|
||||
{
|
||||
QString script = QString("cat %1 | grep AllowedIPs").arg(amnezia::protocols::wireguard::serverConfigPath);
|
||||
|
||||
@@ -170,6 +170,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
|
||||
case DockerContainer::WireGuard: return true;
|
||||
case DockerContainer::OpenVpn: return true;
|
||||
case DockerContainer::ShadowSocks: return true;
|
||||
case DockerContainer::Ipsec: return true;
|
||||
default: return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef CONTAIERNS_DEFS_H
|
||||
#define CONTAIERNS_DEFS_H
|
||||
#ifndef CONTAINERS_DEFS_H
|
||||
#define CONTAINERS_DEFS_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QQmlEngine>
|
||||
@@ -75,4 +75,4 @@ static void declareQmlContainerEnum() {
|
||||
|
||||
QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c);
|
||||
|
||||
#endif // CONTAIERNS_DEFS_H
|
||||
#endif // CONTAINERS_DEFS_H
|
||||
|
||||
@@ -104,8 +104,8 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
|
||||
pd->localSocket->connectToServer(amnezia::getIpcProcessUrl(pid));
|
||||
pd->localSocket->waitForConnected();
|
||||
|
||||
auto proccessReplica = QSharedPointer<PrivilegedProcess>(pd->ipcProcess);
|
||||
return proccessReplica;
|
||||
auto processReplica = QSharedPointer<PrivilegedProcess>(pd->ipcProcess);
|
||||
return processReplica;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -289,10 +289,10 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials,
|
||||
ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &oldConfig, QJsonObject &newConfig)
|
||||
{
|
||||
bool reinstallRequred = isReinstallContainerRequred(container, oldConfig, newConfig);
|
||||
qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" << reinstallRequred;
|
||||
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
|
||||
qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" << reinstallRequired;
|
||||
|
||||
if (reinstallRequred) {
|
||||
if (reinstallRequired) {
|
||||
return setupContainer(credentials, container, newConfig, true);
|
||||
}
|
||||
else {
|
||||
@@ -326,7 +326,7 @@ QJsonObject ServerController::createContainerInitialConfig(DockerContainer conta
|
||||
return config;
|
||||
}
|
||||
|
||||
bool ServerController::isReinstallContainerRequred(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig)
|
||||
bool ServerController::isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig)
|
||||
{
|
||||
Proto mainProto = ContainerProps::defaultProtocol(container);
|
||||
|
||||
@@ -655,6 +655,11 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
|
||||
script = script.append("|:%1").arg(port);
|
||||
}
|
||||
script = script.append("' | grep -i %1").arg(transportProto);
|
||||
|
||||
if (transportProto == "tcp") {
|
||||
script = script.append(" | grep LISTEN");
|
||||
}
|
||||
|
||||
ErrorCode errorCode = runScript(credentials,
|
||||
replaceVars(script, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
@@ -669,6 +674,10 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
|
||||
|
||||
ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, DockerContainer container)
|
||||
{
|
||||
if (credentials.userName == "root") {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data + "\n";
|
||||
|
||||
@@ -32,11 +32,11 @@ public:
|
||||
|
||||
// create initial config - generate passwords, etc
|
||||
QJsonObject createContainerInitialConfig(DockerContainer container, int port, TransportProto tp);
|
||||
ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
|
||||
|
||||
ErrorCode uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials,
|
||||
const QString &file, const QString &path,
|
||||
libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting);
|
||||
|
||||
QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials,
|
||||
const QString &path, ErrorCode *errorCode = nullptr);
|
||||
|
||||
@@ -62,10 +62,9 @@ private:
|
||||
ErrorCode buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
|
||||
ErrorCode runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config);
|
||||
ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config);
|
||||
ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
|
||||
|
||||
ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config);
|
||||
bool isReinstallContainerRequred(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig);
|
||||
bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig);
|
||||
ErrorCode isUserInSudo(const ServerCredentials &credentials, DockerContainer container);
|
||||
ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container);
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <fstream>
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
#define S_IRWXU 0
|
||||
const uint32_t S_IRWXU = 0644;
|
||||
#endif
|
||||
|
||||
namespace libssh {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Autogenerated by fastlane
|
||||
#
|
||||
# Ensure this file is checked in to source control!
|
||||
# Ensure this file is checked into source control!
|
||||
|
||||
#gem 'fastlane-plugin-run_tests_firebase_testlab'
|
||||
#gem 'fastlane-plugin-firebase_app_distribution'
|
||||
|
||||
+2
-2
@@ -157,7 +157,7 @@ void Logger::clearServiceLogs()
|
||||
|
||||
if (!m_IpcClient->isSocketConnected()) {
|
||||
if (!IpcClient::init(m_IpcClient)) {
|
||||
qWarning() << "Error occured when init IPC client";
|
||||
qWarning() << "Error occurred when init IPC client";
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -167,7 +167,7 @@ void Logger::clearServiceLogs()
|
||||
m_IpcClient->Interface()->cleanUp();
|
||||
}
|
||||
else {
|
||||
qWarning() << "Error occured cleaning up service logs";
|
||||
qWarning() << "Error occurred cleaning up service logs";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ void ManagementServer::onNewConnection()
|
||||
void ManagementServer::onSocketError(QAbstractSocket::SocketError socketError)
|
||||
{
|
||||
Q_UNUSED(socketError)
|
||||
qDebug().noquote() << QString("Mananement server error: %1").arg(m_socket->errorString());
|
||||
qDebug().noquote() << QString("Management server error: %1").arg(m_socket->errorString());
|
||||
}
|
||||
|
||||
void ManagementServer::onSocketDisconnected()
|
||||
|
||||
@@ -176,6 +176,21 @@ ErrorCode AndroidController::start()
|
||||
appContext.object());
|
||||
|
||||
QJsonDocument doc(m_vpnConfig);
|
||||
|
||||
/*** The following code snippet needs to correct displaying of config in debug console
|
||||
* (Android's stdout limits length of output message)
|
||||
*
|
||||
* QString string(doc.toJson(QJsonDocument::Compact));
|
||||
*
|
||||
* qDebug() << "*** config: ";
|
||||
* for (int i = 0; i <= string.length()/100; i++) {
|
||||
* int start = i*100;
|
||||
* qDebug() << string.mid(start, 100);
|
||||
* }
|
||||
*
|
||||
* qDebug() << "*** config: " << m_vpnConfig;
|
||||
***/
|
||||
|
||||
AndroidVPNActivity::sendToService(ServiceAction::ACTION_ACTIVATE, doc.toJson());
|
||||
|
||||
return NoError;
|
||||
@@ -297,7 +312,7 @@ void AndroidController::startActivityForResult(JNIEnv *env, jobject, jobject int
|
||||
[](int receiverRequestCode, int resultCode,
|
||||
const QJniObject& data) {
|
||||
// Currently this function just used in
|
||||
// VPNService.kt::checkPersmissions. So the result
|
||||
// VPNService.kt::checkPermissions. So the result
|
||||
// we're getting is if the User gave us the
|
||||
// Vpn.bind permission. In case of NO we should
|
||||
// abort.
|
||||
|
||||
@@ -17,7 +17,7 @@ enum ServiceAction {
|
||||
ACTION_ACTIVATE = 1,
|
||||
// Deactivate the vpn. Body is empty
|
||||
ACTION_DEACTIVATE = 2,
|
||||
// Register an IBinder to recieve events body is an Ibinder
|
||||
// Register an IBinder to receive events body is an Ibinder
|
||||
ACTION_REGISTERLISTENER = 3,
|
||||
// Requests an EVENT_STATISTIC_UPDATE to be send
|
||||
ACTION_REQUEST_STATISTIC = 4,
|
||||
@@ -40,14 +40,14 @@ typedef enum ServiceAction ServiceAction;
|
||||
|
||||
// Event Types that will be Dispatched after registration
|
||||
enum ServiceEvents {
|
||||
// The Service has Accecpted our Binder
|
||||
// The Service has Accepted our Binder
|
||||
// Responds with the current status of the vpn.
|
||||
EVENT_INIT = 0,
|
||||
// WG-Go has enabled the adapter (empty response)
|
||||
EVENT_CONNECTED = 1,
|
||||
// WG-Go has disabled the adapter (empty response)
|
||||
EVENT_DISCONNECTED = 2,
|
||||
// Contains the Current transfered bytes to endpoint x.
|
||||
// Contains the Current transferred bytes to endpoint x.
|
||||
EVENT_STATISTIC_UPDATE = 3,
|
||||
EVENT_BACKEND_LOGS = 4,
|
||||
// An Error happened during activation
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
|
||||
_screen = [UIScreen.mainScreen snapshotViewAfterScreenUpdates: false];
|
||||
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle: UIBlurEffectStyleDark];
|
||||
UIVisualEffectView *blurBackround = [[UIVisualEffectView alloc] initWithEffect: blurEffect];
|
||||
[_screen addSubview: blurBackround];
|
||||
blurBackround.frame = _screen.frame;
|
||||
UIVisualEffectView *blurBackground = [[UIVisualEffectView alloc] initWithEffect: blurEffect];
|
||||
[_screen addSubview: blurBackground];
|
||||
blurBackground.frame = _screen.frame;
|
||||
UIWindow *_window = UIApplication.sharedApplication.keyWindow;
|
||||
[_window addSubview: _screen];
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ Logger logger(LOG_IAP, "IOSIAPHandler");
|
||||
logger.debug() << "transaction deferred";
|
||||
break;
|
||||
default:
|
||||
logger.warning() << "transaction unknwon state";
|
||||
logger.warning() << "transaction unknown state";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,7 +428,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
let password = ssConfig[Constants.ssPasswordKey] as? String else {
|
||||
self.ssCompletion?(0, NSError(domain: Bundle.main.bundleIdentifier ?? "unknown",
|
||||
code: 100,
|
||||
userInfo: [NSLocalizedDescriptionKey: "Cannot asign profile params for ss in tunnel"]))
|
||||
userInfo: [NSLocalizedDescriptionKey: "Cannot assign profile params for ss in tunnel"]))
|
||||
return nil
|
||||
}
|
||||
var insettings: [String: Any] = .init()
|
||||
@@ -639,7 +639,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
wg_log(.error, message: "Start reading packets to connection")
|
||||
wg_log(.error, message: "Connection is \(session != nil ? "not null" : "null")")
|
||||
packetFlow.readPackets { [weak self] packets, protocols in
|
||||
wg_log(.error, message: "\(packets.count) outcoming packets proccessed of \(protocols.first?.stringValue ?? "unknown") type")
|
||||
wg_log(.error, message: "\(packets.count) outcoming packets processed of \(protocols.first?.stringValue ?? "unknown") type")
|
||||
guard let `self` = self else { return }
|
||||
self.session?.writeMultipleDatagrams(packets, completionHandler: { _ in
|
||||
self.processQueue.async {
|
||||
@@ -662,7 +662,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
session?.setReadHandler({ ssdata, error in
|
||||
wg_log(.error, message: "Packets are \(ssdata != nil ? "not null" : "null"), error: \(error?.localizedDescription ?? "none")")
|
||||
guard error == nil, let packets = ssdata else { return }
|
||||
wg_log(.error, message: "\(packets.count) incoming packets proccessed")
|
||||
wg_log(.error, message: "\(packets.count) incoming packets processed")
|
||||
self.packetFlow.writePackets(packets, withProtocols: [NSNumber(value: AF_INET)])
|
||||
}, maxDatagrams: Int.max)
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@ class TunProvider: NSObject {
|
||||
dispatchQueue.async {
|
||||
let success = leaf_shutdown(self.tunId)
|
||||
if !success {
|
||||
let errMsg = "Tunnel canot be stopped for some odd reason."
|
||||
let errMsg = "Tunnel cannot be stopped for some odd reason."
|
||||
self.stopCompletion?(.undefinedError(errMsg))
|
||||
}
|
||||
pthread_kill(self.tunThreadId!, SIGUSR1)
|
||||
|
||||
@@ -120,7 +120,7 @@ void MacosRouteMonitor::handleRtmChange(const struct rt_msghdr* rtm,
|
||||
for (auto addr : addrlist) {
|
||||
list.append(addrToString(addr));
|
||||
}
|
||||
logger.debug() << "Route chagned by" << rtm->rtm_pid
|
||||
logger.debug() << "Route changed by" << rtm->rtm_pid
|
||||
<< QString("addrs(%1):").arg(rtm->rtm_addrs) << list.join(" ");
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ void MacosRouteMonitor::handleIfaceInfo(const struct if_msghdr* ifm,
|
||||
list.append(addrToString(addr));
|
||||
}
|
||||
logger.debug() << "Interface " << ifm->ifm_index
|
||||
<< "chagned flags:" << ifm->ifm_flags
|
||||
<< "changed flags:" << ifm->ifm_flags
|
||||
<< QString("addrs(%1):").arg(ifm->ifm_addrs) << list.join(" ");
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ void Ikev2Protocol::stop()
|
||||
void Ikev2Protocol::newConnectionStateEventReceived(UINT unMsg, tagRASCONNSTATE rasconnstate, DWORD dwError)
|
||||
{
|
||||
Q_UNUSED(unMsg);
|
||||
qDebug()<<"Recive the new event "<<static_cast<int>(rasconnstate);
|
||||
qDebug()<<"Receive the new event "<<static_cast<int>(rasconnstate);
|
||||
switch (rasconnstate)
|
||||
{
|
||||
case RASCS_OpenPort:
|
||||
|
||||
@@ -54,7 +54,7 @@ private:
|
||||
private:
|
||||
QJsonObject m_config;
|
||||
|
||||
//RAS functions and parametrs
|
||||
//RAS functions and parameters
|
||||
HRASCONN hRasConn{nullptr};
|
||||
bool create_new_vpn(const QString & vpn_name,
|
||||
const QString & serv_addr);
|
||||
|
||||
@@ -36,7 +36,7 @@ public:
|
||||
void cleanupBackendLogs();
|
||||
|
||||
signals:
|
||||
void newTransmitedDataCount(quint64 rxBytes, quint64 txBytes);
|
||||
void newTransmittedDataCount(quint64 rxBytes, quint64 txBytes);
|
||||
|
||||
protected slots:
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ Proto currentProto = amnezia::Proto::Any;
|
||||
IOSVpnProtocol::IOSVpnProtocol(Proto proto, const QJsonObject &configuration, QObject* parent)
|
||||
: VpnProtocol(configuration, parent), m_protocol(proto)
|
||||
{
|
||||
connect(this, &IOSVpnProtocol::newTransmitedDataCount, this, &IOSVpnProtocol::setBytesChanged);
|
||||
connect(this, &IOSVpnProtocol::newTransmittedDataCount, this, &IOSVpnProtocol::setBytesChanged);
|
||||
}
|
||||
|
||||
IOSVpnProtocol* IOSVpnProtocol::instance() {
|
||||
@@ -209,7 +209,7 @@ void IOSVpnProtocol::checkStatus()
|
||||
qDebug() << "ServerIpv4Gateway:" << QString::fromNSString(serverIpv4Gateway)
|
||||
<< "DeviceIpv4Address:" << QString::fromNSString(deviceIpv4Address)
|
||||
<< "RxBytes:" << rxBytes << "TxBytes:" << txBytes;
|
||||
emit newTransmitedDataCount(rxBytes, txBytes);
|
||||
emit newTransmittedDataCount(rxBytes, txBytes);
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ constexpr char defaultSubnetAddress[] = "10.8.0.0";
|
||||
constexpr char defaultSubnetMask[] = "255.255.255.0";
|
||||
constexpr char defaultSubnetCidr[] = "24";
|
||||
|
||||
constexpr char serverConfigPath[] = "/opt/amnezia/openvpn/server.conf";
|
||||
constexpr char caCertPath[] = "/opt/amnezia/openvpn/pki/ca.crt";
|
||||
constexpr char clientCertPath[] = "/opt/amnezia/openvpn/pki/issued";
|
||||
constexpr char taKeyPath[] = "/opt/amnezia/openvpn/ta.key";
|
||||
|
||||
@@ -84,6 +84,10 @@
|
||||
<file>ui/qml/Pages/PageAbout.qml</file>
|
||||
<file>ui/qml/Pages/PageQrDecoderIos.qml</file>
|
||||
<file>ui/qml/Pages/PageViewConfig.qml</file>
|
||||
<file>ui/qml/Pages/PageClientManagement.qml</file>
|
||||
<file>ui/qml/Pages/ClientInfo/PageClientInfoBase.qml</file>
|
||||
<file>ui/qml/Pages/ClientInfo/PageClientInfoOpenVPN.qml</file>
|
||||
<file>ui/qml/Pages/ClientInfo/PageClientInfoWireGuard.qml</file>
|
||||
<file>ui/qml/Pages/Protocols/PageProtoCloak.qml</file>
|
||||
<file>ui/qml/Pages/Protocols/PageProtoOpenVPN.qml</file>
|
||||
<file>ui/qml/Pages/Protocols/PageProtoShadowSocks.qml</file>
|
||||
|
||||
@@ -334,14 +334,14 @@ class XCodeprojPatcher
|
||||
def setup_target_gobridge(platform)
|
||||
target_gobridge = legacy_target = @project.new(Xcodeproj::Project::PBXLegacyTarget)
|
||||
|
||||
bridge_platofrm = platform == 'ios' ? 'iOS' : 'macOS'
|
||||
bridge_platform = platform == 'ios' ? 'iOS' : 'macOS'
|
||||
|
||||
target_gobridge.build_working_directory = platform == 'ios' ? '3rd/wireguard-apple/Sources/WireGuardKitGo' : 'macos/gobridge'
|
||||
target_gobridge.build_tool_path = 'make'
|
||||
target_gobridge.pass_build_settings_in_environment = '1'
|
||||
target_gobridge.build_arguments_string = '$(ACTION)'
|
||||
target_gobridge.name = "WireGuardGoBridge<#{bridge_platofrm}>"
|
||||
target_gobridge.product_name = "WireGuardGoBridge<#{bridge_platofrm}>"
|
||||
target_gobridge.name = "WireGuardGoBridge<#{bridge_platform}>"
|
||||
target_gobridge.product_name = "WireGuardGoBridge<#{bridge_platform}>"
|
||||
|
||||
@project.targets << target_gobridge
|
||||
@target_extension.add_dependency target_gobridge
|
||||
|
||||
@@ -18,6 +18,7 @@ user nobody
|
||||
group nobody
|
||||
persist-key
|
||||
persist-tun
|
||||
crl-verify /opt/amnezia/openvpn/crl.pem
|
||||
status openvpn-status.log
|
||||
verb 1
|
||||
tls-server
|
||||
|
||||
@@ -21,5 +21,6 @@ cd /opt/amnezia/openvpn && easyrsa gen-dh; \
|
||||
cd /opt/amnezia/openvpn && cp pki/dh.pem /opt/amnezia/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req AmneziaReq nopass << EOF2 yes EOF2;\
|
||||
cd /opt/amnezia/openvpn && easyrsa sign-req server AmneziaReq << EOF3 yes EOF3;\
|
||||
cd /opt/amnezia/openvpn && openvpn --genkey --secret ta.key << EOF4;\
|
||||
cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn'
|
||||
|
||||
cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn;\
|
||||
cd /opt/amnezia/openvpn && easyrsa gen-crl;\
|
||||
cd /opt/amnezia/openvpn && cp pki/crl.pem /opt/amnezia/openvpn/crl.pem'
|
||||
|
||||
@@ -18,6 +18,7 @@ user nobody
|
||||
group nobody
|
||||
persist-key
|
||||
persist-tun
|
||||
crl-verify /opt/amnezia/openvpn/crl.pem
|
||||
status openvpn-status.log
|
||||
verb 1
|
||||
tls-server
|
||||
|
||||
@@ -21,4 +21,6 @@ cd /opt/amnezia/openvpn && easyrsa gen-dh; \
|
||||
cd /opt/amnezia/openvpn && cp pki/dh.pem /opt/amnezia/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req AmneziaReq nopass << EOF2 yes EOF2;\
|
||||
cd /opt/amnezia/openvpn && easyrsa sign-req server AmneziaReq << EOF3 yes EOF3;\
|
||||
cd /opt/amnezia/openvpn && openvpn --genkey --secret ta.key << EOF4;\
|
||||
cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn'
|
||||
cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn;\
|
||||
cd /opt/amnezia/openvpn && easyrsa gen-crl;\
|
||||
cd /opt/amnezia/openvpn && cp pki/crl.pem /opt/amnezia/openvpn/crl.pem'
|
||||
|
||||
@@ -18,6 +18,7 @@ user nobody
|
||||
group nobody
|
||||
persist-key
|
||||
persist-tun
|
||||
crl-verify /opt/amnezia/openvpn/crl.pem
|
||||
status openvpn-status.log
|
||||
verb 1
|
||||
tls-server
|
||||
|
||||
@@ -21,4 +21,6 @@ cd /opt/amnezia/openvpn && easyrsa gen-dh; \
|
||||
cd /opt/amnezia/openvpn && cp pki/dh.pem /opt/amnezia/openvpn && easyrsa build-ca nopass << EOF yes EOF && easyrsa gen-req AmneziaReq nopass << EOF2 yes EOF2;\
|
||||
cd /opt/amnezia/openvpn && easyrsa sign-req server AmneziaReq << EOF3 yes EOF3;\
|
||||
cd /opt/amnezia/openvpn && openvpn --genkey --secret ta.key << EOF4;\
|
||||
cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn'
|
||||
cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn;\
|
||||
cd /opt/amnezia/openvpn && easyrsa gen-crl;\
|
||||
cd /opt/amnezia/openvpn && cp pki/crl.pem /opt/amnezia/openvpn/crl.pem'
|
||||
|
||||
@@ -403,7 +403,7 @@ Please note, this protocol still does not support export connection profile to m
|
||||
<location filename="../ui/mainwindow.ui" line="1388"/>
|
||||
<source>Optional.
|
||||
|
||||
We recommend to enable VPN mode "For selected sites" and add blocked sites you need to visit manually. If you will choose this option, you will need add every bloked site you want to visit to the access list. You may switch between modes later.
|
||||
We recommend to enable VPN mode "For selected sites" and add blocked sites you need to visit manually. If you will choose this option, you will need add every blocked site you want to visit to the access list. You may switch between modes later.
|
||||
|
||||
Please note, you should add addresses to the list after VPN connection established. You may add any domain, URL or IP address, it will be resolved to IP address.</source>
|
||||
<translation>Опционально.
|
||||
@@ -582,7 +582,7 @@ OpenVPN over ShadowSocks</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/mainwindow.ui" line="4302"/>
|
||||
<source>Secondray DNS server</source>
|
||||
<source>Secondary DNS server</source>
|
||||
<translation>Вторичный DNS сервер</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -1111,7 +1111,7 @@ This code does not include server credentials.</source>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/mainwindow.cpp" line="1756"/>
|
||||
<source>VPN Protocol not choosen</source>
|
||||
<source>VPN Protocol not chosen</source>
|
||||
<translation>VPN протокол не выбран</translation>
|
||||
</message>
|
||||
<message>
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
#include "clientManagementModel.h"
|
||||
|
||||
#include <QJsonObject>
|
||||
|
||||
ClientManagementModel::ClientManagementModel(QObject *parent) : QAbstractListModel(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ClientManagementModel::clearData()
|
||||
{
|
||||
beginResetModel();
|
||||
m_content.clear();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void ClientManagementModel::setContent(const QVector<QVariant> &data)
|
||||
{
|
||||
beginResetModel();
|
||||
m_content = data;
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
QJsonObject ClientManagementModel::getContent(amnezia::Proto protocol)
|
||||
{
|
||||
QJsonObject clientsTable;
|
||||
for (const auto &item : m_content) {
|
||||
if (protocol == amnezia::Proto::OpenVpn) {
|
||||
clientsTable[item.toJsonObject()["openvpnCertId"].toString()] = item.toJsonObject();
|
||||
} else if (protocol == amnezia::Proto::WireGuard) {
|
||||
clientsTable[item.toJsonObject()["wireguardPublicKey"].toString()] = item.toJsonObject();
|
||||
}
|
||||
}
|
||||
return clientsTable;
|
||||
}
|
||||
|
||||
int ClientManagementModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return static_cast<int>(m_content.size());
|
||||
}
|
||||
|
||||
QVariant ClientManagementModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() < 0
|
||||
|| index.row() >= static_cast<int>(m_content.size())) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
if (role == NameRole) {
|
||||
return m_content[index.row()].toJsonObject()["clientName"].toString();
|
||||
} else if (role == OpenVpnCertIdRole) {
|
||||
return m_content[index.row()].toJsonObject()["openvpnCertId"].toString();
|
||||
} else if (role == OpenVpnCertDataRole) {
|
||||
return m_content[index.row()].toJsonObject()["openvpnCertData"].toString();
|
||||
} else if (role == WireGuardPublicKey) {
|
||||
return m_content[index.row()].toJsonObject()["wireguardPublicKey"].toString();
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void ClientManagementModel::setData(const QModelIndex &index, QVariant data, int role)
|
||||
{
|
||||
if (!index.isValid() || index.row() < 0
|
||||
|| index.row() >= static_cast<int>(m_content.size())) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto client = m_content[index.row()].toJsonObject();
|
||||
if (role == NameRole) {
|
||||
client["clientName"] = data.toString();
|
||||
} else if (role == OpenVpnCertIdRole) {
|
||||
client["openvpnCertId"] = data.toString();
|
||||
} else if (role == OpenVpnCertDataRole) {
|
||||
client["openvpnCertData"] = data.toString();
|
||||
} else if (role == WireGuardPublicKey) {
|
||||
client["wireguardPublicKey"] = data.toString();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if (m_content[index.row()] != client) {
|
||||
m_content[index.row()] = client;
|
||||
emit dataChanged(index, index);
|
||||
}
|
||||
}
|
||||
|
||||
bool ClientManagementModel::removeRows(int row)
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), row, row);
|
||||
m_content.removeAt(row);
|
||||
endRemoveRows();
|
||||
return true;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> ClientManagementModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[NameRole] = "clientName";
|
||||
roles[OpenVpnCertIdRole] = "openvpnCertId";
|
||||
roles[OpenVpnCertDataRole] = "openvpnCertData";
|
||||
roles[WireGuardPublicKey] = "wireguardPublicKey";
|
||||
return roles;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#ifndef CLIENTMANAGEMENTMODEL_H
|
||||
#define CLIENTMANAGEMENTMODEL_H
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include "protocols/protocols_defs.h"
|
||||
|
||||
class ClientManagementModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum ClientRoles {
|
||||
NameRole = Qt::UserRole + 1,
|
||||
OpenVpnCertIdRole,
|
||||
OpenVpnCertDataRole,
|
||||
WireGuardPublicKey,
|
||||
};
|
||||
|
||||
ClientManagementModel(QObject *parent = nullptr);
|
||||
|
||||
void clearData();
|
||||
void setContent(const QVector<QVariant> &data);
|
||||
QJsonObject getContent(amnezia::Proto protocol);
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
void setData(const QModelIndex &index, QVariant data, int role = Qt::DisplayRole);
|
||||
bool removeRows(int row);
|
||||
|
||||
protected:
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
QVector<QVariant> m_content;
|
||||
};
|
||||
|
||||
#endif // CLIENTMANAGEMENTMODEL_H
|
||||
@@ -93,7 +93,7 @@ void NotificationHandler::unsecuredNetworkNotification(const QString& networkNam
|
||||
|
||||
|
||||
QString title = tr("AmneziaVPN notification");
|
||||
QString message = tr("Unsucured network detected: ") + networkName;
|
||||
QString message = tr("Unsecured network detected: ") + networkName;
|
||||
|
||||
notifyInternal(UnsecuredNetwork, title, message, 2000);
|
||||
}
|
||||
|
||||
+4
-2
@@ -12,7 +12,8 @@ public:
|
||||
enum Type {
|
||||
Basic,
|
||||
Proto,
|
||||
ShareProto
|
||||
ShareProto,
|
||||
ClientInfo
|
||||
};
|
||||
Q_ENUM(Type)
|
||||
};
|
||||
@@ -24,7 +25,8 @@ enum class Page {Start = 0, NewServer, NewServerProtocols, Vpn,
|
||||
Wizard, WizardLow, WizardMedium, WizardHigh, WizardVpnMode, ServerConfiguringProgress,
|
||||
GeneralSettings, AppSettings, NetworkSettings, ServerSettings,
|
||||
ServerContainers, ServersList, ShareConnection, Sites,
|
||||
ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, AdvancedServerSettings};
|
||||
ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig,
|
||||
AdvancedServerSettings, ClientManagement, ClientInfo};
|
||||
Q_ENUM_NS(Page)
|
||||
|
||||
static void declareQmlPageEnum() {
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
#include "ClientInfoLogic.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "defines.h"
|
||||
#include "core/errorstrings.h"
|
||||
#include "core/servercontroller.h"
|
||||
#include "ui/models/clientManagementModel.h"
|
||||
#include "ui/uilogic.h"
|
||||
|
||||
namespace {
|
||||
bool isErrorOccured(ErrorCode error) {
|
||||
if (error != ErrorCode::NoError) {
|
||||
QMessageBox::warning(nullptr, APPLICATION_NAME,
|
||||
QObject::tr("An error occurred while saving the list of clients.") + "\n" + errorString(error));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ClientInfoLogic::ClientInfoLogic(UiLogic *logic, QObject *parent):
|
||||
PageLogicBase(logic, parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ClientInfoLogic::setCurrentClientId(int index)
|
||||
{
|
||||
m_currentClientIndex = index;
|
||||
}
|
||||
|
||||
void ClientInfoLogic::onUpdatePage()
|
||||
{
|
||||
set_pageContentVisible(false);
|
||||
set_busyIndicatorIsRunning(true);
|
||||
|
||||
const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex);
|
||||
const DockerContainer container = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex);
|
||||
const QString containerNameString = ContainerProps::containerHumanNames().value(container);
|
||||
set_labelCurrentVpnProtocolText(tr("Service: ") + containerNameString);
|
||||
|
||||
const QVector<amnezia::Proto> protocols = ContainerProps::protocolsForContainer(container);
|
||||
if (!protocols.empty()) {
|
||||
const Proto currentMainProtocol = protocols.front();
|
||||
|
||||
auto model = qobject_cast<ClientManagementModel*>(uiLogic()->clientManagementModel());
|
||||
const QModelIndex modelIndex = model->index(m_currentClientIndex);
|
||||
|
||||
set_lineEditNameAliasText(model->data(modelIndex, ClientManagementModel::ClientRoles::NameRole).toString());
|
||||
if (currentMainProtocol == Proto::OpenVpn) {
|
||||
const QString certId = model->data(modelIndex, ClientManagementModel::ClientRoles::OpenVpnCertIdRole).toString();
|
||||
QString certData = model->data(modelIndex, ClientManagementModel::ClientRoles::OpenVpnCertDataRole).toString();
|
||||
|
||||
if (certData.isEmpty() && !certId.isEmpty()) {
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data + "\n";
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
|
||||
const QString getOpenVpnCertData = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'cat /opt/amnezia/openvpn/pki/issued/%1.crt'")
|
||||
.arg(certId);
|
||||
ServerController serverController(m_settings);
|
||||
const QString script = serverController.replaceVars(getOpenVpnCertData, serverController.genVarsForScript(credentials, container));
|
||||
ErrorCode error = serverController.runScript(credentials, script, cbReadStdOut);
|
||||
certData = stdOut;
|
||||
if (isErrorOccured(error)) {
|
||||
set_busyIndicatorIsRunning(false);
|
||||
emit uiLogic()->closePage();
|
||||
return;
|
||||
}
|
||||
}
|
||||
set_labelOpenVpnCertId(certId);
|
||||
set_textAreaOpenVpnCertData(certData);
|
||||
} else if (currentMainProtocol == Proto::WireGuard) {
|
||||
set_textAreaWireGuardKeyData(model->data(modelIndex, ClientManagementModel::ClientRoles::WireGuardPublicKey).toString());
|
||||
}
|
||||
}
|
||||
set_pageContentVisible(true);
|
||||
set_busyIndicatorIsRunning(false);
|
||||
}
|
||||
|
||||
void ClientInfoLogic::onLineEditNameAliasEditingFinished()
|
||||
{
|
||||
set_busyIndicatorIsRunning(true);
|
||||
|
||||
auto model = qobject_cast<ClientManagementModel*>(uiLogic()->clientManagementModel());
|
||||
const QModelIndex modelIndex = model->index(m_currentClientIndex);
|
||||
model->setData(modelIndex, m_lineEditNameAliasText, ClientManagementModel::ClientRoles::NameRole);
|
||||
|
||||
const DockerContainer selectedContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex);
|
||||
const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex);
|
||||
const QVector<amnezia::Proto> protocols = ContainerProps::protocolsForContainer(selectedContainer);
|
||||
if (!protocols.empty()) {
|
||||
const Proto currentMainProtocol = protocols.front();
|
||||
const QJsonObject clientsTable = model->getContent(currentMainProtocol);
|
||||
ErrorCode error = setClientsList(credentials,
|
||||
selectedContainer,
|
||||
currentMainProtocol,
|
||||
clientsTable);
|
||||
isErrorOccured(error);
|
||||
}
|
||||
|
||||
set_busyIndicatorIsRunning(false);
|
||||
}
|
||||
|
||||
void ClientInfoLogic::onRevokeOpenVpnCertificateClicked()
|
||||
{
|
||||
set_busyIndicatorIsRunning(true);
|
||||
const DockerContainer container = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex);
|
||||
const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex);
|
||||
|
||||
auto model = qobject_cast<ClientManagementModel*>(uiLogic()->clientManagementModel());
|
||||
const QModelIndex modelIndex = model->index(m_currentClientIndex);
|
||||
const QString certId = model->data(modelIndex, ClientManagementModel::ClientRoles::OpenVpnCertIdRole).toString();
|
||||
|
||||
const QString getOpenVpnCertData = QString("sudo docker exec -i $CONTAINER_NAME bash -c '"
|
||||
"cd /opt/amnezia/openvpn ;\\"
|
||||
"easyrsa revoke %1 ;\\"
|
||||
"easyrsa gen-crl ;\\"
|
||||
"cp pki/crl.pem .'").arg(certId);
|
||||
ServerController serverController(m_settings);
|
||||
const QString script = serverController.replaceVars(getOpenVpnCertData,
|
||||
serverController.genVarsForScript(credentials, container));
|
||||
auto error = serverController.runScript(credentials, script);
|
||||
if (isErrorOccured(error)) {
|
||||
set_busyIndicatorIsRunning(false);
|
||||
emit uiLogic()->goToPage(Page::ServerSettings);
|
||||
return;
|
||||
}
|
||||
|
||||
model->removeRows(m_currentClientIndex);
|
||||
const QJsonObject clientsTable = model->getContent(Proto::OpenVpn);
|
||||
error = setClientsList(credentials, container, Proto::OpenVpn, clientsTable);
|
||||
if (isErrorOccured(error)) {
|
||||
set_busyIndicatorIsRunning(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonObject &containerConfig = m_settings->containerConfig(uiLogic()->m_selectedServerIndex, container);
|
||||
error = serverController.startupContainerWorker(credentials, container, containerConfig);
|
||||
if (isErrorOccured(error)) {
|
||||
set_busyIndicatorIsRunning(false);
|
||||
return;
|
||||
}
|
||||
|
||||
set_busyIndicatorIsRunning(false);
|
||||
}
|
||||
|
||||
void ClientInfoLogic::onRevokeWireGuardKeyClicked()
|
||||
{
|
||||
set_busyIndicatorIsRunning(true);
|
||||
ErrorCode error;
|
||||
const DockerContainer container = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex);
|
||||
const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex);
|
||||
|
||||
ServerController serverController(m_settings);
|
||||
|
||||
const QString wireGuardConfigFile = "opt/amnezia/wireguard/wg0.conf";
|
||||
const QString wireguardConfigString = serverController.getTextFileFromContainer(container, credentials, wireGuardConfigFile, &error);
|
||||
if (isErrorOccured(error)) {
|
||||
set_busyIndicatorIsRunning(false);
|
||||
return;
|
||||
}
|
||||
|
||||
auto model = qobject_cast<ClientManagementModel*>(uiLogic()->clientManagementModel());
|
||||
const QModelIndex modelIndex = model->index(m_currentClientIndex);
|
||||
const QString key = model->data(modelIndex, ClientManagementModel::ClientRoles::WireGuardPublicKey).toString();
|
||||
|
||||
auto configSections = wireguardConfigString.split("[", Qt::SkipEmptyParts);
|
||||
for (auto §ion : configSections) {
|
||||
if (section.contains(key)) {
|
||||
configSections.removeOne(section);
|
||||
}
|
||||
}
|
||||
QString newWireGuardConfig = configSections.join("[");
|
||||
newWireGuardConfig.insert(0, "[");
|
||||
error = serverController.uploadTextFileToContainer(container, credentials, newWireGuardConfig,
|
||||
protocols::wireguard::serverConfigPath,
|
||||
libssh::SftpOverwriteMode::SftpOverwriteExisting);
|
||||
if (isErrorOccured(error)) {
|
||||
set_busyIndicatorIsRunning(false);
|
||||
return;
|
||||
}
|
||||
|
||||
model->removeRows(m_currentClientIndex);
|
||||
const QJsonObject clientsTable = model->getContent(Proto::WireGuard);
|
||||
error = setClientsList(credentials, container, Proto::WireGuard, clientsTable);
|
||||
if (isErrorOccured(error)) {
|
||||
set_busyIndicatorIsRunning(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const QString script = "sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip /opt/amnezia/wireguard/wg0.conf)'";
|
||||
error = serverController.runScript(credentials,
|
||||
serverController.replaceVars(script, serverController.genVarsForScript(credentials, container)));
|
||||
if (isErrorOccured(error)) {
|
||||
set_busyIndicatorIsRunning(false);
|
||||
return;
|
||||
}
|
||||
|
||||
set_busyIndicatorIsRunning(false);
|
||||
}
|
||||
|
||||
ErrorCode ClientInfoLogic::setClientsList(const ServerCredentials &credentials, DockerContainer container, Proto mainProtocol, const QJsonObject &clietns)
|
||||
{
|
||||
const QString mainProtocolString = ProtocolProps::protoToString(mainProtocol);
|
||||
const QString clientsTableFile = QString("opt/amnezia/%1/clientsTable").arg(mainProtocolString);
|
||||
ServerController serverController(m_settings);
|
||||
ErrorCode error = serverController.uploadTextFileToContainer(container, credentials, QJsonDocument(clietns).toJson(), clientsTableFile);
|
||||
return error;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef CLIENTINFOLOGIC_H
|
||||
#define CLIENTINFOLOGIC_H
|
||||
|
||||
#include "PageLogicBase.h"
|
||||
|
||||
#include "core/defs.h"
|
||||
#include "containers/containers_defs.h"
|
||||
#include "protocols/protocols_defs.h"
|
||||
|
||||
class UiLogic;
|
||||
|
||||
class ClientInfoLogic : public PageLogicBase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
AUTO_PROPERTY(QString, lineEditNameAliasText)
|
||||
AUTO_PROPERTY(QString, labelOpenVpnCertId)
|
||||
AUTO_PROPERTY(QString, textAreaOpenVpnCertData)
|
||||
AUTO_PROPERTY(QString, labelCurrentVpnProtocolText)
|
||||
AUTO_PROPERTY(QString, textAreaWireGuardKeyData)
|
||||
AUTO_PROPERTY(bool, busyIndicatorIsRunning);
|
||||
AUTO_PROPERTY(bool, pageContentVisible);
|
||||
|
||||
public:
|
||||
ClientInfoLogic(UiLogic *uiLogic, QObject *parent = nullptr);
|
||||
~ClientInfoLogic() = default;
|
||||
|
||||
void setCurrentClientId(int index);
|
||||
|
||||
public slots:
|
||||
void onUpdatePage() override;
|
||||
void onLineEditNameAliasEditingFinished();
|
||||
void onRevokeOpenVpnCertificateClicked();
|
||||
void onRevokeWireGuardKeyClicked();
|
||||
|
||||
private:
|
||||
ErrorCode setClientsList(const ServerCredentials &credentials, DockerContainer container, Proto mainProtocol, const QJsonObject &clietns);
|
||||
|
||||
int m_currentClientIndex;
|
||||
};
|
||||
|
||||
#endif // CLIENTINFOLOGIC_H
|
||||
@@ -0,0 +1,143 @@
|
||||
#include "ClientManagementLogic.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "defines.h"
|
||||
#include "core/errorstrings.h"
|
||||
#include "core/servercontroller.h"
|
||||
#include "ui/pages_logic/ClientInfoLogic.h"
|
||||
#include "ui/models/clientManagementModel.h"
|
||||
#include "ui/uilogic.h"
|
||||
|
||||
ClientManagementLogic::ClientManagementLogic(UiLogic *logic, QObject *parent):
|
||||
PageLogicBase(logic, parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ClientManagementLogic::onUpdatePage()
|
||||
{
|
||||
set_busyIndicatorIsRunning(true);
|
||||
|
||||
qobject_cast<ClientManagementModel*>(uiLogic()->clientManagementModel())->clearData();
|
||||
DockerContainer selectedContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex);
|
||||
QString selectedContainerName = ContainerProps::containerHumanNames().value(selectedContainer);
|
||||
set_labelCurrentVpnProtocolText(tr("Service: ") + selectedContainerName);
|
||||
|
||||
QJsonObject clients;
|
||||
|
||||
auto protocols = ContainerProps::protocolsForContainer(selectedContainer);
|
||||
if (!protocols.empty()) {
|
||||
m_currentMainProtocol = protocols.front();
|
||||
|
||||
const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex);
|
||||
|
||||
ErrorCode error = getClientsList(credentials, selectedContainer, m_currentMainProtocol, clients);
|
||||
if (error != ErrorCode::NoError) {
|
||||
QMessageBox::warning(nullptr, APPLICATION_NAME,
|
||||
tr("An error occurred while getting the list of clients.") + "\n" + errorString(error));
|
||||
set_busyIndicatorIsRunning(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
QVector<QVariant> clientsArray;
|
||||
for (auto &clientId : clients.keys()) {
|
||||
clientsArray.push_back(clients[clientId].toObject());
|
||||
}
|
||||
qobject_cast<ClientManagementModel*>(uiLogic()->clientManagementModel())->setContent(clientsArray);
|
||||
|
||||
set_busyIndicatorIsRunning(false);
|
||||
}
|
||||
|
||||
void ClientManagementLogic::onClientItemClicked(int index)
|
||||
{
|
||||
uiLogic()->pageLogic<ClientInfoLogic>()->setCurrentClientId(index);
|
||||
emit uiLogic()->goToClientInfoPage(m_currentMainProtocol);
|
||||
}
|
||||
|
||||
ErrorCode ClientManagementLogic::getClientsList(const ServerCredentials &credentials, DockerContainer container, Proto mainProtocol, QJsonObject &clietns)
|
||||
{
|
||||
ErrorCode error = ErrorCode::NoError;
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data + "\n";
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
|
||||
const QString mainProtocolString = ProtocolProps::protoToString(mainProtocol);
|
||||
|
||||
ServerController serverController(m_settings);
|
||||
|
||||
const QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable").arg(mainProtocolString);
|
||||
const QByteArray clientsTableString = serverController.getTextFileFromContainer(container, credentials, clientsTableFile, &error);
|
||||
if (error != ErrorCode::NoError) {
|
||||
return error;
|
||||
}
|
||||
QJsonObject clientsTable = QJsonDocument::fromJson(clientsTableString).object();
|
||||
int count = 0;
|
||||
|
||||
if (mainProtocol == Proto::OpenVpn) {
|
||||
const QString getOpenVpnClientsList = "sudo docker exec -i $CONTAINER_NAME bash -c 'ls /opt/amnezia/openvpn/pki/issued'";
|
||||
QString script = serverController.replaceVars(getOpenVpnClientsList, serverController.genVarsForScript(credentials, container));
|
||||
error = serverController.runScript(credentials, script, cbReadStdOut);
|
||||
if (error != ErrorCode::NoError) {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (!stdOut.isEmpty()) {
|
||||
QStringList certsIds = stdOut.split("\n", Qt::SkipEmptyParts);
|
||||
certsIds.removeAll("AmneziaReq.crt");
|
||||
|
||||
for (auto &openvpnCertId : certsIds) {
|
||||
openvpnCertId.replace(".crt", "");
|
||||
if (!clientsTable.contains(openvpnCertId)) {
|
||||
|
||||
QJsonObject client;
|
||||
client["openvpnCertId"] = openvpnCertId;
|
||||
client["clientName"] = QString("Client %1").arg(count);
|
||||
client["openvpnCertData"] = "";
|
||||
clientsTable[openvpnCertId] = client;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (mainProtocol == Proto::WireGuard) {
|
||||
const QString wireGuardConfigFile = "opt/amnezia/wireguard/wg0.conf";
|
||||
const QString wireguardConfigString = serverController.getTextFileFromContainer(container, credentials, wireGuardConfigFile, &error);
|
||||
if (error != ErrorCode::NoError) {
|
||||
return error;
|
||||
}
|
||||
|
||||
auto configLines = wireguardConfigString.split("\n", Qt::SkipEmptyParts);
|
||||
QStringList wireguardKeys;
|
||||
for (const auto &line : configLines) {
|
||||
auto configPair = line.split(" = ", Qt::SkipEmptyParts);
|
||||
if (configPair.front() == "PublicKey") {
|
||||
wireguardKeys.push_back(configPair.back());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &wireguardKey : wireguardKeys) {
|
||||
if (!clientsTable.contains(wireguardKey)) {
|
||||
QJsonObject client;
|
||||
client["clientName"] = QString("Client %1").arg(count);
|
||||
client["wireguardPublicKey"] = wireguardKey;
|
||||
clientsTable[wireguardKey] = client;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const QByteArray newClientsTableString = QJsonDocument(clientsTable).toJson();
|
||||
if (clientsTableString != newClientsTableString) {
|
||||
error = serverController.uploadTextFileToContainer(container, credentials, newClientsTableString, clientsTableFile);
|
||||
}
|
||||
|
||||
if (error != ErrorCode::NoError) {
|
||||
return error;
|
||||
}
|
||||
|
||||
clietns = clientsTable;
|
||||
|
||||
return error;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#ifndef CLIENTMANAGMENTLOGIC_H
|
||||
#define CLIENTMANAGMENTLOGIC_H
|
||||
|
||||
#include "PageLogicBase.h"
|
||||
|
||||
#include "core/defs.h"
|
||||
#include "containers/containers_defs.h"
|
||||
#include "protocols/protocols_defs.h"
|
||||
|
||||
class UiLogic;
|
||||
|
||||
class ClientManagementLogic : public PageLogicBase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
AUTO_PROPERTY(QString, labelCurrentVpnProtocolText)
|
||||
AUTO_PROPERTY(bool, busyIndicatorIsRunning);
|
||||
|
||||
public:
|
||||
ClientManagementLogic(UiLogic *uiLogic, QObject *parent = nullptr);
|
||||
~ClientManagementLogic() = default;
|
||||
|
||||
public slots:
|
||||
void onUpdatePage() override;
|
||||
void onClientItemClicked(int index);
|
||||
|
||||
private:
|
||||
ErrorCode getClientsList(const ServerCredentials &credentials, DockerContainer container, Proto mainProtocol, QJsonObject &clietns);
|
||||
|
||||
amnezia::Proto m_currentMainProtocol;
|
||||
};
|
||||
|
||||
#endif // CLIENTMANAGMENTLOGIC_H
|
||||
@@ -12,7 +12,7 @@ ServerConfiguringProgressLogic::ServerConfiguringProgressLogic(UiLogic *logic, Q
|
||||
m_labelWaitInfoVisible{true},
|
||||
m_labelWaitInfoText{tr("Please wait, configuring process may take up to 5 minutes")},
|
||||
m_progressBarVisible{true},
|
||||
m_progressBarMaximium{100},
|
||||
m_progressBarMaximum{100},
|
||||
m_progressBarTextVisible{true},
|
||||
m_progressBarText{tr("Configuring...")},
|
||||
m_labelServerBusyVisible{false},
|
||||
@@ -46,8 +46,8 @@ ErrorCode ServerConfiguringProgressLogic::doInstallAction(const std::function<Er
|
||||
progress.getValueFunc = [this] (void) -> int {
|
||||
return progressBarValue();
|
||||
};
|
||||
progress.getMaximiumFunc = [this] (void) -> int {
|
||||
return progressBarMaximium();
|
||||
progress.getMaximumFunc = [this] (void) -> int {
|
||||
return progressBarMaximum();
|
||||
};
|
||||
|
||||
LabelFunc busyInfo;
|
||||
@@ -150,7 +150,7 @@ ErrorCode ServerConfiguringProgressLogic::doInstallAction(const std::function<Er
|
||||
// just ui progressbar tweak
|
||||
timer.stop();
|
||||
|
||||
int remainingVal = progress.getMaximiumFunc() - progress.getValueFunc();
|
||||
int remainingVal = progress.getMaximumFunc() - progress.getValueFunc();
|
||||
|
||||
if (remainingVal > 0) {
|
||||
QTimer timer1;
|
||||
@@ -158,7 +158,7 @@ ErrorCode ServerConfiguringProgressLogic::doInstallAction(const std::function<Er
|
||||
|
||||
connect(&timer1, &QTimer::timeout, [&](){
|
||||
progress.setValueFunc(progress.getValueFunc() + 1);
|
||||
if (progress.getValueFunc() >= progress.getMaximiumFunc()) {
|
||||
if (progress.getValueFunc() >= progress.getMaximumFunc()) {
|
||||
loop1.quit();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@ class ServerConfiguringProgressLogic : public PageLogicBase
|
||||
AUTO_PROPERTY(bool, labelWaitInfoVisible)
|
||||
AUTO_PROPERTY(QString, labelWaitInfoText)
|
||||
AUTO_PROPERTY(bool, progressBarVisible)
|
||||
AUTO_PROPERTY(int, progressBarMaximium)
|
||||
AUTO_PROPERTY(int, progressBarMaximum)
|
||||
AUTO_PROPERTY(bool, progressBarTextVisible)
|
||||
AUTO_PROPERTY(QString, progressBarText)
|
||||
AUTO_PROPERTY(bool, labelServerBusyVisible)
|
||||
@@ -32,7 +32,7 @@ private:
|
||||
std::function<void(bool)> setVisibleFunc;
|
||||
std::function<void(int)> setValueFunc;
|
||||
std::function<int(void)> getValueFunc;
|
||||
std::function<int(void)> getMaximiumFunc;
|
||||
std::function<int(void)> getMaximumFunc;
|
||||
std::function<void(bool)> setTextVisibleFunc;
|
||||
std::function<void(const QString&)> setTextFunc;
|
||||
};
|
||||
|
||||
@@ -218,7 +218,7 @@ void VpnLogic::onConnect()
|
||||
}
|
||||
|
||||
if (container == DockerContainer::None) {
|
||||
set_labelErrorText(tr("VPN Protocol not choosen"));
|
||||
set_labelErrorText(tr("VPN Protocol not chosen"));
|
||||
set_pushButtonConnectChecked(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ CloakLogic::CloakLogic(UiLogic *logic, QObject *parent):
|
||||
m_labelInfoVisible{true},
|
||||
m_labelInfoText{},
|
||||
m_progressBarResetValue{0},
|
||||
m_progressBarResetMaximium{100}
|
||||
m_progressBarResetMaximum{100}
|
||||
{
|
||||
|
||||
}
|
||||
@@ -87,8 +87,8 @@ void CloakLogic::onPushButtonSaveClicked()
|
||||
progressBarFunc.getValueFunc = [this] (void) -> int {
|
||||
return progressBarResetValue();
|
||||
};
|
||||
progressBarFunc.getMaximiumFunc = [this] (void) -> int {
|
||||
return progressBarResetMaximium();
|
||||
progressBarFunc.getMaximumFunc = [this] (void) -> int {
|
||||
return progressBarResetMaximum();
|
||||
};
|
||||
progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void {
|
||||
set_progressBarTextVisible(visible);
|
||||
|
||||
@@ -19,7 +19,7 @@ class CloakLogic : public PageProtocolLogicBase
|
||||
AUTO_PROPERTY(bool, labelInfoVisible)
|
||||
AUTO_PROPERTY(QString, labelInfoText)
|
||||
AUTO_PROPERTY(int, progressBarResetValue)
|
||||
AUTO_PROPERTY(int, progressBarResetMaximium)
|
||||
AUTO_PROPERTY(int, progressBarResetMaximum)
|
||||
AUTO_PROPERTY(bool, progressBarTextVisible)
|
||||
AUTO_PROPERTY(QString, progressBarText)
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ OpenVpnLogic::OpenVpnLogic(UiLogic *logic, QObject *parent):
|
||||
m_labelProtoOpenVpnInfoVisible{true},
|
||||
m_labelProtoOpenVpnInfoText{},
|
||||
m_progressBarResetValue{0},
|
||||
m_progressBarResetMaximium{100}
|
||||
m_progressBarResetMaximum{100}
|
||||
{
|
||||
|
||||
}
|
||||
@@ -51,17 +51,17 @@ void OpenVpnLogic::updateProtocolPage(const QJsonObject &openvpnConfig, DockerCo
|
||||
set_lineEditSubnetText(openvpnConfig.value(config_key::subnet_address).
|
||||
toString(protocols::openvpn::defaultSubnetAddress));
|
||||
|
||||
QString trasnsport;
|
||||
QString transport;
|
||||
if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) {
|
||||
trasnsport = "tcp";
|
||||
transport = "tcp";
|
||||
set_radioButtonUdpEnabled(false);
|
||||
set_radioButtonTcpEnabled(false);
|
||||
} else {
|
||||
trasnsport = openvpnConfig.value(config_key::transport_proto).
|
||||
transport = openvpnConfig.value(config_key::transport_proto).
|
||||
toString(protocols::openvpn::defaultTransportProto);
|
||||
}
|
||||
set_radioButtonUdpChecked(trasnsport == protocols::openvpn::defaultTransportProto);
|
||||
set_radioButtonTcpChecked(trasnsport != protocols::openvpn::defaultTransportProto);
|
||||
set_radioButtonUdpChecked(transport == protocols::openvpn::defaultTransportProto);
|
||||
set_radioButtonTcpChecked(transport != protocols::openvpn::defaultTransportProto);
|
||||
|
||||
set_comboBoxVpnCipherText(openvpnConfig.value(config_key::cipher).
|
||||
toString(protocols::openvpn::defaultCipher));
|
||||
@@ -137,8 +137,8 @@ void OpenVpnLogic::onPushButtonSaveClicked()
|
||||
progressBarFunc.getValueFunc = [this] (void) -> int {
|
||||
return progressBarResetValue();
|
||||
};
|
||||
progressBarFunc.getMaximiumFunc = [this] (void) -> int {
|
||||
return progressBarResetMaximium();
|
||||
progressBarFunc.getMaximumFunc = [this] (void) -> int {
|
||||
return progressBarResetMaximum();
|
||||
};
|
||||
progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void {
|
||||
set_progressBarTextVisible(visible);
|
||||
|
||||
@@ -33,7 +33,7 @@ class OpenVpnLogic : public PageProtocolLogicBase
|
||||
AUTO_PROPERTY(bool, labelProtoOpenVpnInfoVisible)
|
||||
AUTO_PROPERTY(QString, labelProtoOpenVpnInfoText)
|
||||
AUTO_PROPERTY(int, progressBarResetValue)
|
||||
AUTO_PROPERTY(int, progressBarResetMaximium)
|
||||
AUTO_PROPERTY(int, progressBarResetMaximum)
|
||||
AUTO_PROPERTY(bool, progressBarTextVisible)
|
||||
AUTO_PROPERTY(QString, progressBarText)
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ ShadowSocksLogic::ShadowSocksLogic(UiLogic *logic, QObject *parent):
|
||||
m_labelInfoVisible{true},
|
||||
m_labelInfoText{},
|
||||
m_progressBarResetValue{0},
|
||||
m_progressBarResetMaximium{100}
|
||||
m_progressBarResetMaximum{100}
|
||||
{
|
||||
|
||||
}
|
||||
@@ -79,8 +79,8 @@ void ShadowSocksLogic::onPushButtonSaveClicked()
|
||||
progressBarFunc.getValueFunc = [this] (void) -> int {
|
||||
return progressBarResetValue();
|
||||
};
|
||||
progressBarFunc.getMaximiumFunc = [this] (void) -> int {
|
||||
return progressBarResetMaximium();
|
||||
progressBarFunc.getMaximumFunc = [this] (void) -> int {
|
||||
return progressBarResetMaximum();
|
||||
};
|
||||
progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void {
|
||||
set_progressBarTextVisible(visible);
|
||||
|
||||
@@ -17,7 +17,7 @@ class ShadowSocksLogic : public PageProtocolLogicBase
|
||||
AUTO_PROPERTY(bool, labelInfoVisible)
|
||||
AUTO_PROPERTY(QString, labelInfoText)
|
||||
AUTO_PROPERTY(int, progressBarResetValue)
|
||||
AUTO_PROPERTY(int, progressBarResetMaximium)
|
||||
AUTO_PROPERTY(int, progressBarResetMaximum)
|
||||
AUTO_PROPERTY(bool, progressBarTextVisible)
|
||||
AUTO_PROPERTY(QString, progressBarText)
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import PageEnum 1.0
|
||||
import ProtocolEnum 1.0
|
||||
import "../"
|
||||
import "../../Controls"
|
||||
import "../../Config"
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
property var protocol: ProtocolEnum.Any
|
||||
page: PageEnum.ClientInfo
|
||||
logic: ClientInfoLogic
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import ProtocolEnum 1.0
|
||||
import "../"
|
||||
import "../../Controls"
|
||||
import "../../Config"
|
||||
|
||||
PageClientInfoBase {
|
||||
id: root
|
||||
protocol: ProtocolEnum.OpenVpn
|
||||
|
||||
BackButton {
|
||||
id: back
|
||||
enabled: !ClientInfoLogic.busyIndicatorIsRunning
|
||||
}
|
||||
|
||||
Caption {
|
||||
id: caption
|
||||
text: qsTr("Client Info")
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
z: 99
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: ClientInfoLogic.busyIndicatorIsRunning
|
||||
running: ClientInfoLogic.busyIndicatorIsRunning
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
id: fl
|
||||
anchors.top: caption.bottom
|
||||
contentHeight: content.height
|
||||
visible: ClientInfoLogic.pageContentVisible
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: GC.defaultMargin
|
||||
|
||||
LabelType {
|
||||
enabled: !ClientInfoLogic.busyIndicatorIsRunning
|
||||
Layout.fillWidth: true
|
||||
font.pixelSize: 20
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: ClientInfoLogic.labelCurrentVpnProtocolText
|
||||
}
|
||||
|
||||
LabelType {
|
||||
enabled: !ClientInfoLogic.busyIndicatorIsRunning
|
||||
height: 21
|
||||
text: qsTr("Client name")
|
||||
}
|
||||
|
||||
TextFieldType {
|
||||
enabled: !ClientInfoLogic.busyIndicatorIsRunning
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 31
|
||||
text: ClientInfoLogic.lineEditNameAliasText
|
||||
onEditingFinished: {
|
||||
if (text !== ClientInfoLogic.lineEditNameAliasText) {
|
||||
ClientInfoLogic.lineEditNameAliasText = text
|
||||
ClientInfoLogic.onLineEditNameAliasEditingFinished()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LabelType {
|
||||
enabled: !ClientInfoLogic.busyIndicatorIsRunning
|
||||
Layout.topMargin: 20
|
||||
height: 21
|
||||
text: qsTr("Certificate id")
|
||||
}
|
||||
|
||||
LabelType {
|
||||
enabled: !ClientInfoLogic.busyIndicatorIsRunning
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: ClientInfoLogic.labelOpenVpnCertId
|
||||
}
|
||||
|
||||
LabelType {
|
||||
enabled: !ClientInfoLogic.busyIndicatorIsRunning
|
||||
Layout.topMargin: 20
|
||||
height: 21
|
||||
text: qsTr("Certificate")
|
||||
}
|
||||
|
||||
TextAreaType {
|
||||
enabled: !ClientInfoLogic.busyIndicatorIsRunning
|
||||
Layout.preferredHeight: 200
|
||||
Layout.fillWidth: true
|
||||
|
||||
textArea.readOnly: true
|
||||
textArea.wrapMode: TextEdit.WrapAnywhere
|
||||
textArea.verticalAlignment: Text.AlignTop
|
||||
textArea.text: ClientInfoLogic.textAreaOpenVpnCertData
|
||||
}
|
||||
|
||||
BlueButtonType {
|
||||
enabled: !ClientInfoLogic.busyIndicatorIsRunning
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 41
|
||||
text: qsTr("Revoke Certificate")
|
||||
onClicked: {
|
||||
ClientInfoLogic.onRevokeOpenVpnCertificateClicked()
|
||||
UiLogic.closePage()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import ProtocolEnum 1.0
|
||||
import "../"
|
||||
import "../../Controls"
|
||||
import "../../Config"
|
||||
|
||||
PageClientInfoBase {
|
||||
id: root
|
||||
protocol: ProtocolEnum.WireGuard
|
||||
|
||||
BackButton {
|
||||
id: back
|
||||
enabled: !ClientInfoLogic.busyIndicatorIsRunning
|
||||
}
|
||||
|
||||
Caption {
|
||||
id: caption
|
||||
text: qsTr("Client Info")
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
z: 99
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: ClientInfoLogic.busyIndicatorIsRunning
|
||||
running: ClientInfoLogic.busyIndicatorIsRunning
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
id: fl
|
||||
anchors.top: caption.bottom
|
||||
contentHeight: content.height
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: GC.defaultMargin
|
||||
|
||||
LabelType {
|
||||
enabled: !ClientInfoLogic.busyIndicatorIsRunning
|
||||
Layout.fillWidth: true
|
||||
font.pixelSize: 20
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: ClientInfoLogic.labelCurrentVpnProtocolText
|
||||
}
|
||||
|
||||
LabelType {
|
||||
enabled: !ClientInfoLogic.busyIndicatorIsRunning
|
||||
height: 21
|
||||
text: qsTr("Client name")
|
||||
}
|
||||
|
||||
TextFieldType {
|
||||
enabled: !ClientInfoLogic.busyIndicatorIsRunning
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 31
|
||||
text: ClientInfoLogic.lineEditNameAliasText
|
||||
onEditingFinished: {
|
||||
if (text !== ClientInfoLogic.lineEditNameAliasText) {
|
||||
ClientInfoLogic.lineEditNameAliasText = text
|
||||
ClientInfoLogic.onLineEditNameAliasEditingFinished()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LabelType {
|
||||
enabled: !ClientInfoLogic.busyIndicatorIsRunning
|
||||
Layout.topMargin: 20
|
||||
height: 21
|
||||
text: qsTr("Public Key")
|
||||
}
|
||||
|
||||
TextAreaType {
|
||||
enabled: !ClientInfoLogic.busyIndicatorIsRunning
|
||||
Layout.preferredHeight: 200
|
||||
Layout.fillWidth: true
|
||||
|
||||
textArea.readOnly: true
|
||||
textArea.wrapMode: TextEdit.WrapAnywhere
|
||||
textArea.verticalAlignment: Text.AlignTop
|
||||
textArea.text: ClientInfoLogic.textAreaWireGuardKeyData
|
||||
}
|
||||
|
||||
BlueButtonType {
|
||||
enabled: !ClientInfoLogic.busyIndicatorIsRunning
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 41
|
||||
text: qsTr("Revoke Key")
|
||||
onClicked: {
|
||||
ClientInfoLogic.onRevokeWireGuardKeyClicked()
|
||||
UiLogic.closePage()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,6 +88,15 @@ PageBase {
|
||||
}
|
||||
}
|
||||
|
||||
BlueButtonType {
|
||||
Layout.topMargin: 10
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Clients Management")
|
||||
onClicked: {
|
||||
UiLogic.goToPage(PageEnum.ClientManagement)
|
||||
}
|
||||
}
|
||||
|
||||
PopupWithQuestion {
|
||||
id: popupClearServer
|
||||
questionText: "Attention! All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. Continue?"
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Shapes 1.4
|
||||
import SortFilterProxyModel 0.2
|
||||
import PageEnum 1.0
|
||||
import "./"
|
||||
import "../Controls"
|
||||
import "../Config"
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
page: PageEnum.ClientManagement
|
||||
logic: ClientManagementLogic
|
||||
enabled: !ClientManagementLogic.busyIndicatorIsRunning
|
||||
|
||||
BackButton {
|
||||
id: back
|
||||
}
|
||||
|
||||
Caption {
|
||||
id: caption
|
||||
text: qsTr("Clients Management")
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
z: 99
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: ClientManagementLogic.busyIndicatorIsRunning
|
||||
running: ClientManagementLogic.busyIndicatorIsRunning
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
id: fl
|
||||
anchors.top: caption.bottom
|
||||
contentHeight: content.height
|
||||
|
||||
Column {
|
||||
id: content
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
LabelType {
|
||||
font.pixelSize: 20
|
||||
leftPadding: -20
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: ClientManagementLogic.labelCurrentVpnProtocolText
|
||||
}
|
||||
|
||||
SortFilterProxyModel {
|
||||
id: proxyClientManagementModel
|
||||
sourceModel: UiLogic.clientManagementModel
|
||||
sorters: RoleSorter { roleName: "clientName" }
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: lv_clients
|
||||
width: parent.width
|
||||
implicitHeight: contentHeight + 20
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 20
|
||||
topMargin: 10
|
||||
spacing: 10
|
||||
clip: true
|
||||
model: proxyClientManagementModel
|
||||
highlightRangeMode: ListView.ApplyRange
|
||||
highlightMoveVelocity: -1
|
||||
delegate: Item {
|
||||
implicitWidth: lv_clients.width
|
||||
implicitHeight: 60
|
||||
|
||||
MouseArea {
|
||||
id: ms
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
ClientManagementLogic.onClientItemClicked(proxyClientManagementModel.mapToSource(index))
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
gradient: ms.containsMouse ? gradient_containsMouse : gradient_notContainsMouse
|
||||
LinearGradient {
|
||||
id: gradient_notContainsMouse
|
||||
x1: 0 ; y1:0
|
||||
x2: 0 ; y2: height
|
||||
stops: [
|
||||
GradientStop { position: 0.0; color: "#FAFBFE" },
|
||||
GradientStop { position: 1.0; color: "#ECEEFF" }
|
||||
]
|
||||
}
|
||||
LinearGradient {
|
||||
id: gradient_containsMouse
|
||||
x1: 0 ; y1:0
|
||||
x2: 0 ; y2: height
|
||||
stops: [
|
||||
GradientStop { position: 0.0; color: "#FAFBFE" },
|
||||
GradientStop { position: 1.0; color: "#DCDEDF" }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
LabelType {
|
||||
x: 20
|
||||
y: 20
|
||||
font.pixelSize: 20
|
||||
text: clientName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ PageBase {
|
||||
enabled: ServerConfiguringProgressLogic.pageEnabled
|
||||
anchors.fill: pb_cancel
|
||||
from: 0
|
||||
to: ServerConfiguringProgressLogic.progressBarMaximium
|
||||
to: ServerConfiguringProgressLogic.progressBarMaximum
|
||||
value: ServerConfiguringProgressLogic.progressBarValue
|
||||
visible: ServerConfiguringProgressLogic.progressBarVisible
|
||||
background: Rectangle {
|
||||
|
||||
@@ -16,6 +16,7 @@ PageBase {
|
||||
BackButton {
|
||||
id: back
|
||||
}
|
||||
|
||||
Caption {
|
||||
id: caption
|
||||
text: qsTr("Server settings")
|
||||
@@ -89,7 +90,6 @@ PageBase {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 10
|
||||
text: qsTr("Advanced server settings")
|
||||
visible: ServerSettingsLogic.pushButtonShareFullVisible //todo
|
||||
onClicked: {
|
||||
UiLogic.goToPage(PageEnum.AdvancedServerSettings)
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ PageBase {
|
||||
Layout.fillWidth: true
|
||||
verticalAlignment: Text.AlignTop
|
||||
text: qsTr('Optional.\n
|
||||
You can enable VPN mode "For selected sites" and add blocked sites you need to visit manually. If you will choose this option, you will need add every bloked site you want to visit to the access list. You may switch between modes later.\n\nPlease note, you should add addresses to the list after VPN connection established. You may add any domain, URL or IP address, it will be resolved to IP address.')
|
||||
You can enable VPN mode "For selected sites" and add blocked sites you need to visit manually. If you will choose this option, you will need add every blocked site you want to visit to the access list. You may switch between modes later.\n\nPlease note, you should add addresses to the list after VPN connection established. You may add any domain, URL or IP address, it will be resolved to IP address.')
|
||||
}
|
||||
|
||||
CheckBoxType {
|
||||
|
||||
@@ -133,7 +133,7 @@ PageProtocolBase {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.fill: pb_save
|
||||
from: 0
|
||||
to: logic.progressBarResetMaximium
|
||||
to: logic.progressBarResetMaximum
|
||||
value: logic.progressBarResetValue
|
||||
background: Rectangle {
|
||||
implicitWidth: parent.width
|
||||
|
||||
@@ -393,7 +393,7 @@ PageProtocolBase {
|
||||
id: progress_save
|
||||
anchors.fill: pb_save
|
||||
from: 0
|
||||
to: logic.progressBarResetMaximium
|
||||
to: logic.progressBarResetMaximum
|
||||
value: logic.progressBarResetValue
|
||||
visible: logic.progressBarResetVisible
|
||||
background: Rectangle {
|
||||
|
||||
@@ -113,7 +113,7 @@ PageProtocolBase {
|
||||
id: progressBar_reset
|
||||
anchors.fill: pb_save
|
||||
from: 0
|
||||
to: logic.progressBarResetMaximium
|
||||
to: logic.progressBarResetMaximum
|
||||
value: logic.progressBarResetValue
|
||||
visible: logic.progressBarResetVisible
|
||||
background: Rectangle {
|
||||
|
||||
@@ -15,7 +15,7 @@ PageShareProtocolBase {
|
||||
|
||||
Caption {
|
||||
id: caption
|
||||
text: qsTr("Share SFTF settings")
|
||||
text: qsTr("Share SFTP settings")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+26
-4
@@ -12,12 +12,14 @@ import "Controls"
|
||||
import "Pages"
|
||||
import "Pages/Protocols"
|
||||
import "Pages/Share"
|
||||
import "Pages/ClientInfo"
|
||||
import "Config"
|
||||
|
||||
Window {
|
||||
property var pages: ({})
|
||||
property var protocolPages: ({})
|
||||
property var sharePages: ({})
|
||||
property var clientInfoPages: ({})
|
||||
|
||||
id: root
|
||||
visible: true
|
||||
@@ -38,6 +40,7 @@ Window {
|
||||
if (type === PageType.Basic) p_obj = pages[page]
|
||||
else if (type === PageType.Proto) p_obj = protocolPages[page]
|
||||
else if (type === PageType.ShareProto) p_obj = sharePages[page]
|
||||
else if (type === PageType.ClientInfo) p_obj = clientInfoPages[page]
|
||||
else return
|
||||
|
||||
//console.debug("QML gotoPage " + type + " " + page + " " + p_obj)
|
||||
@@ -124,7 +127,7 @@ Window {
|
||||
for (var i=0; i<folderModelPages.count; i++) {
|
||||
createPagesObjects(folderModelPages.get(i, "filePath"), PageType.Basic);
|
||||
}
|
||||
UiLogic.initalizeUiLogic()
|
||||
UiLogic.initializeUiLogic()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,9 +157,22 @@ Window {
|
||||
}
|
||||
}
|
||||
|
||||
FolderListModel {
|
||||
id: folderModelClientInfo
|
||||
folder: "qrc:/ui/qml/Pages/ClientInfo/"
|
||||
nameFilters: ["*.qml"]
|
||||
showDirs: false
|
||||
|
||||
onStatusChanged: if (status == FolderListModel.Ready) {
|
||||
for (var i=0; i<folderModelClientInfo.count; i++) {
|
||||
createPagesObjects(folderModelClientInfo.get(i, "filePath"), PageType.ClientInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createPagesObjects(file, type) {
|
||||
if (file.indexOf("Base") !== -1) return; // skip Base Pages
|
||||
//console.debug("Creating compenent " + file + " for " + type);
|
||||
//console.debug("Creating component " + file + " for " + type);
|
||||
|
||||
var c = Qt.createComponent("qrc" + file);
|
||||
|
||||
@@ -177,8 +193,11 @@ Window {
|
||||
else if (type === PageType.ShareProto) {
|
||||
sharePages[obj.protocol] = obj
|
||||
}
|
||||
else if (type === PageType.ClientInfo) {
|
||||
clientInfoPages[obj.protocol] = obj
|
||||
}
|
||||
|
||||
// console.debug("Created compenent " + component.url + " for " + type);
|
||||
// console.debug("Created component " + component.url + " for " + type);
|
||||
}
|
||||
} else if (component.status === Component.Error) {
|
||||
console.debug("Error loading component:", component.errorString());
|
||||
@@ -206,7 +225,10 @@ Window {
|
||||
//console.debug("Qml Connections onGoToShareProtocolPage " + protocol);
|
||||
root.gotoPage(PageType.ShareProto, protocol, reset, slide)
|
||||
}
|
||||
|
||||
function onGoToClientInfoPage(protocol, reset, slide) {
|
||||
//console.debug("Qml Connections onGoToClientInfoPage " + protocol);
|
||||
root.gotoPage(PageType.ClientInfo, protocol, reset, slide)
|
||||
}
|
||||
|
||||
function onClosePage() {
|
||||
root.close_page()
|
||||
|
||||
+11
-3
@@ -66,6 +66,8 @@
|
||||
#include "pages_logic/VpnLogic.h"
|
||||
#include "pages_logic/WizardLogic.h"
|
||||
#include "pages_logic/AdvancedServerSettingsLogic.h"
|
||||
#include "pages_logic/ClientManagementLogic.h"
|
||||
#include "pages_logic/ClientInfoLogic.h"
|
||||
|
||||
#include "pages_logic/protocols/CloakLogic.h"
|
||||
#include "pages_logic/protocols/OpenVpnLogic.h"
|
||||
@@ -84,6 +86,7 @@ UiLogic::UiLogic(std::shared_ptr<Settings> settings, std::shared_ptr<VpnConfigur
|
||||
{
|
||||
m_containersModel = new ContainersModel(settings, this);
|
||||
m_protocolsModel = new ProtocolsModel(settings, this);
|
||||
m_clientManagementModel = new ClientManagementModel(this);
|
||||
m_vpnConnection = new VpnConnection(settings, configurator);
|
||||
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
|
||||
m_vpnConnectionThread.start();
|
||||
@@ -124,7 +127,7 @@ UiLogic::~UiLogic()
|
||||
qDebug() << "Application closed";
|
||||
}
|
||||
|
||||
void UiLogic::initalizeUiLogic()
|
||||
void UiLogic::initializeUiLogic()
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
connect(AndroidController::instance(), &AndroidController::initialized, [this](bool status, bool connected, const QDateTime& connectionDate) {
|
||||
@@ -178,6 +181,9 @@ void UiLogic::showOnStartup()
|
||||
void UiLogic::onUpdateAllPages()
|
||||
{
|
||||
for (auto logic : m_logicMap) {
|
||||
if (dynamic_cast<ClientInfoLogic*>(logic) || dynamic_cast<ClientManagementLogic*>(logic)) {
|
||||
continue;
|
||||
}
|
||||
logic->onUpdatePage();
|
||||
}
|
||||
}
|
||||
@@ -305,8 +311,8 @@ void UiLogic::installServer(QPair<DockerContainer, QJsonObject> &container)
|
||||
progressBarFunc.getValueFunc = [this] (void) -> int {
|
||||
return pageLogic<ServerConfiguringProgressLogic>()->progressBarValue();
|
||||
};
|
||||
progressBarFunc.getMaximiumFunc = [this] (void) -> int {
|
||||
return pageLogic<ServerConfiguringProgressLogic>()->progressBarMaximium();
|
||||
progressBarFunc.getMaximumFunc = [this] (void) -> int {
|
||||
return pageLogic<ServerConfiguringProgressLogic>()->progressBarMaximum();
|
||||
};
|
||||
progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void {
|
||||
pageLogic<ServerConfiguringProgressLogic>()->set_progressBarTextVisible(visible);
|
||||
@@ -533,6 +539,8 @@ void UiLogic::registerPagesLogic()
|
||||
registerPageLogic<ViewConfigLogic>();
|
||||
registerPageLogic<VpnLogic>();
|
||||
registerPageLogic<WizardLogic>();
|
||||
registerPageLogic<ClientManagementLogic>();
|
||||
registerPageLogic<ClientInfoLogic>();
|
||||
registerPageLogic<AdvancedServerSettingsLogic>();
|
||||
}
|
||||
|
||||
|
||||
+8
-1
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "models/containers_model.h"
|
||||
#include "models/protocols_model.h"
|
||||
#include "models/clientManagementModel.h"
|
||||
|
||||
#include "notificationhandler.h"
|
||||
|
||||
@@ -43,6 +44,8 @@ class StartPageLogic;
|
||||
class ViewConfigLogic;
|
||||
class VpnLogic;
|
||||
class WizardLogic;
|
||||
class ClientManagementLogic;
|
||||
class ClientInfoLogic;
|
||||
class AdvancedServerSettingsLogic;
|
||||
|
||||
class PageProtocolLogicBase;
|
||||
@@ -66,6 +69,7 @@ class UiLogic : public QObject
|
||||
|
||||
READONLY_PROPERTY(QObject *, containersModel)
|
||||
READONLY_PROPERTY(QObject *, protocolsModel)
|
||||
READONLY_PROPERTY(QObject *, clientManagementModel)
|
||||
|
||||
public:
|
||||
explicit UiLogic(std::shared_ptr<Settings> settings, std::shared_ptr<VpnConfigurator> configurator, QObject *parent = nullptr);
|
||||
@@ -88,6 +92,8 @@ public:
|
||||
friend class ViewConfigLogic;
|
||||
friend class VpnLogic;
|
||||
friend class WizardLogic;
|
||||
friend class ClientManagementLogic;
|
||||
friend class ClientInfoLogic;
|
||||
friend class AdvancedServerSettingsLogic;
|
||||
|
||||
friend class PageProtocolLogicBase;
|
||||
@@ -102,7 +108,7 @@ public:
|
||||
Q_INVOKABLE virtual void onUpdatePage() {} // UiLogic is set as logic class for some qml pages
|
||||
Q_INVOKABLE void onUpdateAllPages();
|
||||
|
||||
Q_INVOKABLE void initalizeUiLogic();
|
||||
Q_INVOKABLE void initializeUiLogic();
|
||||
Q_INVOKABLE void onCloseWindow();
|
||||
|
||||
Q_INVOKABLE QString containerName(int container);
|
||||
@@ -129,6 +135,7 @@ signals:
|
||||
void goToPage(PageEnumNS::Page page, bool reset = true, bool slide = true);
|
||||
void goToProtocolPage(Proto protocol, bool reset = true, bool slide = true);
|
||||
void goToShareProtocolPage(Proto protocol, bool reset = true, bool slide = true);
|
||||
void goToClientInfoPage(Proto protocol, bool reset = true, bool slide = true);
|
||||
|
||||
void closePage();
|
||||
void setStartPage(PageEnumNS::Page page, bool slide = true);
|
||||
|
||||
@@ -120,9 +120,9 @@ QString Utils::getIPAddress(const QString& host)
|
||||
return host;
|
||||
}
|
||||
|
||||
QList<QHostAddress> adresses = QHostInfo::fromName(host).addresses();
|
||||
if (!adresses.isEmpty()) {
|
||||
return adresses.first().toString();
|
||||
QList<QHostAddress> addresses = QHostInfo::fromName(host).addresses();
|
||||
if (!addresses.isEmpty()) {
|
||||
return addresses.first().toString();
|
||||
}
|
||||
qDebug() << "Unable to resolve address for " << host;
|
||||
return "";
|
||||
|
||||
@@ -307,7 +307,7 @@ QJsonObject VpnConnection::createVpnConfiguration(int serverIndex,
|
||||
void VpnConnection::connectToVpn(int serverIndex,
|
||||
const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig)
|
||||
{
|
||||
qDebug() << QString("СonnectToVpn, Server index is %1, container is %2, route mode is")
|
||||
qDebug() << QString("ConnectToVpn, Server index is %1, container is %2, route mode is")
|
||||
.arg(serverIndex).arg(ContainerProps::containerToString(container)) << m_settings->routeMode();
|
||||
|
||||
#if !defined (Q_OS_ANDROID) && !defined (Q_OS_IOS)
|
||||
@@ -317,7 +317,7 @@ void VpnConnection::connectToVpn(int serverIndex,
|
||||
|
||||
if (!m_IpcClient->isSocketConnected()) {
|
||||
if (!IpcClient::init(m_IpcClient)) {
|
||||
qWarning() << "Error occured when init IPC client";
|
||||
qWarning() << "Error occurred when init IPC client";
|
||||
emit serviceIsNotReady();
|
||||
emit connectionStateChanged(VpnProtocol::Error);
|
||||
return;
|
||||
|
||||
@@ -20,7 +20,7 @@ APP_DOMAIN=org.amneziavpn.package
|
||||
OUT_APP_DIR=$BUILD_DIR/client
|
||||
BUNDLE_DIR=$OUT_APP_DIR/$APP_FILENAME
|
||||
|
||||
# Seacrh Qt
|
||||
# Search Qt
|
||||
if [ -z "${QT_VERSION+x}" ]; then
|
||||
QT_VERSION=6.4.1;
|
||||
QT_BIN_DIR=$HOME/Qt/$QT_VERSION/$ANDROID_CURRENT_ARCH/bin
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ BUNDLE_DIR=$OUT_APP_DIR/$APP_FILENAME
|
||||
PRO_FILE_PATH=$PROJECT_DIR/$APP_NAME.pro
|
||||
QMAKE_STASH_FILE=$PROJECT_DIR/.qmake_stash
|
||||
|
||||
# Seacrh Qt
|
||||
# Search Qt
|
||||
if [ -z "${QT_VERSION+x}" ]; then
|
||||
QT_VERSION=5.15.2;
|
||||
QIF_VERSION=4.1
|
||||
|
||||
@@ -33,7 +33,7 @@ INSTALLER_DATA_DIR=$PROJECT_DIR/deploy/installer/packages/$APP_DOMAIN/data
|
||||
PRO_FILE_PATH=$PROJECT_DIR/$APP_NAME.pro
|
||||
QMAKE_STASH_FILE=$PROJECT_DIR/.qmake_stash
|
||||
|
||||
# Seacrh Qt
|
||||
# Search Qt
|
||||
if [ -z "${QT_VERSION+x}" ]; then
|
||||
QT_VERSION=5.15.2
|
||||
if [ -f /opt/Qt/$QT_VERSION/gcc_64/bin/qmake ]; then
|
||||
|
||||
@@ -36,7 +36,7 @@ PRO_FILE_PATH=$PROJECT_DIR/$APP_NAME.pro
|
||||
QMAKE_STASH_FILE=$PROJECT_DIR/.qmake_stash
|
||||
DMG_FILENAME=$PROJECT_DIR/${APP_NAME}.dmg
|
||||
|
||||
# Seacrh Qt
|
||||
# Search Qt
|
||||
if [ -z "${QT_VERSION+x}" ]; then
|
||||
QT_VERSION=6.4.1;
|
||||
QIF_VERSION=4.1
|
||||
@@ -102,7 +102,7 @@ if [ "${MAC_CERT_PW+x}" ]; then
|
||||
spctl -a -vvvv $BUNDLE_DIR || true
|
||||
|
||||
if [ "${NOTARIZE_APP+x}" ]; then
|
||||
echo "Notatizing App bundle..."
|
||||
echo "Notarizing App bundle..."
|
||||
/usr/bin/ditto -c -k --keepParent $BUNDLE_DIR $PROJECT_DIR/Bundle_to_notarize.zip
|
||||
xcrun altool --notarize-app -f $PROJECT_DIR/Bundle_to_notarize.zip -t osx --primary-bundle-id "$APP_DOMAIN" -u "$APPLE_DEV_EMAIL" -p $APPLE_DEV_PASSWORD
|
||||
rm $PROJECT_DIR/Bundle_to_notarize.zip
|
||||
@@ -135,7 +135,7 @@ if [ "${MAC_CERT_PW+x}" ]; then
|
||||
/usr/bin/codesign --verify -vvvv $INSTALLER_BUNDLE_DIR || true
|
||||
|
||||
if [ "${NOTARIZE_APP+x}" ]; then
|
||||
echo "Notatizing installer bundle..."
|
||||
echo "Notarizing installer bundle..."
|
||||
/usr/bin/ditto -c -k --keepParent $INSTALLER_BUNDLE_DIR $PROJECT_DIR/Installer_bundle_to_notarize.zip
|
||||
xcrun altool --notarize-app -f $PROJECT_DIR/Installer_bundle_to_notarize.zip -t osx --primary-bundle-id "$APP_DOMAIN" -u "$APPLE_DEV_EMAIL" -p $APPLE_DEV_PASSWORD
|
||||
rm $PROJECT_DIR/Installer_bundle_to_notarize.zip
|
||||
@@ -156,7 +156,7 @@ if [ "${MAC_CERT_PW+x}" ]; then
|
||||
/usr/bin/codesign --verify -vvvv $DMG_FILENAME || true
|
||||
|
||||
if [ "${NOTARIZE_APP+x}" ]; then
|
||||
echo "Notatizing DMG installer..."
|
||||
echo "Notarizing DMG installer..."
|
||||
xcrun altool --notarize-app -f $DMG_FILENAME -t osx --primary-bundle-id $APP_DOMAIN -u $APPLE_DEV_EMAIL -p $APPLE_DEV_PASSWORD
|
||||
sleep 600
|
||||
xcrun stapler staple $DMG_FILENAME
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user