Compare commits

..

19 Commits

Author SHA1 Message Date
vladimir.kuznetsov 44018ec94c Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into feature/v2ray-container 2023-04-08 18:14:52 +03:00
vladimir.kuznetsov 89577651d6 fixed V2RayKeyMissing error handling 2023-03-27 08:37:28 +03:00
vladimir.kuznetsov 3b2d3a0b34 Merge branch 'dev' of github.com:amnezia-vpn/desktop-client into feature/v2ray-container 2023-03-19 14:40:30 +03:00
vladimir.kuznetsov fe3228ed74 Merge branch 'dev' of github.com:amnezia-vpn/desktop-client into feature/v2ray-container 2023-02-28 05:34:34 +03:00
vladimir.kuznetsov 917942be94 Merge branch 'dev' of github.com:amnezia-vpn/desktop-client into feature/v2ray-container 2023-02-27 19:51:26 +03:00
vladimir.kuznetsov 6a3875ec7f Merge branch 'dev' of github.com:amnezia-vpn/desktop-client into feature/v2ray-container 2023-02-24 17:54:09 +03:00
vladimir.kuznetsov e121ca9b53 added v2ray binaries for macos and linux 2023-02-15 18:27:00 +03:00
vladimir.kuznetsov c23c8e8462 changed the way shadowsocks client config was created
- some code style refactoring
2023-02-14 19:46:08 +03:00
vladimir.kuznetsov 36fae9152f added local port field to v2ray settings page
- some code style refactoring
2023-02-14 08:35:03 +03:00
vladimir.kuznetsov 2b0ba2aff9 added settings page for v2ray 2023-02-13 18:17:42 +03:00
vladimir.kuznetsov 68830021d6 changed v2ray client config generation 2023-02-13 10:05:15 +03:00
vladimir.kuznetsov 6a903792ab Merge branch 'dev' of github.com:amnezia-vpn/desktop-client into feature/v2ray-container 2023-02-13 07:48:47 +03:00
vladimir.kuznetsov d2d9c33837 Merge branch 'feature/v2ray-container' of github.com:amnezia-vpn/desktop-client into feature/v2ray-container 2023-02-10 20:07:33 +03:00
vladimir.kuznetsov 7d51cb7d58 added implementation of V2RayConfigurator and V2RayProtocol classes 2023-02-10 20:07:17 +03:00
Alexander Gertovskiy 7df3600a62 v2ray-container: added server scripts to start openvpn with v2ray.
* fixed client/server_scripts/openvpn_v2ray_vmess/template_v2ray_client.json
2023-02-09 23:14:34 +02:00
Alexander Gertovskiy 634ca6cbe8 v2ray-container: added server scripts to start openvpn with v2ray.
* added client/server_scripts/openvpn_v2ray_vmess/template_v2ray_client.json
* fixed client/server_scripts/openvpn_v2ray_vmess/template.ovpn
2023-02-09 23:00:14 +02:00
vladimir.kuznetsov 8032e55d7c added a template for the v2ray protocol 2023-02-09 12:41:10 +03:00
Alexander Gertovskiy dc2a1467c7 v2ray-container: added server scripts to start openvpn with v2ray. 2023-02-08 19:00:51 +02:00
Alexander Gertovskiy 67c36f5b30 v2ray-container: added server scripts to start openvpn with v2ray. 2023-02-08 17:48:27 +02:00
156 changed files with 1393 additions and 4102 deletions
+3 -3
View File
@@ -1,3 +1,6 @@
[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/
@@ -34,9 +37,6 @@
[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 3rd/QtSsh deleted from a34ded6e69
+4 -4
View File
@@ -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 probably you need to install arch based cmake
if above error still persists on you M1 Mac, then most proably 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 platform 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 platfrom 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 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!
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!
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.
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.
## License
GPL v.3
-20
View File
@@ -1,20 +0,0 @@
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 ""
)
+6 -5
View File
@@ -235,6 +235,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/v2rayprotocol.h
)
set(SOURCES ${SOURCES}
@@ -245,6 +246,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/v2rayprotocol.cpp
)
endif()
@@ -273,8 +275,6 @@ 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)
@@ -343,7 +343,7 @@ if(IOS)
enable_language(OBJCXX)
enable_language(Swift)
#disable in cicd
#disbale in cicd
include(cmake/osxtools.cmake)
# set(CMAKE_XCODE_GENERATE_TOP_LEVEL_PROJECT_ONLY TRUE)
@@ -355,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 release
#need to change for debug and relase
set_target_properties(${PROJECT}
PROPERTIES XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "org.amnezia.${PROJECT}"
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1"
@@ -509,6 +509,7 @@ 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
@@ -559,7 +560,7 @@ if(WIN32)
set(DEPLOY_ARTIFACT_PATH "windows/x32")
endif()
elseif(LINUX)
set(DEPLOY_ARTIFACT_PATH "linux/client")
set(DEPLOY_ARTIFACT_PATH "linux/client/bin")
elseif(APPLE AND NOT IOS)
set(DEPLOY_ARTIFACT_PATH "macos")
endif()
+6 -39
View File
@@ -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,7 +11,6 @@ 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 {
@@ -106,7 +105,7 @@ android {
resources.srcDirs = ['resources']
renderscript.srcDirs = ['src']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs', relativePathToStrongswan]
jniLibs.srcDirs = ['libs']
androidTest.assets.srcDirs += files("${qtAndroidDir}/schemas".toString())
}
}
@@ -139,65 +138,33 @@ 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 {
@@ -261,7 +261,7 @@ object BaseService {
fun stopRunner(restart: Boolean = false, msg: String? = null) {
if (data.state == State.Stopping) return
// change the state
// channge the state
data.changeState(State.Stopping)
GlobalScope.launch(Dispatchers.Main.immediate) {
data.connectingJob?.cancelAndJoin() // ensure stop connecting first
@@ -30,7 +30,7 @@ object Key {
const val id = "profileId"
const val name = "profileName"
const val individual = "Proxied"
const val individual = "Proxyed"
const val serviceMode = "serviceMode"
const val modeProxy = "proxy"
@@ -835,7 +835,7 @@ public final class Ed25519 {
}
/**
* Decodes {@code s} into an extended projective point.
* Decodes {@code s} into an extented 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 values in a[0..9],b[0..9] must
* values in a[10..19] or b[10..19] aren't swapped, and all 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) {
@@ -1,761 +0,0 @@
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 notification content
// [data] is here a json containing the noification 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 {
findConfigIndices()
findConfigIndicies()
}
private fun findConfigIndices() {
private fun findConfigIndicies() {
val n: Int = stats_n()
for (i in 0 until n) {
+1 -1
View File
@@ -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.preferences", Context.MODE_PRIVATE)
return context.getSharedPreferences("com.amnezia.vpn.prefrences", 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_daemon_logs.txt
* Also stores a copy of all logs in tmp/mozilla_deamon_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_daemon_logs.txt")
file = File(tempDIR, "mozilla_deamon_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 && Logs when in debug, noop in release.
// Only Prints && Loggs when in debug, noop in release.
fun sensitive(tag: String, message: String?) {
if (!BuildConfig.DEBUG) { return; }
if (message == null) { return; }
@@ -150,8 +150,6 @@ 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
@@ -167,7 +165,6 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
Log.e(tag, "Wireguard Version ${wgVersion()}")
mOpenVPNThreadv3 = OpenVPNThreadv3(this)
mAlreadyInitialised = true
ikev2VpnThread = IKEv2Thread(mbuilder, getFilesDir().getAbsolutePath())
}
override fun onCreate() {
@@ -387,11 +384,8 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
startShadowsocks()
startTest()
}
"ikev2" -> {
startIPSEC()
}
else -> {
Log.e(tag, "Unknown protocol ($mProtocol)")
Log.e(tag, "No protocol")
return 0
}
}
@@ -399,18 +393,6 @@ 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)
@@ -566,7 +548,7 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
* Create a Wireguard [Config] from a [json] string -
* The [json] will be created in AndroidVpnProtocol.cpp
*/
private fun buildWireguardConfig(obj: JSONObject): Config {
private fun buildWireugardConfig(obj: JSONObject): Config {
val confBuilder = Config.Builder()
val wireguardConfigData = obj.getJSONObject("wireguard_config_data")
val config = parseConfigData(wireguardConfigData.getString("config"))
@@ -715,7 +697,7 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
}
private fun startWireGuard() {
val wireguard_conf = buildWireguardConfig(mConfig!!)
val wireguard_conf = buildWireugardConfig(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, "Activation Requested, parsing Config")
Log.i(tag, "Activiation Requested, parsing Config")
// [data] is here a json containing the wireguard/openvpn conf
val buffer = data.createByteArray()
val json = buffer?.let { String(it) }
@@ -1,111 +0,0 @@
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
}
}
@@ -1,79 +0,0 @@
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
}
}
}
@@ -1,272 +0,0 @@
/*
* 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;
}
}
@@ -1,67 +0,0 @@
/*
* 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";
}
@@ -1,510 +0,0 @@
/*
* 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;
}
}
@@ -1,224 +0,0 @@
/*
* 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();
}
}
@@ -1,161 +0,0 @@
/*
* 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>();
}
}
@@ -1,167 +0,0 @@
/*
* 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;
}
}
@@ -1,78 +0,0 @@
/*
* 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);
}
}
@@ -0,0 +1,189 @@
/* 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 definitely should since android 12 displays clipboard access.
// And we defnitly 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_REQUIRED = 6
private val EVENT_PERMISSION_REQURED = 6
private val EVENT_DISCONNECTED = 2
private val UI_EVENT_QR_CODE_RECEIVED = 0
fun onPermissionRequest(code: Int, data: Parcel?) {
if (code != EVENT_PERMISSION_REQUIRED) {
if (code != EVENT_PERMISSION_REQURED) {
return
}
+10 -13
View File
@@ -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,9 +105,6 @@ 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
+1 -1
View File
@@ -13,7 +13,7 @@ endif()
if(CODE_SIGN_IDENTITY)
find_program(CODESIGN_BIN NAMES codesign)
if(NOT CODESIGN_BIN)
message(FATAL_ERROR "Cannot sign code, could not find 'codesign' executable")
messsage(FATAL_ERROR "Cannot sign code, could not find 'codesign' executable")
endif()
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY ${CODE_SIGN_IDENTITY})
endif()
+7 -7
View File
@@ -7,24 +7,24 @@
#include "core/servercontroller.h"
#include "containers/containers_defs.h"
CloakConfigurator::CloakConfigurator(std::shared_ptr<Settings> settings, QObject *parent):
ConfiguratorBase(settings, parent)
CloakConfigurator::CloakConfigurator(std::shared_ptr<Settings> settings,
QObject *parent): ConfiguratorBase(settings, parent)
{
}
QString CloakConfigurator::genCloakConfig(const ServerCredentials &credentials,
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
QString CloakConfigurator::genCloakConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode)
{
ErrorCode e = ErrorCode::NoError;
ServerController serverController(m_settings);
QString cloakPublicKey = serverController.getTextFileFromContainer(container, credentials,
amnezia::protocols::cloak::ckPublicKeyPath, &e);
amnezia::protocols::cloak::ckPublicKeyPath, &e);
cloakPublicKey.replace("\n", "");
QString cloakBypassUid = serverController.getTextFileFromContainer(container, credentials,
amnezia::protocols::cloak::ckBypassUidKeyPath, &e);
amnezia::protocols::cloak::ckBypassUidKeyPath, &e);
cloakBypassUid.replace("\n", "");
if (e) {
+1 -1
View File
@@ -14,7 +14,7 @@ public:
CloakConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
QString genCloakConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
};
#endif // CLOAK_CONFIGURATOR_H
+2 -4
View File
@@ -1,8 +1,6 @@
#include "configurator_base.h"
ConfiguratorBase::ConfiguratorBase(std::shared_ptr<Settings> settings, QObject *parent)
: QObject{parent},
m_settings(settings)
ConfiguratorBase::ConfiguratorBase(std::shared_ptr<Settings> settings,
QObject *parent): QObject{parent}, m_settings(settings)
{
}
+13 -16
View File
@@ -1,4 +1,5 @@
#include "ikev2_configurator.h"
#include <QApplication>
#include <QProcess>
#include <QString>
@@ -15,14 +16,13 @@
#include "core/servercontroller.h"
Ikev2Configurator::Ikev2Configurator(std::shared_ptr<Settings> settings, QObject *parent):
ConfiguratorBase(settings, parent)
Ikev2Configurator::Ikev2Configurator(std::shared_ptr<Settings> settings,
QObject *parent): ConfiguratorBase(settings, parent)
{
}
Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const ServerCredentials &credentials,
DockerContainer container, ErrorCode *errorCode)
DockerContainer container, ErrorCode *errorCode)
{
Ikev2Configurator::ConnectionData connData;
connData.host = credentials.hostName;
@@ -33,21 +33,18 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se
QString certFileName = "/opt/amnezia/ikev2/clients/" + connData.clientId + ".p12";
QString scriptCreateCert = QString("certutil -z <(head -c 1024 /dev/urandom) "\
"-S -c \"IKEv2 VPN CA\" -n \"%1\" "\
"-s \"O=IKEv2 VPN,CN=%1\" "\
"-k rsa -g 3072 -v 120 "\
"-d sql:/etc/ipsec.d -t \",,\" "\
"--keyUsage digitalSignature,keyEncipherment "\
"--extKeyUsage serverAuth,clientAuth -8 \"%1\"")
.arg(connData.clientId);
"-S -c \"IKEv2 VPN CA\" -n \"%1\" "\
"-s \"O=IKEv2 VPN,CN=%1\" "\
"-k rsa -g 3072 -v 120 "\
"-d sql:/etc/ipsec.d -t \",,\" "\
"--keyUsage digitalSignature,keyEncipherment "\
"--extKeyUsage serverAuth,clientAuth -8 \"%1\"").arg(connData.clientId);
ServerController serverController(m_settings);
ErrorCode e = serverController.runContainerScript(credentials, container, scriptCreateCert);
QString scriptExportCert = QString("pk12util -W \"%1\" -d sql:/etc/ipsec.d -n \"%2\" -o \"%3\"")
.arg(connData.password)
.arg(connData.clientId)
.arg(certFileName);
.arg(connData.password, connData.clientId, certFileName);
e = serverController.runContainerScript(credentials, container, scriptExportCert);
connData.clientCert = serverController.getTextFileFromContainer(container, credentials, certFileName, &e);
@@ -59,8 +56,8 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se
return connData;
}
QString Ikev2Configurator::genIkev2Config(const ServerCredentials &credentials,
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
QString Ikev2Configurator::genIkev2Config(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode)
{
Q_UNUSED(containerConfig)
+3 -3
View File
@@ -22,14 +22,14 @@ public:
};
QString genIkev2Config(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
QString genIkev2Config(const ConnectionData &connData);
QString genMobileConfig(const ConnectionData &connData);
QString genStrongSwanConfig(const ConnectionData &connData);
ConnectionData prepareIkev2Config(const ServerCredentials &credentials,
DockerContainer container, ErrorCode *errorCode = nullptr);
ConnectionData prepareIkev2Config(const ServerCredentials &credentials, DockerContainer container,
ErrorCode *errorCode = nullptr);
};
#endif // IKEV2_CONFIGURATOR_H
+28 -32
View File
@@ -1,4 +1,5 @@
#include "openvpn_configurator.h"
#include <QApplication>
#include <QProcess>
#include <QString>
@@ -19,14 +20,13 @@
#include <openssl/x509.h>
#include <openssl/pem.h>
OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent):
ConfiguratorBase(settings, parent)
OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings,
QObject *parent): ConfiguratorBase(settings, parent)
{
}
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials,
DockerContainer container, ErrorCode *errorCode)
DockerContainer container, ErrorCode *errorCode)
{
OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest();
connData.host = credentials.hostName;
@@ -36,9 +36,7 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co
return connData;
}
QString reqFileName = QString("%1/%2.req").
arg(amnezia::protocols::openvpn::clientsDirPath).
arg(connData.clientId);
QString reqFileName = QString("%1/%2.req").arg(amnezia::protocols::openvpn::clientsDirPath, connData.clientId);
ServerController serverController(m_settings);
ErrorCode e = serverController.uploadTextFileToContainer(container, credentials, connData.request, reqFileName);
@@ -53,9 +51,11 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co
return connData;
}
connData.caCert = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath, &e);
connData.caCert = serverController.getTextFileFromContainer(container, credentials,
amnezia::protocols::openvpn::caCertPath, &e);
connData.clientCert = serverController.getTextFileFromContainer(container, credentials,
QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), &e);
QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath, connData.clientId),
&e);
if (e) {
if (errorCode) *errorCode = e;
@@ -71,12 +71,12 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co
return connData;
}
QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials,
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode)
{
ServerController serverController(m_settings);
QString config = serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container),
serverController.genVarsForScript(credentials, container, containerConfig));
serverController.genVarsForScript(credentials, container, containerConfig));
ConnectionData connData = prepareOpenVpnConfig(credentials, container, errorCode);
if (errorCode && *errorCode) {
@@ -89,8 +89,7 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia
if (config.contains("$OPENVPN_TA_KEY")) {
config.replace("$OPENVPN_TA_KEY", connData.taKey);
}
else {
} else {
config.replace("<tls-auth>", "");
config.replace("</tls-auth>", "");
}
@@ -112,8 +111,7 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig)
if (m_settings->routeMode() != Settings::VpnAllSites) {
config.replace("redirect-gateway def1 bypass-dhcp", "");
}
else {
} else {
if(!config.contains("redirect-gateway def1 bypass-dhcp")) {
config.append("redirect-gateway def1 bypass-dhcp\n");
}
@@ -151,23 +149,22 @@ QString OpenVpnConfigurator::processConfigWithExportSettings(QString jsonConfig)
return QJsonDocument(json).toJson();
}
ErrorCode OpenVpnConfigurator::signCert(DockerContainer container,
const ServerCredentials &credentials, QString clientId)
ErrorCode OpenVpnConfigurator::signCert(DockerContainer container, const ServerCredentials &credentials, QString clientId)
{
QString script_import = QString("sudo docker exec -i %1 bash -c \"cd /opt/amnezia/openvpn && "
"easyrsa import-req %2/%3.req %3\"")
.arg(ContainerProps::containerToString(container))
.arg(amnezia::protocols::openvpn::clientsDirPath)
.arg(clientId);
"easyrsa import-req %2/%3.req %3\"")
.arg(ContainerProps::containerToString(container),
amnezia::protocols::openvpn::clientsDirPath,
clientId);
QString script_sign = QString("sudo docker exec -i %1 bash -c \"export EASYRSA_BATCH=1; cd /opt/amnezia/openvpn && "
"easyrsa sign-req client %2\"")
.arg(ContainerProps::containerToString(container))
.arg(clientId);
"easyrsa sign-req client %2\"")
.arg(ContainerProps::containerToString(container), clientId);
ServerController serverController(m_settings);
QStringList scriptList {script_import, script_sign};
QString script = serverController.replaceVars(scriptList.join("\n"), serverController.genVarsForScript(credentials, container));
QString script = serverController.replaceVars(scriptList.join("\n"),
serverController.genVarsForScript(credentials, container));
return serverController.runScript(credentials, script);
}
@@ -177,8 +174,8 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
ConnectionData connData;
connData.clientId = Utils::getRandomString(32);
int ret = 0;
int nVersion = 1;
int ret = 0;
int nVersion = 1;
QByteArray clientIdUtf8 = connData.clientId.toUtf8();
@@ -211,7 +208,7 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
// 4. set public key of x509 req
ret = X509_REQ_set_pubkey(x509_req, pKey);
if (ret != 1){
if (ret != 1) {
qWarning() << "Could not set pubkey!";
X509_REQ_free(x509_req);
EVP_PKEY_free(pKey);
@@ -220,7 +217,7 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
// 5. set sign key of x509 req
ret = X509_REQ_sign(x509_req, pKey, EVP_sha256()); // return x509_req->signature->length
if (ret <= 0){
if (ret <= 0) {
qWarning() << "Could not sign request!";
X509_REQ_free(x509_req);
EVP_PKEY_free(pKey);
@@ -230,8 +227,7 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
// save private key
BIO * bp_private = BIO_new(BIO_s_mem());
q_check_ptr(bp_private);
if (PEM_write_bio_PrivateKey(bp_private, pKey, nullptr, nullptr, 0, nullptr, nullptr) != 1)
{
if (PEM_write_bio_PrivateKey(bp_private, pKey, nullptr, nullptr, 0, nullptr, nullptr) != 1) {
qFatal("PEM_write_bio_PrivateKey");
EVP_PKEY_free(pKey);
BIO_free_all(bp_private);
+4 -6
View File
@@ -24,20 +24,18 @@ public:
};
QString genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
QString processConfigWithLocalSettings(QString jsonConfig);
QString processConfigWithExportSettings(QString jsonConfig);
ErrorCode signCert(DockerContainer container,
const ServerCredentials &credentials, QString clientId);
ErrorCode signCert(DockerContainer container, const ServerCredentials &credentials, QString clientId);
private:
ConnectionData createCertRequest();
ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials,
DockerContainer container, ErrorCode *errorCode = nullptr);
ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
ErrorCode *errorCode = nullptr);
};
#endif // OPENVPN_CONFIGURATOR_H
@@ -5,16 +5,16 @@
#include <QJsonDocument>
#include "containers/containers_defs.h"
#include "core/scripts_registry.h"
#include "core/servercontroller.h"
ShadowSocksConfigurator::ShadowSocksConfigurator(std::shared_ptr<Settings> settings, QObject *parent):
ConfiguratorBase(settings, parent)
ShadowSocksConfigurator::ShadowSocksConfigurator(std::shared_ptr<Settings> settings,
QObject *parent): ConfiguratorBase(settings, parent)
{
}
QString ShadowSocksConfigurator::genShadowSocksConfig(const ServerCredentials &credentials,
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
QString ShadowSocksConfigurator::genShadowSocksConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode)
{
ErrorCode e = ErrorCode::NoError;
ServerController serverController(m_settings);
@@ -28,18 +28,12 @@ QString ShadowSocksConfigurator::genShadowSocksConfig(const ServerCredentials &c
return "";
}
QJsonObject config;
config.insert("server", credentials.hostName);
config.insert("server_port", "$SHADOWSOCKS_SERVER_PORT");
config.insert("local_port", "$SHADOWSOCKS_LOCAL_PORT");
config.insert("password", ssKey);
config.insert("timeout", 60);
config.insert("method", "$SHADOWSOCKS_CIPHER");
QString ssClientConfig = serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::shadowsocks_client_template, container),
serverController.genVarsForScript(credentials, container, containerConfig));
QString textCfg = serverController.replaceVars(QJsonDocument(config).toJson(),
serverController.genVarsForScript(credentials, container, containerConfig));
ssClientConfig.replace("$SHADOWSOCKS_PASSWORD", ssKey);
ssClientConfig = serverController.replaceVars(ssClientConfig, serverController.genVarsForScript(credentials, container, containerConfig));
//qDebug().noquote() << textCfg;
return textCfg;
return ssClientConfig;
}
@@ -13,7 +13,7 @@ public:
ShadowSocksConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
QString genShadowSocksConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
};
#endif // SHADOWSOCKS_CONFIGURATOR_H
+5 -8
View File
@@ -1,4 +1,5 @@
#include "ssh_configurator.h"
#include <QApplication>
#include <QProcess>
#include <QString>
@@ -14,11 +15,9 @@
#include "core/server_defs.h"
#include "utilities.h"
SshConfigurator::SshConfigurator(std::shared_ptr<Settings> settings, QObject *parent):
ConfiguratorBase(settings, parent)
SshConfigurator::SshConfigurator(std::shared_ptr<Settings> settings,
QObject *parent): ConfiguratorBase(settings, parent)
{
}
QString SshConfigurator::convertOpenSShKey(const QString &key)
@@ -73,10 +72,8 @@ void SshConfigurator::openSshTerminal(const ServerCredentials &credentials)
// todo: connect by key
// p->setNativeArguments(QString("%1@%2")
// .arg(credentials.userName).arg(credentials.hostName).arg(credentials.password));
}
else {
p->setNativeArguments(QString("%1@%2 -pw %3")
.arg(credentials.userName).arg(credentials.hostName).arg(credentials.password));
} else {
p->setNativeArguments(QString("%1@%2 -pw %3").arg(credentials.userName).arg(credentials.hostName, credentials.password));
}
#else
p->setProgram("/bin/bash");
-1
View File
@@ -16,7 +16,6 @@ public:
QProcessEnvironment prepareEnv();
QString convertOpenSShKey(const QString &key);
void openSshTerminal(const ServerCredentials &credentials);
};
#endif // SSH_CONFIGURATOR_H
@@ -0,0 +1,46 @@
#include "v2ray_configurator.h"
#include <QFile>
#include <QPair>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonDocument>
#include "core/servercontroller.h"
#include "core/scripts_registry.h"
#include "containers/containers_defs.h"
V2RayConfigurator::V2RayConfigurator(std::shared_ptr<Settings> settings,
QObject *parent) : ConfiguratorBase(settings, parent)
{
}
QString V2RayConfigurator::genV2RayConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode)
{
ErrorCode e = ErrorCode::NoError;
ServerController serverController(m_settings);
QString v2RayVmessClientUuid = serverController.getTextFileFromContainer(container, credentials,
amnezia::protocols::v2ray::v2rayKeyPath, &e);
if (v2RayVmessClientUuid.isEmpty()) {
if (errorCode) *errorCode = ErrorCode::V2RayKeyMissing;
return "";
}
v2RayVmessClientUuid.replace("\n", "");
if (e) {
if (errorCode) *errorCode = e;
return "";
}
QString v2RayClientConfig = serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::v2ray_client_template, container),
serverController.genVarsForScript(credentials, container, containerConfig));
v2RayClientConfig.replace("$V2RAY_VMESS_CLIENT_UUID", v2RayVmessClientUuid);
v2RayClientConfig = serverController.replaceVars(v2RayClientConfig,
serverController.genVarsForScript(credentials, container, containerConfig));
return v2RayClientConfig;
}
+20
View File
@@ -0,0 +1,20 @@
#ifndef V2RAYCONFIGURATOR_H
#define V2RAYCONFIGURATOR_H
#include <QObject>
#include "configurator_base.h"
using namespace amnezia;
class V2RayConfigurator : ConfiguratorBase
{
public:
V2RayConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
QString genV2RayConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
};
#endif // V2RAYCONFIGURATOR_H
+12 -18
View File
@@ -5,6 +5,7 @@
#include "wireguard_configurator.h"
#include "ikev2_configurator.h"
#include "ssh_configurator.h"
#include "v2ray_configurator.h"
#include <QFile>
#include <QJsonObject>
@@ -14,8 +15,8 @@
#include "utilities.h"
#include "settings.h"
VpnConfigurator::VpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent):
ConfiguratorBase(settings, parent)
VpnConfigurator::VpnConfigurator(std::shared_ptr<Settings> settings,
QObject *parent) : ConfiguratorBase(settings, parent)
{
openVpnConfigurator = std::shared_ptr<OpenVpnConfigurator>(new OpenVpnConfigurator(settings, this));
shadowSocksConfigurator = std::shared_ptr<ShadowSocksConfigurator>(new ShadowSocksConfigurator(settings, this));
@@ -25,25 +26,22 @@ VpnConfigurator::VpnConfigurator(std::shared_ptr<Settings> settings, QObject *pa
sshConfigurator = std::shared_ptr<SshConfigurator>(new SshConfigurator(settings, this));
}
QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials,
DockerContainer container, const QJsonObject &containerConfig, Proto proto, ErrorCode *errorCode)
QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, Proto proto, ErrorCode *errorCode)
{
switch (proto) {
case Proto::OpenVpn:
return openVpnConfigurator->genOpenVpnConfig(credentials, container, containerConfig, errorCode);
case Proto::ShadowSocks:
return shadowSocksConfigurator->genShadowSocksConfig(credentials, container, containerConfig, errorCode);
case Proto::Cloak:
return cloakConfigurator->genCloakConfig(credentials, container, containerConfig, errorCode);
case Proto::WireGuard:
return wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, errorCode);
case Proto::Ikev2:
return ikev2Configurator->genIkev2Config(credentials, container, containerConfig, errorCode);
case Proto::V2Ray:
return v2RayConfigurator->genV2RayConfig(credentials, container, containerConfig, errorCode);
default:
return "";
}
@@ -62,8 +60,7 @@ QPair<QString, QString> VpnConfigurator::getDnsForConfig(int serverIndex)
if (dns.first.isEmpty() || !Utils::checkIPv4Format(dns.first)) {
if (useAmneziaDns && m_settings->containers(serverIndex).contains(DockerContainer::Dns)) {
dns.first = protocols::dns::amneziaDnsIp;
}
else dns.first = m_settings->primaryDns();
} else dns.first = m_settings->primaryDns();
}
if (dns.second.isEmpty() || !Utils::checkIPv4Format(dns.second)) {
dns.second = m_settings->secondaryDns();
@@ -73,8 +70,7 @@ QPair<QString, QString> VpnConfigurator::getDnsForConfig(int serverIndex)
return dns;
}
QString &VpnConfigurator::processConfigWithDnsSettings(int serverIndex, DockerContainer container,
Proto proto, QString &config)
QString &VpnConfigurator::processConfigWithDnsSettings(int serverIndex, DockerContainer container, Proto proto, QString &config)
{
auto dns = getDnsForConfig(serverIndex);
@@ -84,8 +80,7 @@ QString &VpnConfigurator::processConfigWithDnsSettings(int serverIndex, DockerCo
return config;
}
QString &VpnConfigurator::processConfigWithLocalSettings(int serverIndex, DockerContainer container,
Proto proto, QString &config)
QString &VpnConfigurator::processConfigWithLocalSettings(int serverIndex, DockerContainer container, Proto proto, QString &config)
{
processConfigWithDnsSettings(serverIndex, container, proto, config);
@@ -95,8 +90,7 @@ QString &VpnConfigurator::processConfigWithLocalSettings(int serverIndex, Docker
return config;
}
QString &VpnConfigurator::processConfigWithExportSettings(int serverIndex, DockerContainer container,
Proto proto, QString &config)
QString &VpnConfigurator::processConfigWithExportSettings(int serverIndex, DockerContainer container, Proto proto, QString &config)
{
processConfigWithDnsSettings(serverIndex, container, proto, config);
@@ -107,7 +101,7 @@ QString &VpnConfigurator::processConfigWithExportSettings(int serverIndex, Docke
}
void VpnConfigurator::updateContainerConfigAfterInstallation(DockerContainer container, QJsonObject &containerConfig,
const QString &stdOut)
const QString &stdOut)
{
Proto mainProto = ContainerProps::defaultProtocol(container);
+5 -4
View File
@@ -13,6 +13,7 @@ class CloakConfigurator;
class WireguardConfigurator;
class Ikev2Configurator;
class SshConfigurator;
class V2RayConfigurator;
// Retrieve connection settings from server
class VpnConfigurator : ConfiguratorBase
@@ -22,7 +23,7 @@ public:
VpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
QString genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, Proto proto, ErrorCode *errorCode = nullptr);
const QJsonObject &containerConfig, Proto proto, ErrorCode *errorCode = nullptr);
QPair<QString, QString> getDnsForConfig(int serverIndex);
QString &processConfigWithDnsSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
@@ -30,9 +31,8 @@ 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 configuration
void updateContainerConfigAfterInstallation(DockerContainer container,
QJsonObject &containerConfig, const QString &stdOut);
// workaround for containers which is not support normal configaration
void updateContainerConfigAfterInstallation(DockerContainer container, QJsonObject &containerConfig, const QString &stdOut);
std::shared_ptr<OpenVpnConfigurator> openVpnConfigurator;
std::shared_ptr<ShadowSocksConfigurator> shadowSocksConfigurator;
@@ -40,6 +40,7 @@ public:
std::shared_ptr<WireguardConfigurator> wireguardConfigurator;
std::shared_ptr<Ikev2Configurator> ikev2Configurator;
std::shared_ptr<SshConfigurator> sshConfigurator;
std::shared_ptr<V2RayConfigurator> v2RayConfigurator;
};
#endif // VPN_CONFIGURATOR_H
+21 -24
View File
@@ -7,13 +7,11 @@
#include <QTemporaryFile>
#include <QJsonDocument>
#include <openssl/rand.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include "containers/containers_defs.h"
#include "core/server_defs.h"
#include "core/scripts_registry.h"
@@ -21,10 +19,9 @@
#include "core/servercontroller.h"
#include "settings.h"
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings, QObject *parent):
ConfiguratorBase(settings, parent)
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings,
QObject *parent): ConfiguratorBase(settings, parent)
{
}
WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys()
@@ -59,7 +56,9 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys()
}
WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
DockerContainer container,
const QJsonObject &containerConfig,
ErrorCode *errorCode)
{
WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys();
connData.host = credentials.hostName;
@@ -72,7 +71,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
ErrorCode e = ErrorCode::NoError;
ServerController serverController(m_settings);
// Get list of already created clients (only IP addresses)
// Get list of already created clients (only IP addreses)
QString nextIpNumber;
{
QString script = QString("cat %1 | grep AllowedIPs").arg(amnezia::protocols::wireguard::serverConfigPath);
@@ -95,8 +94,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
// Calc next IP address
if (ips.isEmpty()) {
nextIpNumber = "2";
}
else {
} else {
int next = ips.last().split(".").last().toInt() + 1;
if (next > 254) {
if (errorCode) *errorCode = ErrorCode::AddressPoolError;
@@ -120,14 +118,16 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
}
// Get keys
connData.serverPubKey = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::wireguard::serverPublicKeyPath, &e);
connData.serverPubKey = serverController.getTextFileFromContainer(container, credentials,
amnezia::protocols::wireguard::serverPublicKeyPath, &e);
connData.serverPubKey.replace("\n", "");
if (e) {
if (errorCode) *errorCode = e;
return connData;
}
connData.pskKey = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::wireguard::serverPskKeyPath, &e);
connData.pskKey = serverController.getTextFileFromContainer(container, credentials,
amnezia::protocols::wireguard::serverPskKeyPath, &e);
connData.pskKey.replace("\n", "");
if (e) {
@@ -136,32 +136,29 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
}
// Add client to config
QString configPart = QString(
"[Peer]\n"
"PublicKey = %1\n"
"PresharedKey = %2\n"
"AllowedIPs = %3/32\n\n").
arg(connData.clientPubKey).
arg(connData.pskKey).
arg(connData.clientIP);
QString configPart = QString("[Peer]\n"
"PublicKey = %1\n"
"PresharedKey = %2\n"
"AllowedIPs = %3/32\n\n").arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
e = serverController.uploadTextFileToContainer(container, credentials, configPart,
protocols::wireguard::serverConfigPath, libssh::SftpOverwriteMode::SftpAppendToExisting);
protocols::wireguard::serverConfigPath,
libssh::SftpOverwriteMode::SftpAppendToExisting);
if (e) {
if (errorCode) *errorCode = e;
return connData;
}
QString script = "sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip /opt/amnezia/wireguard/wg0.conf)'";
e = serverController.runScript(credentials,
serverController.replaceVars("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip /opt/amnezia/wireguard/wg0.conf)'",
serverController.genVarsForScript(credentials, container)));
serverController.replaceVars(script, serverController.genVarsForScript(credentials, container)));
return connData;
}
QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &credentials,
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode)
{
ServerController serverController(m_settings);
QString config = serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::wireguard_template, container),
@@ -23,15 +23,14 @@ public:
};
QString genWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
QString processConfigWithLocalSettings(QString config);
QString processConfigWithExportSettings(QString config);
private:
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials,
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
ConnectionData genClientKeys();
};
+8 -1
View File
@@ -55,6 +55,9 @@ QVector<amnezia::Proto> ContainerProps::protocolsForContainer(amnezia::DockerCon
case DockerContainer::Ipsec:
return { Proto::Ikev2 /*, Protocol::L2tp */};
case DockerContainer::V2Ray:
return { Proto::OpenVpn, Proto::V2Ray };
case DockerContainer::Dns:
return { };
@@ -86,6 +89,7 @@ QMap<DockerContainer, QString> ContainerProps::containerHumanNames()
{DockerContainer::Cloak, "OpenVpn over Cloak"},
{DockerContainer::WireGuard, "WireGuard"},
{DockerContainer::Ipsec, QObject::tr("IPsec")},
{DockerContainer::V2Ray, "V2Ray"},
{DockerContainer::TorWebSite, QObject::tr("Web site in Tor network")},
{DockerContainer::Dns, QObject::tr("DNS Service")},
@@ -103,6 +107,7 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
"configured with traffic masking by Cloak plugin")},
{DockerContainer::WireGuard, QObject::tr("WireGuard container")},
{DockerContainer::Ipsec, QObject::tr("IPsec container")},
{DockerContainer::V2Ray, QObject::tr("V2Ray container")},
{DockerContainer::TorWebSite, QObject::tr("Web site in Tor network")},
{DockerContainer::Dns, QObject::tr("DNS Service")},
@@ -120,6 +125,8 @@ amnezia::ServiceType ContainerProps::containerService(DockerContainer c)
case DockerContainer::ShadowSocks : return ServiceType::Vpn;
case DockerContainer::WireGuard : return ServiceType::Vpn;
case DockerContainer::Ipsec : return ServiceType::Vpn;
case DockerContainer::V2Ray : return ServiceType::Vpn;
case DockerContainer::TorWebSite : return ServiceType::Other;
case DockerContainer::Dns : return ServiceType::Other;
//case DockerContainer::FileShare : return ServiceType::Other;
@@ -137,6 +144,7 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
case DockerContainer::ShadowSocks : return Proto::ShadowSocks;
case DockerContainer::WireGuard : return Proto::WireGuard;
case DockerContainer::Ipsec : return Proto::Ikev2;
case DockerContainer::V2Ray : return Proto::V2Ray;
case DockerContainer::TorWebSite : return Proto::TorWebSite;
case DockerContainer::Dns : return Proto::Dns;
@@ -170,7 +178,6 @@ 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;
}
+4 -3
View File
@@ -1,5 +1,5 @@
#ifndef CONTAINERS_DEFS_H
#define CONTAINERS_DEFS_H
#ifndef CONTAIERNS_DEFS_H
#define CONTAIERNS_DEFS_H
#include <QObject>
#include <QQmlEngine>
@@ -19,6 +19,7 @@ enum DockerContainer {
Cloak,
WireGuard,
Ipsec,
V2Ray,
//non-vpn
TorWebSite,
@@ -75,4 +76,4 @@ static void declareQmlContainerEnum() {
QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c);
#endif // CONTAINERS_DEFS_H
#endif // CONTAIERNS_DEFS_H
+4 -2
View File
@@ -32,7 +32,6 @@ enum ErrorCode
ServerContainerMissingError,
ServerDockerFailedError,
ServerCancelInstallation,
ServerUserNotInSudo,
// Ssh connection errors
SshRequsetDeniedError, SshInterruptedError, SshInternalError,
@@ -56,6 +55,7 @@ enum ErrorCode
ShadowSocksExecutableMissing,
CloakExecutableMissing,
AmneziaServiceConnectionFailed,
V2RayExecutableMissing,
ExecutableMissing,
// VPN errors
@@ -68,7 +68,9 @@ enum ErrorCode
OpenSslFailed,
OpenVpnExecutableCrashed,
ShadowSocksExecutableCrashed,
CloakExecutableCrashed
CloakExecutableCrashed,
V2RayExecutableCrashed,
V2RayKeyMissing
};
} // namespace amnezia
+4 -1
View File
@@ -16,7 +16,6 @@ QString errorString(ErrorCode code){
case(ServerContainerMissingError): return QObject::tr("Server error: Docker container missing");
case(ServerDockerFailedError): return QObject::tr("Server error: Docker failed");
case(ServerCancelInstallation): return QObject::tr("Installation canceled by user");
case(ServerUserNotInSudo): return QObject::tr("The user does not have permission to use sudo");
// Libssh errors
case(SshRequsetDeniedError): return QObject::tr("Ssh request was denied");
@@ -52,6 +51,10 @@ QString errorString(ErrorCode code){
case (AmneziaServiceConnectionFailed): return QObject::tr("Amnezia helper service error");
case (OpenSslFailed): return QObject::tr("OpenSSL failed");
// V2Ray errors
case (V2RayExecutableMissing): return QObject::tr("V2Ray (v2ray) executable missing");
case (V2RayKeyMissing): return QObject::tr("V2Ray key missing");
// VPN errors
case (OpenVpnAdaptersInUseError): return QObject::tr("Can't connect: another VPN connection is active");
case (OpenVpnTapAdapterError): return QObject::tr("Can't setup OpenVPN TAP network adapter");
+2 -2
View File
@@ -104,8 +104,8 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
pd->localSocket->connectToServer(amnezia::getIpcProcessUrl(pid));
pd->localSocket->waitForConnected();
auto processReplica = QSharedPointer<PrivilegedProcess>(pd->ipcProcess);
return processReplica;
auto proccessReplica = QSharedPointer<PrivilegedProcess>(pd->ipcProcess);
return proccessReplica;
}
+3 -1
View File
@@ -12,6 +12,7 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container)
case DockerContainer::ShadowSocks: return QLatin1String("openvpn_shadowsocks");
case DockerContainer::WireGuard: return QLatin1String("wireguard");
case DockerContainer::Ipsec: return QLatin1String("ipsec");
case DockerContainer::V2Ray: return QLatin1String("openvpn_v2ray_vmess");
case DockerContainer::TorWebSite: return QLatin1String("website_tor");
case DockerContainer::Dns: return QLatin1String("dns");
@@ -32,7 +33,6 @@ QString amnezia::scriptName(SharedScriptType type)
case SharedScriptType::setup_host_firewall: return QLatin1String("setup_host_firewall.sh");
case SharedScriptType::check_connection: return QLatin1String("check_connection.sh");
case SharedScriptType::check_server_is_busy: return QLatin1String("check_server_is_busy.sh");
case SharedScriptType::check_user_in_sudo: return QLatin1String("check_user_in_sudo.sh");
}
}
@@ -45,6 +45,8 @@ QString amnezia::scriptName(ProtocolScriptType type)
case ProtocolScriptType::container_startup: return QLatin1String("start.sh");
case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn");
case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf");
case ProtocolScriptType::v2ray_client_template: return QLatin1String("template_v2ray_client.json");
case ProtocolScriptType::shadowsocks_client_template: return QLatin1String("template_ss_client.json");
}
}
+4 -3
View File
@@ -16,8 +16,7 @@ enum SharedScriptType {
remove_all_containers,
setup_host_firewall,
check_connection,
check_server_is_busy,
check_user_in_sudo
check_server_is_busy
};
enum ProtocolScriptType {
// Protocol scripts
@@ -26,7 +25,9 @@ enum ProtocolScriptType {
configure_container,
container_startup,
openvpn_template,
wireguard_template
wireguard_template,
v2ray_client_template,
shadowsocks_client_template
};
+40 -81
View File
@@ -245,17 +245,11 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials,
//qDebug().noquote() << QJsonDocument(config).toJson();
ErrorCode e = ErrorCode::NoError;
e = isUserInSudo(credentials, container);
if (e) return e;
if (!isUpdate) {
e = isServerPortBusy(credentials, container, config);
if (e) return e;
}
e = isServerDpkgBusy(credentials, container);
if (e) return e;
e = installDockerWorker(credentials, container);
if (e) return e;
qDebug().noquote() << "ServerController::setupContainer installDockerWorker finished";
@@ -289,10 +283,10 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials,
ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &oldConfig, QJsonObject &newConfig)
{
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" << reinstallRequired;
bool reinstallRequred = isReinstallContainerRequred(container, oldConfig, newConfig);
qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" << reinstallRequred;
if (reinstallRequired) {
if (reinstallRequred) {
return setupContainer(credentials, container, newConfig, true);
}
else {
@@ -326,7 +320,7 @@ QJsonObject ServerController::createContainerInitialConfig(DockerContainer conta
return config;
}
bool ServerController::isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig)
bool ServerController::isReinstallContainerRequred(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig)
{
Proto mainProto = ContainerProps::defaultProtocol(container);
@@ -374,6 +368,37 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent
return ErrorCode::NoError;
};
QFutureWatcher<ErrorCode> watcher;
QFuture<ErrorCode> future = QtConcurrent::run([this, &stdOut, &cbReadStdOut, &cbReadStdErr, &credentials]() {
do {
if (m_cancelInstallation) {
return ErrorCode::ServerCancelInstallation;
}
stdOut.clear();
runScript(credentials,
replaceVars(amnezia::scriptData(SharedScriptType::check_server_is_busy),
genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
if (!stdOut.isEmpty() || stdOut.contains("Unable to acquire the dpkg frontend lock")) {
emit serverIsBusy(true);
QThread::msleep(1000);
}
} while (!stdOut.isEmpty());
return ErrorCode::NoError;
});
QEventLoop wait;
QObject::connect(&watcher, &QFutureWatcher<ErrorCode>::finished, &wait, &QEventLoop::quit);
watcher.setFuture(future);
wait.exec();
m_cancelInstallation = false;
emit serverIsBusy(false);
if (future.result() != ErrorCode::NoError) {
return future.result();
}
ErrorCode error = runScript(credentials,
replaceVars(amnezia::scriptData(SharedScriptType::install_docker),
genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
@@ -491,6 +516,7 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
const QJsonObject &ssConfig = config.value(ProtocolProps::protoToString(Proto::ShadowSocks)).toObject();
const QJsonObject &wireguarConfig = config.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject();
const QJsonObject &sftpConfig = config.value(ProtocolProps::protoToString(Proto::Sftp)).toObject();
const QJsonObject &v2RayConfig = config.value(ProtocolProps::protoToString(Proto::V2Ray)).toObject();
//
Vars vars;
@@ -542,6 +568,10 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
vars.append({{"$WIREGUARD_SERVER_PORT", wireguarConfig.value(config_key::port).toString(protocols::wireguard::defaultPort) }});
// V2Ray vars
vars.append({{"$V2RAY_VMESS_PORT", v2RayConfig.value(config_key::port).toString(protocols::v2ray::defaultServerPort) }});
vars.append({{"$V2RAY_SOCKS_LOCAL_PORT", v2RayConfig.value(config_key::local_port).toString(protocols::v2ray::defaultLocalPort) }});
// IPsec vars
vars.append({{"$IPSEC_VPN_L2TP_NET", "192.168.42.0/24"}});
vars.append({{"$IPSEC_VPN_L2TP_POOL", "192.168.42.10-192.168.42.250"}});
@@ -655,11 +685,6 @@ 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) {
@@ -672,72 +697,6 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
return ErrorCode::NoError;
}
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";
return ErrorCode::NoError;
};
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo);
ErrorCode error = runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
if (!stdOut.contains("sudo")) return ErrorCode::ServerUserNotInSudo;
return error;
}
ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container)
{
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
QFutureWatcher<ErrorCode> watcher;
QFuture<ErrorCode> future = QtConcurrent::run([this, &stdOut, &cbReadStdOut, &cbReadStdErr, &credentials]() {
do {
if (m_cancelInstallation) {
return ErrorCode::ServerCancelInstallation;
}
stdOut.clear();
runScript(credentials,
replaceVars(amnezia::scriptData(SharedScriptType::check_server_is_busy),
genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
if (!stdOut.isEmpty() || stdOut.contains("Unable to acquire the dpkg frontend lock")) {
emit serverIsBusy(true);
QThread::msleep(1000);
}
} while (!stdOut.isEmpty());
return ErrorCode::NoError;
});
QEventLoop wait;
QObject::connect(&watcher, &QFutureWatcher<ErrorCode>::finished, &wait, &QEventLoop::quit);
watcher.setFuture(future);
wait.exec();
m_cancelInstallation = false;
emit serverIsBusy(false);
return future.result();
}
ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap<DockerContainer, QJsonObject> &installedContainers)
{
QString stdOut;
+4 -5
View File
@@ -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,12 +62,11 @@ 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 isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig);
ErrorCode isUserInSudo(const ServerCredentials &credentials, DockerContainer container);
ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container);
bool isReinstallContainerRequred(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig);
ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data,
const QString &remotePath, libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting);
+1 -1
View File
@@ -6,7 +6,7 @@
#include <fstream>
#ifdef Q_OS_WINDOWS
const uint32_t S_IRWXU = 0644;
#define S_IRWXU 0
#endif
namespace libssh {
+1 -1
View File
@@ -1,6 +1,6 @@
# Autogenerated by fastlane
#
# Ensure this file is checked into source control!
# Ensure this file is checked in to source control!
#gem 'fastlane-plugin-run_tests_firebase_testlab'
#gem 'fastlane-plugin-firebase_app_distribution'
+2 -2
View File
@@ -157,7 +157,7 @@ void Logger::clearServiceLogs()
if (!m_IpcClient->isSocketConnected()) {
if (!IpcClient::init(m_IpcClient)) {
qWarning() << "Error occurred when init IPC client";
qWarning() << "Error occured when init IPC client";
return;
}
}
@@ -167,7 +167,7 @@ void Logger::clearServiceLogs()
m_IpcClient->Interface()->cleanUp();
}
else {
qWarning() << "Error occurred cleaning up service logs";
qWarning() << "Error occured cleaning up service logs";
}
#endif
}
+1 -1
View File
@@ -57,7 +57,7 @@ void ManagementServer::onNewConnection()
void ManagementServer::onSocketError(QAbstractSocket::SocketError socketError)
{
Q_UNUSED(socketError)
qDebug().noquote() << QString("Management server error: %1").arg(m_socket->errorString());
qDebug().noquote() << QString("Mananement server error: %1").arg(m_socket->errorString());
}
void ManagementServer::onSocketDisconnected()
@@ -176,21 +176,6 @@ 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;
@@ -312,7 +297,7 @@ void AndroidController::startActivityForResult(JNIEnv *env, jobject, jobject int
[](int receiverRequestCode, int resultCode,
const QJniObject& data) {
// Currently this function just used in
// VPNService.kt::checkPermissions. So the result
// VPNService.kt::checkPersmissions. 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 receive events body is an Ibinder
// Register an IBinder to recieve 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 Accepted our Binder
// The Service has Accecpted 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 transferred bytes to endpoint x.
// Contains the Current transfered bytes to endpoint x.
EVENT_STATISTIC_UPDATE = 3,
EVENT_BACKEND_LOGS = 4,
// An Error happened during activation
+3 -3
View File
@@ -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 *blurBackground = [[UIVisualEffectView alloc] initWithEffect: blurEffect];
[_screen addSubview: blurBackground];
blurBackground.frame = _screen.frame;
UIVisualEffectView *blurBackround = [[UIVisualEffectView alloc] initWithEffect: blurEffect];
[_screen addSubview: blurBackround];
blurBackround.frame = _screen.frame;
UIWindow *_window = UIApplication.sharedApplication.keyWindow;
[_window addSubview: _screen];
}
+1 -1
View File
@@ -130,7 +130,7 @@ Logger logger(LOG_IAP, "IOSIAPHandler");
logger.debug() << "transaction deferred";
break;
default:
logger.warning() << "transaction unknown state";
logger.warning() << "transaction unknwon state";
break;
}
}
+3 -3
View File
@@ -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 assign profile params for ss in tunnel"]))
userInfo: [NSLocalizedDescriptionKey: "Cannot asign 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 processed of \(protocols.first?.stringValue ?? "unknown") type")
wg_log(.error, message: "\(packets.count) outcoming packets proccessed 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 processed")
wg_log(.error, message: "\(packets.count) incoming packets proccessed")
self.packetFlow.writePackets(packets, withProtocols: [NSNumber(value: AF_INET)])
}, maxDatagrams: Int.max)
+1 -1
View File
@@ -166,7 +166,7 @@ class TunProvider: NSObject {
dispatchQueue.async {
let success = leaf_shutdown(self.tunId)
if !success {
let errMsg = "Tunnel cannot be stopped for some odd reason."
let errMsg = "Tunnel canot 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 changed by" << rtm->rtm_pid
logger.debug() << "Route chagned 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
<< "changed flags:" << ifm->ifm_flags
<< "chagned 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()<<"Receive the new event "<<static_cast<int>(rasconnstate);
qDebug()<<"Recive the new event "<<static_cast<int>(rasconnstate);
switch (rasconnstate)
{
case RASCS_OpenPort:
@@ -54,7 +54,7 @@ private:
private:
QJsonObject m_config;
//RAS functions and parameters
//RAS functions and parametrs
HRASCONN hRasConn{nullptr};
bool create_new_vpn(const QString & vpn_name,
const QString & serv_addr);
+1 -1
View File
@@ -36,7 +36,7 @@ public:
void cleanupBackendLogs();
signals:
void newTransmittedDataCount(quint64 rxBytes, quint64 txBytes);
void newTransmitedDataCount(quint64 rxBytes, quint64 txBytes);
protected slots:
+2 -2
View File
@@ -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::newTransmittedDataCount, this, &IOSVpnProtocol::setBytesChanged);
connect(this, &IOSVpnProtocol::newTransmitedDataCount, 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 newTransmittedDataCount(rxBytes, txBytes);
emit newTransmitedDataCount(rxBytes, txBytes);
}];
}
@@ -52,26 +52,25 @@ ErrorCode OpenVpnOverCloakProtocol::start()
args << "-u";
}
qDebug().noquote() << "OpenVpnOverCloakProtocol::start()"
<< cloakExecPath() << args.join(" ");
qDebug().noquote() << "OpenVpnOverCloakProtocol::start()" << cloakExecPath() << args.join(" ");
m_ckProcess.setProcessChannelMode(QProcess::MergedChannels);
m_ckProcess.setProgram(cloakExecPath());
m_ckProcess.setArguments(args);
connect(&m_ckProcess, &QProcess::readyReadStandardOutput, this, [this](){
connect(&m_ckProcess, &QProcess::readyReadStandardOutput, this, [this]() {
qDebug().noquote() << "ck-client:" << m_ckProcess.readAllStandardOutput();
});
m_errorHandlerConnection = connect(&m_ckProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus){
m_errorHandlerConnection = connect(&m_ckProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
qDebug().noquote() << "OpenVpnOverCloakProtocol finished, exitCode, exiStatus" << exitCode << exitStatus;
setConnectionState(VpnProtocol::Disconnected);
if (exitStatus != QProcess::NormalExit){
if (exitStatus != QProcess::NormalExit) {
emit protocolError(amnezia::ErrorCode::CloakExecutableCrashed);
stop();
}
if (exitCode !=0 ){
if (exitCode !=0 ) {
emit protocolError(amnezia::ErrorCode::InternalError);
stop();
}
@@ -84,8 +83,9 @@ ErrorCode OpenVpnOverCloakProtocol::start()
setConnectionState(VpnConnectionState::Connecting);
return OpenVpnProtocol::start();
} else {
return ErrorCode::CloakExecutableMissing;
}
else return ErrorCode::CloakExecutableMissing;
#endif
}
+12 -5
View File
@@ -75,6 +75,7 @@ QMap<amnezia::Proto, QString> ProtocolProps::protocolHumanNames()
{Proto::WireGuard, "WireGuard"},
{Proto::Ikev2, "IKEv2"},
{Proto::L2tp, "L2TP"},
{Proto::V2Ray, "V2Ray"},
{Proto::TorWebSite, "Web site in Tor network"},
{Proto::Dns, "DNS Service"},
@@ -96,10 +97,12 @@ amnezia::ServiceType ProtocolProps::protocolService(Proto p)
case Proto::Cloak : return ServiceType::Vpn;
case Proto::ShadowSocks : return ServiceType::Vpn;
case Proto::WireGuard : return ServiceType::Vpn;
case Proto::TorWebSite : return ServiceType::Other;
case Proto::V2Ray : return ServiceType::Vpn;
case Proto::TorWebSite : return ServiceType::Other;
case Proto::Dns : return ServiceType::Other;
case Proto::FileShare : return ServiceType::Other;
default: return ServiceType::Other;
default: return ServiceType::Other;
}
}
@@ -113,12 +116,13 @@ int ProtocolProps::defaultPort(Proto p)
case Proto::WireGuard : return 51820;
case Proto::Ikev2 : return -1;
case Proto::L2tp : return -1;
case Proto::V2Ray : return 10086;
case Proto::TorWebSite : return -1;
case Proto::Dns : return 53;
case Proto::FileShare : return 139;
case Proto::Sftp : return 222;
default: return -1;
default: return -1;
}
}
@@ -132,12 +136,13 @@ bool ProtocolProps::defaultPortChangeable(Proto p)
case Proto::WireGuard : return true;
case Proto::Ikev2 : return false;
case Proto::L2tp : return false;
case Proto::V2Ray : return true;
case Proto::TorWebSite : return true;
case Proto::Dns : return false;
case Proto::FileShare : return false;
default: return -1;
default: return -1;
}
}
@@ -151,6 +156,7 @@ TransportProto ProtocolProps::defaultTransportProto(Proto p)
case Proto::WireGuard : return TransportProto::Udp;
case Proto::Ikev2 : return TransportProto::Udp;
case Proto::L2tp : return TransportProto::Udp;
case Proto::V2Ray : return TransportProto::Tcp;
// non-vpn
case Proto::TorWebSite : return TransportProto::Tcp;
case Proto::Dns : return TransportProto::Udp;
@@ -169,12 +175,13 @@ bool ProtocolProps::defaultTransportProtoChangeable(Proto p)
case Proto::WireGuard : return false;
case Proto::Ikev2 : return false;
case Proto::L2tp : return false;
case Proto::V2Ray : return false;
// non-vpn
case Proto::TorWebSite : return false;
case Proto::Dns : return false;
case Proto::FileShare : return false;
case Proto::Sftp : return false;
default: return false;
default: return false;
}
}
+7 -1
View File
@@ -77,7 +77,6 @@ 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";
@@ -125,6 +124,12 @@ constexpr char serverPskKeyPath[] = "/opt/amnezia/wireguard/wireguard_psk.key";
}
namespace v2ray {
constexpr char v2rayKeyPath[] = "/opt/amnezia/v2ray/v2ray.key";
constexpr char defaultLocalPort[] = "1080";
constexpr char defaultServerPort[] = "10086";
}
namespace sftp {
constexpr char defaultUserName[] = "sftp_user";
@@ -149,6 +154,7 @@ enum Proto {
WireGuard,
Ikev2,
L2tp,
V2Ray,
// non-vpn
TorWebSite,
+10 -24
View File
@@ -9,7 +9,7 @@
#include <QJsonObject>
ShadowSocksVpnProtocol::ShadowSocksVpnProtocol(const QJsonObject &configuration, QObject *parent):
OpenVpnProtocol(configuration, parent)
OpenVpnProtocol(configuration, parent)
{
readShadowSocksConfiguration(configuration);
}
@@ -32,7 +32,6 @@ ErrorCode ShadowSocksVpnProtocol::start()
return lastError();
}
#ifndef Q_OS_IOS
if (Utils::processIsRunning(Utils::executable("ss-local", false))) {
Utils::killProcessByName(Utils::executable("ss-local", false));
@@ -48,30 +47,28 @@ ErrorCode ShadowSocksVpnProtocol::start()
#ifdef Q_OS_LINUX
QStringList args = QStringList() << "-c" << m_shadowSocksCfgFile.fileName();
#else
QStringList args = QStringList() << "-c" << m_shadowSocksCfgFile.fileName()
<< "--no-delay";
QStringList args = QStringList() << "-c" << m_shadowSocksCfgFile.fileName() << "--no-delay";
#endif
qDebug().noquote() << "ShadowSocksVpnProtocol::start()"
<< shadowSocksExecPath() << args.join(" ");
qDebug().noquote() << "ShadowSocksVpnProtocol::start()" << shadowSocksExecPath() << args.join(" ");
m_ssProcess.setProcessChannelMode(QProcess::MergedChannels);
m_ssProcess.setProgram(shadowSocksExecPath());
m_ssProcess.setArguments(args);
connect(&m_ssProcess, &QProcess::readyReadStandardOutput, this, [this](){
connect(&m_ssProcess, &QProcess::readyReadStandardOutput, this, [this]() {
qDebug().noquote() << "ss-local:" << m_ssProcess.readAllStandardOutput();
});
connect(&m_ssProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus){
connect(&m_ssProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
qDebug().noquote() << "ShadowSocksVpnProtocol finished, exitCode, exiStatus" << exitCode << exitStatus;
setConnectionState(VpnProtocol::Disconnected);
if (exitStatus != QProcess::NormalExit){
if (exitStatus != QProcess::NormalExit) {
emit protocolError(amnezia::ErrorCode::ShadowSocksExecutableCrashed);
stop();
}
if (exitCode !=0 ){
if (exitCode !=0 ) {
emit protocolError(amnezia::ErrorCode::InternalError);
stop();
}
@@ -84,8 +81,9 @@ ErrorCode ShadowSocksVpnProtocol::start()
setConnectionState(VpnConnectionState::Connecting);
return OpenVpnProtocol::start();
} else {
return ErrorCode::ShadowSocksExecutableMissing;
}
else return ErrorCode::ShadowSocksExecutableMissing;
#else
return ErrorCode::NotImplementedError;
#endif
@@ -116,17 +114,5 @@ QString ShadowSocksVpnProtocol::shadowSocksExecPath()
void ShadowSocksVpnProtocol::readShadowSocksConfiguration(const QJsonObject &configuration)
{
QJsonObject shadowSocksConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::ShadowSocks)).toObject();
bool isLocalPortConvertOk = false;
bool isServerPortConvertOk = false;
int localPort = shadowSocksConfig.value("local_port").toString().toInt(&isLocalPortConvertOk);
int serverPort = shadowSocksConfig.value("server_port").toString().toInt(&isServerPortConvertOk);
if (!isLocalPortConvertOk) {
qDebug() << "Error when converting local_port field in ShadowSocks config";
} else if (!isServerPortConvertOk) {
qDebug() << "Error when converting server_port field in ShadowSocks config";
}
shadowSocksConfig["local_port"] = localPort;
shadowSocksConfig["server_port"] = serverPort;
m_shadowSocksConfig = shadowSocksConfig;
m_shadowSocksConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::ShadowSocks)).toObject();
}
+5 -7
View File
@@ -1,9 +1,9 @@
#ifndef SHADOWSOCKSVPNPROTOCOL_H
#define SHADOWSOCKSVPNPROTOCOL_H
#include "openvpnprotocol.h"
#include "QProcess"
#include "containers/containers_defs.h"
#include "openvpnprotocol.h"
class ShadowSocksVpnProtocol : public OpenVpnProtocol
{
@@ -14,20 +14,18 @@ public:
ErrorCode start() override;
void stop() override;
protected:
void readShadowSocksConfiguration(const QJsonObject &configuration);
protected:
QJsonObject m_shadowSocksConfig;
private:
static QString shadowSocksExecPath();
void readShadowSocksConfiguration(const QJsonObject &configuration);
private:
#ifndef Q_OS_IOS
QProcess m_ssProcess;
#endif
QTemporaryFile m_shadowSocksCfgFile;
static QString shadowSocksExecPath();
};
#endif // SHADOWSOCKSVPNPROTOCOL_H
+108
View File
@@ -0,0 +1,108 @@
#include "v2rayprotocol.h"
#include <QThread>
#include <QFileInfo>
#include <QJsonDocument>
#include "utilities.h"
V2RayProtocol::V2RayProtocol(const QJsonObject &configuration, QObject *parent) : OpenVpnProtocol(configuration, parent)
{
writeV2RayConfiguration(configuration);
}
V2RayProtocol::~V2RayProtocol()
{
qDebug() << "V2RayProtocol::~V2RayProtocol";
V2RayProtocol::stop();
QThread::msleep(200);
}
ErrorCode V2RayProtocol::start()
{
#ifndef Q_OS_IOS
if (!QFileInfo::exists(v2RayExecPath())) {
setLastError(ErrorCode::V2RayExecutableMissing);
return lastError();
}
if (Utils::processIsRunning(Utils::executable("v2ray", false))) {
Utils::killProcessByName(Utils::executable("v2ray", false));
}
QStringList args = QStringList() << "-c" << m_v2RayConfigFile.fileName();
qDebug().noquote() << "V2RayProtocol::start()" << v2RayExecPath() << args.join(" ");
m_v2RayProcess.setProcessChannelMode(QProcess::MergedChannels);
m_v2RayProcess.setProgram(v2RayExecPath());
m_v2RayProcess.setArguments(args);
connect(&m_v2RayProcess, &QProcess::readyReadStandardOutput, this, [this]() {
qDebug().noquote() << "V2Ray:" << m_v2RayProcess.readAllStandardOutput();
});
connect(&m_v2RayProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
qDebug().noquote() << "V2RayProtocol finished, exitCode, exiStatus" << exitCode << exitStatus;
setConnectionState(VpnProtocol::Disconnected);
if (exitStatus != QProcess::NormalExit) {
emit protocolError(amnezia::ErrorCode::V2RayExecutableCrashed);
stop();
}
if (exitCode != 0 ) {
emit protocolError(amnezia::ErrorCode::InternalError);
stop();
}
});
m_v2RayProcess.start();
m_v2RayProcess.waitForStarted();
if (m_v2RayProcess.state() == QProcess::ProcessState::Running) {
setConnectionState(VpnConnectionState::Connecting);
return OpenVpnProtocol::start();
} else {
return ErrorCode::V2RayExecutableMissing;
}
#else
return ErrorCode::NotImplementedError;
#endif
return ErrorCode::NoError;
}
void V2RayProtocol::stop()
{
OpenVpnProtocol::stop();
qDebug() << "V2RayProtocol::stop()";
#ifndef Q_OS_IOS
m_v2RayProcess.terminate();
#endif
#ifdef Q_OS_WIN
Utils::signalCtrl(m_v2RayProcess.processId(), CTRL_C_EVENT);
#endif
}
void V2RayProtocol::writeV2RayConfiguration(const QJsonObject &configuration)
{
m_v2RayConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::V2Ray)).toObject();
#ifdef QT_DEBUG
m_v2RayConfigFile.setAutoRemove(false);
#endif
m_v2RayConfigFile.open();
m_v2RayConfigFile.write(QJsonDocument(m_v2RayConfig).toJson());
m_v2RayConfigFile.close();
}
const QString V2RayProtocol::v2RayExecPath() const
{
#ifdef Q_OS_WIN
return Utils::executable(QString("v2ray/v2ray"), true);
#else
return Utils::executable(QString("v2ray"), true);
#endif
}
+30
View File
@@ -0,0 +1,30 @@
#ifndef V2RAYPROTOCOL_H
#define V2RAYPROTOCOL_H
#include "QProcess"
#include "QTemporaryFile"
#include "openvpnprotocol.h"
class V2RayProtocol : public OpenVpnProtocol
{
public:
V2RayProtocol(const QJsonObject& configuration, QObject* parent = nullptr);
virtual ~V2RayProtocol() override;
ErrorCode start() override;
void stop() override;
private:
QJsonObject m_v2RayConfig;
QTemporaryFile m_v2RayConfigFile;
#ifndef Q_OS_IOS
QProcess m_v2RayProcess;
#endif
void writeV2RayConfiguration(const QJsonObject &configuration);
const QString v2RayExecPath() const;
};
#endif // V2RAYPROTOCOL_H
+2
View File
@@ -9,6 +9,7 @@
#include "shadowsocksvpnprotocol.h"
#include "openvpnovercloakprotocol.h"
#include "wireguardprotocol.h"
#include "v2rayprotocol.h"
#endif
#ifdef Q_OS_WINDOWS
@@ -114,6 +115,7 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject&
case DockerContainer::Cloak: return new OpenVpnOverCloakProtocol(configuration);
case DockerContainer::ShadowSocks: return new ShadowSocksVpnProtocol(configuration);
case DockerContainer::WireGuard: return new WireguardProtocol(configuration);
case DockerContainer::V2Ray: return new V2RayProtocol(configuration);
#endif
default: return nullptr;
}
+8 -5
View File
@@ -84,10 +84,6 @@
<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>
@@ -170,6 +166,13 @@
<file>ui/qml/Pages/PageAdvancedServerSettings.qml</file>
<file>ui/qml/Controls/PopupWarning.qml</file>
<file>ui/qml/Controls/PopupWithTextField.qml</file>
<file>server_scripts/check_user_in_sudo.sh</file>
<file>ui/qml/Pages/Protocols/PageProtoV2Ray.qml</file>
<file>server_scripts/openvpn_v2ray_vmess/configure_container.sh</file>
<file>server_scripts/openvpn_v2ray_vmess/Dockerfile</file>
<file>server_scripts/openvpn_v2ray_vmess/run_container.sh</file>
<file>server_scripts/openvpn_v2ray_vmess/start.sh</file>
<file>server_scripts/openvpn_v2ray_vmess/template.ovpn</file>
<file>server_scripts/openvpn_v2ray_vmess/template_v2ray_client.json</file>
<file>server_scripts/openvpn_shadowsocks/template_ss_client.json</file>
</qresource>
</RCC>
+3 -3
View File
@@ -334,14 +334,14 @@ class XCodeprojPatcher
def setup_target_gobridge(platform)
target_gobridge = legacy_target = @project.new(Xcodeproj::Project::PBXLegacyTarget)
bridge_platform = platform == 'ios' ? 'iOS' : 'macOS'
bridge_platofrm = 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_platform}>"
target_gobridge.product_name = "WireGuardGoBridge<#{bridge_platform}>"
target_gobridge.name = "WireGuardGoBridge<#{bridge_platofrm}>"
target_gobridge.product_name = "WireGuardGoBridge<#{bridge_platofrm}>"
@project.targets << target_gobridge
@target_extension.add_dependency target_gobridge
+1 -1
View File
@@ -1 +1 @@
sudo docker build -t $CONTAINER_NAME $DOCKERFILE_FOLDER --build-arg SERVER_ARCH=$(uname -m)
sudo docker build --network host -t $CONTAINER_NAME $DOCKERFILE_FOLDER --build-arg SERVER_ARCH=$(uname -m)
@@ -1,2 +0,0 @@
CUR_USER=$(whoami);\
groups $CUR_USER
@@ -18,7 +18,6 @@ user nobody
group nobody
persist-key
persist-tun
crl-verify /opt/amnezia/openvpn/crl.pem
status openvpn-status.log
verb 1
tls-server
@@ -21,6 +21,5 @@ 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 && easyrsa gen-crl;\
cd /opt/amnezia/openvpn && cp pki/crl.pem /opt/amnezia/openvpn/crl.pem'
cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn'
@@ -18,7 +18,6 @@ user nobody
group nobody
persist-key
persist-tun
crl-verify /opt/amnezia/openvpn/crl.pem
status openvpn-status.log
verb 1
tls-server
@@ -21,6 +21,4 @@ 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 && easyrsa gen-crl;\
cd /opt/amnezia/openvpn && cp pki/crl.pem /opt/amnezia/openvpn/crl.pem'
cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn'
@@ -18,7 +18,6 @@ user nobody
group nobody
persist-key
persist-tun
crl-verify /opt/amnezia/openvpn/crl.pem
status openvpn-status.log
verb 1
tls-server
@@ -21,6 +21,4 @@ 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 && easyrsa gen-crl;\
cd /opt/amnezia/openvpn && cp pki/crl.pem /opt/amnezia/openvpn/crl.pem'
cd /opt/amnezia/openvpn && cp pki/ca.crt pki/issued/AmneziaReq.crt pki/private/AmneziaReq.key /opt/amnezia/openvpn'
@@ -0,0 +1,8 @@
{
"local_port": $SHADOWSOCKS_LOCAL_PORT,
"method": "$SHADOWSOCKS_CIPHER",
"password": "$SHADOWSOCKS_PASSWORD",
"server": "$REMOTE_HOST",
"server_port": $SHADOWSOCKS_SERVER_PORT,
"timeout": 60
}
@@ -0,0 +1,51 @@
FROM alpine:3.15
LABEL maintainer="AmneziaVPN"
#Install required packages
RUN apk add --no-cache curl openvpn easy-rsa bash netcat-openbsd dumb-init rng-tools
RUN apk --update upgrade --no-cache
ENV EASYRSA_BATCH 1
ENV PATH="/usr/share/easy-rsa:${PATH}"
RUN mkdir -p /opt/amnezia
RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh
RUN chmod a+x /opt/amnezia/start.sh
RUN apk add --no-cache v2ray
# Tune network
RUN echo -e " \n\
fs.file-max = 51200 \n\
\n\
net.core.rmem_max = 67108864 \n\
net.core.wmem_max = 67108864 \n\
net.core.netdev_max_backlog = 250000 \n\
net.core.somaxconn = 4096 \n\
\n\
net.ipv4.tcp_syncookies = 1 \n\
net.ipv4.tcp_tw_reuse = 1 \n\
net.ipv4.tcp_tw_recycle = 0 \n\
net.ipv4.tcp_fin_timeout = 30 \n\
net.ipv4.tcp_keepalive_time = 1200 \n\
net.ipv4.ip_local_port_range = 10000 65000 \n\
net.ipv4.tcp_max_syn_backlog = 8192 \n\
net.ipv4.tcp_max_tw_buckets = 5000 \n\
net.ipv4.tcp_fastopen = 3 \n\
net.ipv4.tcp_mem = 25600 51200 102400 \n\
net.ipv4.tcp_rmem = 4096 87380 67108864 \n\
net.ipv4.tcp_wmem = 4096 65536 67108864 \n\
net.ipv4.tcp_mtu_probing = 1 \n\
net.ipv4.tcp_congestion_control = hybla \n\
# for low-latency network, use cubic instead \n\
# net.ipv4.tcp_congestion_control = cubic \n\
" | sed -e 's/^\s\+//g' | tee -a /etc/sysctl.conf && \
mkdir -p /etc/security && \
echo -e " \n\
* soft nofile 51200 \n\
* hard nofile 51200 \n\
" | sed -e 's/^\s\+//g' | tee -a /etc/security/limits.conf
ENTRYPOINT [ "dumb-init", "/opt/amnezia/start.sh" ]
CMD [ "" ]
@@ -0,0 +1,70 @@
cat > /opt/amnezia/openvpn/server.conf <<EOF
port $OPENVPN_PORT
proto $OPENVPN_TRANSPORT_PROTO
dev tun
ca /opt/amnezia/openvpn/ca.crt
cert /opt/amnezia/openvpn/AmneziaReq.crt
key /opt/amnezia/openvpn/AmneziaReq.key
dh /opt/amnezia/openvpn/dh.pem
server $OPENVPN_SUBNET_IP $OPENVPN_SUBNET_MASK
ifconfig-pool-persist ipp.txt
duplicate-cn
keepalive 10 120
$OPENVPN_NCP_DISABLE
cipher $OPENVPN_CIPHER
data-ciphers $OPENVPN_CIPHER
auth $OPENVPN_HASH
user nobody
group nobody
persist-key
persist-tun
status openvpn-status.log
verb 1
tls-server
tls-version-min 1.2
$OPENVPN_TLS_AUTH
$OPENVPN_ADDITIONAL_SERVER_CONFIG
EOF
# V2RAY_VMESS_PORT port for v2ray listening, for example 10086.
# V2RAY_VMESS_CLIENT_UUID client's id and secret as UUID.
# UUID is 32 hexadecimal digits /([0-9a-f]-?){32}/ (128 bit value).
mkdir -p /opt/amnezia/v2ray
cd /opt/amnezia/v2ray
V2RAY_VMESS_CLIENT_UUID=$(cat /proc/sys/kernel/random/uuid)
echo $V2RAY_VMESS_CLIENT_UUID > /opt/amnezia/v2ray/v2ray.key
cat > /opt/amnezia/v2ray/v2ray-server.json <<EOF
{
"log": {
"loglevel": "None"
},
"inbounds": [
{
"port": $V2RAY_VMESS_PORT,
"protocol": "vmess",
"settings": {
"clients": [
{
"id": "$V2RAY_VMESS_CLIENT_UUID",
"level": 1,
"alterId": 64
}
]
}
}
],
"outbounds": [
{
"protocol": "freedom",
"settings": {}
}
],
"policy": {
"levels": {
"0": {"uplinkOnly": 0}
}
}
}
EOF
@@ -0,0 +1,24 @@
# Run container
sudo docker run -d \
--log-driver none \
--restart always \
--cap-add=NET_ADMIN \
-p $V2RAY_VMESS_PORT:$V2RAY_VMESS_PORT/tcp \
--name $CONTAINER_NAME $CONTAINER_NAME
sudo docker network connect amnezia-dns-net $CONTAINER_NAME
# Create tun device if not exist
sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /dev/net; if [ ! -c /dev/net/tun ]; then mknod /dev/net/tun c 10 200; fi'
# Prevent to route packets outside of the container in case if server behind of the NAT
sudo docker exec -i $CONTAINER_NAME sh -c "ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up"
# OpenVPN config
sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /opt/amnezia/openvpn/clients; \
cd /opt/amnezia/openvpn && easyrsa init-pki; \
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'
@@ -0,0 +1,31 @@
#!/bin/bash
# This scripts copied from Amnezia client to Docker container to /opt/amnezia and launched every time container starts
echo "Container startup"
ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up
if [ ! -c /dev/net/tun ]; then mkdir -p /dev/net; mknod /dev/net/tun c 10 200; fi
# Allow traffic on the TUN interface.
iptables -A INPUT -i tun0 -j ACCEPT
iptables -A FORWARD -i tun0 -j ACCEPT
iptables -A OUTPUT -o tun0 -j ACCEPT
# Allow forwarding traffic only from the VPN.
iptables -A FORWARD -i tun0 -o eth0 -s $OPENVPN_SUBNET_IP/$OPENVPN_SUBNET_CIDR -j ACCEPT
iptables -A FORWARD -i tun0 -o eth1 -s $OPENVPN_SUBNET_IP/$OPENVPN_SUBNET_CIDR -j ACCEPT
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -t nat -A POSTROUTING -s $OPENVPN_SUBNET_IP/$OPENVPN_SUBNET_CIDR -o eth0 -j MASQUERADE
iptables -t nat -A POSTROUTING -s $OPENVPN_SUBNET_IP/$OPENVPN_SUBNET_CIDR -o eth1 -j MASQUERADE
# kill daemons in case of restart
killall -KILL openvpn
# start daemons if configured
if [ -f /opt/amnezia/openvpn/ca.crt ]; then (openvpn --config /opt/amnezia/openvpn/server.conf --daemon); fi
if [ -f /opt/amnezia/v2ray/v2ray-server.json ]; then (v2ray -config /opt/amnezia/v2ray/v2ray-server.json &); fi
tail -f /dev/null
@@ -0,0 +1,39 @@
client
dev tun
proto $OPENVPN_TRANSPORT_PROTO
resolv-retry infinite
nobind
persist-key
persist-tun
$OPENVPN_NCP_DISABLE
cipher $OPENVPN_CIPHER
auth $OPENVPN_HASH
verb 3
tls-client
tls-version-min 1.2
key-direction 1
remote-cert-tls server
redirect-gateway def1 bypass-dhcp
dhcp-option DNS $PRIMARY_DNS
dhcp-option DNS $SECONDARY_DNS
block-outside-dns
socks-proxy 127.0.0.1 $V2RAY_SOCKS_LOCAL_PORT
route $REMOTE_HOST 255.255.255.255 net_gateway
remote 127.0.0.1 $OPENVPN_PORT
$OPENVPN_ADDITIONAL_CLIENT_CONFIG
<ca>
$OPENVPN_CA_CERT
</ca>
<cert>
$OPENVPN_CLIENT_CERT
</cert>
<key>
$OPENVPN_PRIV_KEY
</key>
<tls-auth>
$OPENVPN_TA_KEY
</tls-auth>
@@ -0,0 +1,31 @@
{
"inbounds": [
{
"listen": "127.0.0.1",
"port": $V2RAY_SOCKS_LOCAL_PORT,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true
}
}
],
"outbounds": [
{
"protocol": "vmess",
"settings": {
"vnext": [
{
"address": "$REMOTE_HOST",
"port": $V2RAY_VMESS_PORT,
"users": [
{
"id": "$V2RAY_VMESS_CLIENT_UUID"
}
]
}
]
}
}
]
}
+3 -3
View File
@@ -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 &quot;For selected sites&quot; 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.
We recommend to enable VPN mode &quot;For selected sites&quot; 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.
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>Secondary DNS server</source>
<source>Secondray 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 chosen</source>
<source>VPN Protocol not choosen</source>
<translation>VPN протокол не выбран</translation>
</message>
<message>
-104
View File
@@ -1,104 +0,0 @@
#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;
}
-37
View File
@@ -1,37 +0,0 @@
#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
+1 -1
View File
@@ -93,7 +93,7 @@ void NotificationHandler::unsecuredNetworkNotification(const QString& networkNam
QString title = tr("AmneziaVPN notification");
QString message = tr("Unsecured network detected: ") + networkName;
QString message = tr("Unsucured network detected: ") + networkName;
notifyInternal(UnsecuredNetwork, title, message, 2000);
}

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