add openvpn connection logic draft

This commit is contained in:
Розов Никита Валерьевич 2021-10-09 20:17:19 +03:00
parent c057786011
commit 1ceee8901e
6 changed files with 527 additions and 322 deletions

View file

@ -14,8 +14,17 @@
<!-- %%INSERT_FEATURES --> <!-- %%INSERT_FEATURES -->
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/> <supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
<application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="-- %%INSERT_APP_NAME%% --" android:extractNativeLibs="true"> <application
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="-- %%INSERT_APP_NAME%% --" android:screenOrientation="unspecified" android:launchMode="singleTop"> android:hardwareAccelerated="true"
android:name="org.qtproject.qt5.android.bindings.QtApplication"
android:label="-- %%INSERT_APP_NAME%% --"
android:extractNativeLibs="true">
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
android:name="org.qtproject.qt5.android.bindings.QtActivity"
android:label="-- %%INSERT_APP_NAME%% --"
android:screenOrientation="unspecified"
android:launchMode="singleTop">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
@ -83,13 +92,14 @@
</activity> </activity>
<service android:name=".VPNService" <service android:name=".VPNService"
android:permission="android.permission.BIND_VPN_SERVICE"> android:permission="android.permission.BIND_VPN_SERVICE">
<intent-filter> <intent-filter>
<action android:name="android.net.VpnService"/> <action android:name="android.net.VpnService"/>
</intent-filter> </intent-filter>
</service> </service>
<service android:name="org.amnezia.vpn.qt.VPNPermissionHelper" <service android:name="org.amnezia.vpn.qt.VPNPermissionHelper"
android:process=":QtOnlyProcess"
android:permission="android.permission.BIND_VPN_SERVICE"> android:permission="android.permission.BIND_VPN_SERVICE">
</service> </service>

View file

@ -0,0 +1,113 @@
/* 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
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.system.OsConstants
import java.io.File
import com.wireguard.android.util.SharedLibraryLoader
import com.wireguard.config.*
import com.wireguard.crypto.Key
import org.json.JSONObject
import net.openvpn.ovpn3.ClientAPI_Config
import net.openvpn.ovpn3.ClientAPI_EvalConfig
import net.openvpn.ovpn3.ClientAPI_Event
import net.openvpn.ovpn3.ClientAPI_ExternalPKICertRequest
import net.openvpn.ovpn3.ClientAPI_ExternalPKISignRequest
import net.openvpn.ovpn3.ClientAPI_LogInfo
import net.openvpn.ovpn3.ClientAPI_OpenVPNClient
import net.openvpn.ovpn3.ClientAPI_ProvideCreds
import net.openvpn.ovpn3.ClientAPI_Status
import net.openvpn.ovpn3.ClientAPI_TransportStats
class OpenVPNThreadv3(var service: VPNService): ClientAPI_OpenVPNClient(), Runnable {
private val tag = "OpenVPNThreadv3"
private var mConfig: JSONObject? = null
private var mConnectionTime: Long = 0
private var mAlreadyInitialised = false
private var mService: VPNService = service
private var currentTunnelHandle = -1
override fun run() {
//TEMP
Log.i(tag, "run()")
val lConfigData: String = readFileDirectlyAsText("/data/local/tmp/osinit.ovpn")
val config: ClientAPI_Config = ClientAPI_Config()
config.content = lConfigData
val lCreds: ClientAPI_ProvideCreds = ClientAPI_ProvideCreds()
//username from config or GUI
lCreds.username = "username"
//password from config or GUI
lCreds.password = "password"
provide_creds(lCreds)
eval_config(config)
connect()
Log.i(tag, "connect()")
}
fun readFileDirectlyAsText(fileName: String): String = File(fileName).readText(Charsets.UTF_8)
override fun log(arg0: ClientAPI_LogInfo){
Log.i(tag, arg0.getText())
}
override fun event(event: ClientAPI_Event ){
Log.i(tag, event.getName())
}
// override fun reconnect() {
// reconnect(1);
// }
override fun tun_builder_new(): Boolean {
return true
}
override fun tun_builder_establish(): Int {
Log.v(tag, "tun_builder_establish")
return mService.turnOn(null)!!.detachFd()
}
override fun tun_builder_add_address(address: String , prefix_length: Int , gateway: String , ipv6:Boolean , net30: Boolean ): Boolean {
Log.v(tag, "tun_builder_add_address")
mService.addAddress(address, prefix_length)
return true
}
override fun tun_builder_add_route(address: String, prefix_length: Int, metric: Int, ipv6: Boolean): Boolean {
if (address.equals("remote_host"))
return false
mService.addRoute(address, prefix_length);
return true
}
override fun tun_builder_set_remote_address(address: String , ipv6: Boolean): Boolean {
mService.setMtu(1500)
return true
}
override fun tun_builder_set_mtu(mtu: Int): Boolean {
mService.setMtu(mtu)
return true
}
override fun tun_builder_add_dns_server(address: String , ipv6: Boolean): Boolean {
mService.addDNS(address)
return true
}
}

View file

@ -1,6 +1,6 @@
/* This Source Code Form is subject to the terms of the Mozilla Public /* 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 * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.amnezia.vpn package org.amnezia.vpn
@ -8,6 +8,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.os.ParcelFileDescriptor
import android.system.OsConstants import android.system.OsConstants
import com.wireguard.android.util.SharedLibraryLoader import com.wireguard.android.util.SharedLibraryLoader
import com.wireguard.config.* import com.wireguard.config.*
@ -20,7 +21,10 @@ class VPNService : android.net.VpnService() {
private var mConfig: JSONObject? = null private var mConfig: JSONObject? = null
private var mConnectionTime: Long = 0 private var mConnectionTime: Long = 0
private var mAlreadyInitialised = false private var mAlreadyInitialised = false
private var mbuilder: Builder = Builder()
private var mOpenVPNThreadv3: OpenVPNThreadv3? = null
private var currentTunnelHandle = -1 private var currentTunnelHandle = -1
fun init() { fun init() {
@ -29,9 +33,12 @@ class VPNService : android.net.VpnService() {
} }
Log.init(this) Log.init(this)
SharedLibraryLoader.loadSharedLibrary(this, "wg-go") SharedLibraryLoader.loadSharedLibrary(this, "wg-go")
Log.i(tag, "loaded lib") SharedLibraryLoader.loadSharedLibrary(this, "ovpn3")
Log.i(tag, "Loaded libs")
Log.e(tag, "Wireguard Version ${wgVersion()}") Log.e(tag, "Wireguard Version ${wgVersion()}")
mOpenVPNThreadv3 = OpenVPNThreadv3 (this)
mAlreadyInitialised = true mAlreadyInitialised = true
} }
override fun onUnbind(intent: Intent?): Boolean { override fun onUnbind(intent: Intent?): Boolean {
@ -44,9 +51,9 @@ class VPNService : android.net.VpnService() {
} }
/** /**
* EntryPoint for the Service, gets Called when AndroidController.cpp * EntryPoint for the Service, gets Called when AndroidController.cpp
* calles bindService. Returns the [VPNServiceBinder] so QT can send Requests to it. * calles bindService. Returns the [VPNServiceBinder] so QT can send Requests to it.
*/ */
override fun onBind(intent: Intent?): IBinder? { override fun onBind(intent: Intent?): IBinder? {
Log.v(tag, "Got Bind request") Log.v(tag, "Got Bind request")
init() init()
@ -54,34 +61,38 @@ class VPNService : android.net.VpnService() {
} }
/** /**
* Might be the entryPoint if the Service gets Started via an * Might be the entryPoint if the Service gets Started via an
* Service Intent: Might be from Always-On-Vpn from Settings * Service Intent: Might be from Always-On-Vpn from Settings
* or from Booting the device and having "connect on boot" enabled. * or from Booting the device and having "connect on boot" enabled.
*/ */
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
init() init()
intent?.let { // intent?.let {
if (intent.getBooleanExtra("startOnly", false)) { // if (intent.getBooleanExtra("startOnly", false)) {
Log.i(tag, "Start only!") // Log.i(tag, "Start only!")
return super.onStartCommand(intent, flags, startId) // return super.onStartCommand(intent, flags, startId)
} // }
} // }
// This start is from always-on // // This start is from always-on
if (this.mConfig == null) { // if (this.mConfig == null) {
// We don't have tunnel to turn on - Try to create one with last config the service got // // We don't have tunnel to turn on - Try to create one with last config the service got
val prefs = Prefs.get(this) // val prefs = Prefs.get(this)
val lastConfString = prefs.getString("lastConf", "") // val lastConfString = prefs.getString("lastConf", "")
if (lastConfString.isNullOrEmpty()) { // if (lastConfString.isNullOrEmpty()) {
// We have nothing to connect to -> Exit // // We have nothing to connect to -> Exit
Log.e( // Log.e(
tag, // tag,
"VPN service was triggered without defining a Server or having a tunnel" // "VPN service was triggered without defining a Server or having a tunnel"
) // )
return super.onStartCommand(intent, flags, startId) // return super.onStartCommand(intent, flags, startId)
} // }
this.mConfig = JSONObject(lastConfString) // this.mConfig = JSONObject(lastConfString)
} // }
turnOn(this.mConfig!!)
// Log.v(tag, "onStartCommand:" + this.mConfig)
// turnOn(this.mConfig)
return super.onStartCommand(intent, flags, startId) return super.onStartCommand(intent, flags, startId)
} }
@ -93,38 +104,38 @@ class VPNService : android.net.VpnService() {
} }
var connectionTime: Long = 0 var connectionTime: Long = 0
get() { get() {
return mConnectionTime return mConnectionTime
} }
var isUp: Boolean var isUp: Boolean
get() { get() {
return currentTunnelHandle >= 0 return currentTunnelHandle >= 0
} }
private set(value) { private set(value) {
if (value) { if (value) {
mBinder.dispatchEvent(VPNServiceBinder.EVENTS.connected, "") mBinder.dispatchEvent(VPNServiceBinder.EVENTS.connected, "")
mConnectionTime = System.currentTimeMillis() mConnectionTime = System.currentTimeMillis()
return return
}
mBinder.dispatchEvent(VPNServiceBinder.EVENTS.disconnected, "")
mConnectionTime = 0
} }
mBinder.dispatchEvent(VPNServiceBinder.EVENTS.disconnected, "")
mConnectionTime = 0
}
val status: JSONObject val status: JSONObject
get() { get() {
val deviceIpv4: String = "" val deviceIpv4: String = ""
return JSONObject().apply { return JSONObject().apply {
putOpt("rx_bytes", getConfigValue("rx_bytes")) putOpt("rx_bytes", getConfigValue("rx_bytes"))
putOpt("tx_bytes", getConfigValue("tx_bytes")) putOpt("tx_bytes", getConfigValue("tx_bytes"))
putOpt("endpoint", mConfig?.getJSONObject("server")?.getString("ipv4Gateway")) putOpt("endpoint", mConfig?.getJSONObject("server")?.getString("ipv4Gateway"))
putOpt("deviceIpv4", mConfig?.getJSONObject("device")?.getString("ipv4Address")) putOpt("deviceIpv4", mConfig?.getJSONObject("device")?.getString("ipv4Address"))
}
} }
/* }
* Checks if the VPN Permission is given. /*
* If the permission is given, returns true * Checks if the VPN Permission is given.
* Requests permission and returns false if not. * If the permission is given, returns true
*/ * Requests permission and returns false if not.
*/
fun checkPermissions(): Boolean { fun checkPermissions(): Boolean {
// See https://developer.android.com/guide/topics/connectivity/vpn#connect_a_service // See https://developer.android.com/guide/topics/connectivity/vpn#connect_a_service
// Call Prepare, if we get an Intent back, we dont have the VPN Permission // Call Prepare, if we get an Intent back, we dont have the VPN Permission
@ -138,66 +149,116 @@ class VPNService : android.net.VpnService() {
return false return false
} }
fun turnOn(json: JSONObject) { fun turnOn(json: JSONObject?): ParcelFileDescriptor? {
Log.sensitive(tag, json.toString()) Log.sensitive(tag, "" + json.toString())
val wireguard_conf = buildWireugardConfig(json) // val wireguard_conf = buildWireugardConfig(json)
if (!checkPermissions()) { if (!checkPermissions()) {
Log.e(tag, "turn on was called without no permissions present!") Log.e(tag, "turn on was called without no permissions present!")
isUp = false isUp = false
return return null
} }
Log.i(tag, "Permission okay") // Log.i(tag, "Permission okay")
if (currentTunnelHandle != -1) { // if (currentTunnelHandle != -1) {
Log.e(tag, "Tunnel already up") // Log.e(tag, "Tunnel already up")
// Turn the tunnel down because this might be a switch // // Turn the tunnel down because this might be a switch
wgTurnOff(currentTunnelHandle) // wgTurnOff(currentTunnelHandle)
} // }
val wgConfig: String = wireguard_conf!!.toWgUserspaceString() // val wgConfig: String = wireguard_conf!!.toWgUserspaceString()
val builder = Builder() // val builder = Builder()
setupBuilder(wireguard_conf, builder) // setupBuilder(wireguard_conf, builder)
builder.setSession("mvpn0") // builder.setSession("mvpn0")
builder.establish().use { tun -> // builder.establish().use { tun ->
if (tun == null)return // if (tun == null)return
Log.i(tag, "Go backend " + wgVersion()) // Log.i(tag, "Go backend " + wgVersion())
currentTunnelHandle = wgTurnOn("mvpn0", tun.detachFd(), wgConfig) // currentTunnelHandle = wgTurnOn("mvpn0", tun.detachFd(), wgConfig)
} // }
if (currentTunnelHandle < 0) { // if (currentTunnelHandle < 0) {
Log.e(tag, "Activation Error Code -> $currentTunnelHandle") // Log.e(tag, "Activation Error Code -> $currentTunnelHandle")
isUp = false // isUp = false
return // return
} // }
protect(wgGetSocketV4(currentTunnelHandle)) // protect(wgGetSocketV4(currentTunnelHandle))
protect(wgGetSocketV6(currentTunnelHandle)) // protect(wgGetSocketV6(currentTunnelHandle))
mConfig = json // mConfig = json
isUp = true // isUp = true
// Store the config in case the service gets // // Store the config in case the service gets
// asked boot vpn from the OS // // asked boot vpn from the OS
val prefs = Prefs.get(this) // val prefs = Prefs.get(this)
prefs.edit() // prefs.edit()
.putString("lastConf", json.toString()) // .putString("lastConf", json.toString())
.apply() // .apply()
// NotificationUtil.show(this) // Go foreground
// val builder = Builder()
// // setupBuilder(wireguard_conf, builder)
// builder.addAddress("192.168.194.1", 24)
// builder.addDnsServer("8.8.8.8")
// builder.addRoute("0.0.0.0", 0)
// builder.setSession("mvpn0")
// builder.establish()
// Configure a new interface from our VpnService instance. This must be done
// from inside a VpnService.
// val builder = Builder()
// Create a local TUN interface using predetermined addresses. In your app,
// you typically use values returned from the VPN gateway during handshaking.
// val localTunnel = builder
// .addAddress("10.0.20.1", 0)
// .addRoute("192.168.111.0", 24)
// .addDnsServer("192.168.111.1")
// .establish()
val localTunnel = mbuilder.establish()
Log.v(tag, "builder.establish()")
startOpenVpn()
return localTunnel
NotificationUtil.show(this) // Go foreground
} }
fun setMtu(mtu: Int) {
Log.v(tag, "setMtu()" + mtu)
mbuilder.setMtu(mtu)
}
fun addAddress(ip: String, len: Int){
Log.v(tag, "addAddress()" + ip + " " + len)
mbuilder.addAddress(ip, len)
}
fun addRoute(ip: String, len: Int){
Log.v(tag, "addRoute()" + ip + " " + len)
mbuilder.addRoute(ip, len)
}
fun addDNS(ip: String){
mbuilder.addDnsServer(ip)
}
fun turnOff() { fun turnOff() {
Log.v(tag, "Try to disable tunnel") Log.v(tag, "Try to disable tunnel")
wgTurnOff(currentTunnelHandle) // wgTurnOff(currentTunnelHandle)
currentTunnelHandle = -1 // currentTunnelHandle = -1
stopForeground(false) // stopForeground(false)
isUp = false isUp = false
} }
/** /**
* Configures an Android VPN Service Tunnel * Configures an Android VPN Service Tunnel
* with a given Wireguard Config * with a given Wireguard Config
*/ */
private fun setupBuilder(config: Config, builder: Builder) { private fun setupBuilder(config: Config, builder: Builder) {
// Setup Split tunnel // Setup Split tunnel
for (excludedApplication in config.`interface`.excludedApplications) for (excludedApplication in config.`interface`.excludedApplications)
builder.addDisallowedApplication(excludedApplication) builder.addDisallowedApplication(excludedApplication)
// Device IP // Device IP
for (addr in config.`interface`.addresses) builder.addAddress(addr.address, addr.mask) for (addr in config.`interface`.addresses) builder.addAddress(addr.address, addr.mask)
@ -220,9 +281,9 @@ class VPNService : android.net.VpnService() {
} }
/** /**
* Gets config value for {key} from the Current * Gets config value for {key} from the Current
* running Wireguard tunnel * running Wireguard tunnel
*/ */
private fun getConfigValue(key: String): String? { private fun getConfigValue(key: String): String? {
if (!isUp) { if (!isUp) {
return null return null
@ -241,15 +302,15 @@ class VPNService : android.net.VpnService() {
} }
/** /**
* Create a Wireguard [Config] from a [json] string - * Create a Wireguard [Config] from a [json] string -
* The [json] will be created in AndroidController.cpp * The [json] will be created in AndroidVpnProtocol.cpp
*/ */
private fun buildWireugardConfig(obj: JSONObject): Config { private fun buildWireugardConfig(obj: JSONObject): Config {
val confBuilder = Config.Builder() val confBuilder = Config.Builder()
val jServer = obj.getJSONObject("server") val jServer = obj.getJSONObject("server")
val peerBuilder = Peer.Builder() val peerBuilder = Peer.Builder()
val ep = val ep =
InetEndpoint.parse(jServer.getString("ipv4AddrIn") + ":" + jServer.getString("port")) InetEndpoint.parse(jServer.getString("ipv4AddrIn") + ":" + jServer.getString("port"))
peerBuilder.setEndpoint(ep) peerBuilder.setEndpoint(ep)
peerBuilder.setPublicKey(Key.fromBase64(jServer.getString("publicKey"))) peerBuilder.setPublicKey(Key.fromBase64(jServer.getString("publicKey")))
@ -257,53 +318,61 @@ class VPNService : android.net.VpnService() {
if (jAllowedIPList.length() == 0) { if (jAllowedIPList.length() == 0) {
val internet = InetNetwork.parse("0.0.0.0/0") // aka The whole internet. val internet = InetNetwork.parse("0.0.0.0/0") // aka The whole internet.
peerBuilder.addAllowedIp(internet) peerBuilder.addAllowedIp(internet)
} else { } else {
(0 until jAllowedIPList.length()).toList().forEach { (0 until jAllowedIPList.length()).toList().forEach {
val network = InetNetwork.parse(jAllowedIPList.getString(it)) val network = InetNetwork.parse(jAllowedIPList.getString(it))
peerBuilder.addAllowedIp(network) peerBuilder.addAllowedIp(network)
}
}
confBuilder.addPeer(peerBuilder.build())
val privateKey = obj.getJSONObject("keys").getString("privateKey")
val jDevice = obj.getJSONObject("device")
val ifaceBuilder = Interface.Builder()
ifaceBuilder.parsePrivateKey(privateKey)
ifaceBuilder.addAddress(InetNetwork.parse(jDevice.getString("ipv4Address")))
ifaceBuilder.addAddress(InetNetwork.parse(jDevice.getString("ipv6Address")))
ifaceBuilder.addDnsServer(InetNetwork.parse(obj.getString("dns")).address)
val jExcludedApplication = obj.getJSONArray("excludedApps")
(0 until jExcludedApplication.length()).toList().forEach {
val appName = jExcludedApplication.get(it).toString()
ifaceBuilder.excludeApplication(appName)
}
confBuilder.setInterface(ifaceBuilder.build())
return confBuilder.build()
}
companion object {
@JvmStatic
fun startService(c: Context) {
c.applicationContext.startService(
Intent(c.applicationContext, VPNService::class.java).apply {
putExtra("startOnly", true)
} }
) }
confBuilder.addPeer(peerBuilder.build())
val privateKey = obj.getJSONObject("keys").getString("privateKey")
val jDevice = obj.getJSONObject("device")
val ifaceBuilder = Interface.Builder()
ifaceBuilder.parsePrivateKey(privateKey)
ifaceBuilder.addAddress(InetNetwork.parse(jDevice.getString("ipv4Address")))
ifaceBuilder.addAddress(InetNetwork.parse(jDevice.getString("ipv6Address")))
ifaceBuilder.addDnsServer(InetNetwork.parse(obj.getString("dns")).address)
val jExcludedApplication = obj.getJSONArray("excludedApps")
(0 until jExcludedApplication.length()).toList().forEach {
val appName = jExcludedApplication.get(it).toString()
ifaceBuilder.excludeApplication(appName)
}
confBuilder.setInterface(ifaceBuilder.build())
return confBuilder.build()
} }
@JvmStatic
private external fun wgGetConfig(handle: Int): String? private fun startOpenVpn() {
@JvmStatic Thread ({
private external fun wgGetSocketV4(handle: Int): Int mOpenVPNThreadv3?.run()
@JvmStatic }).start()
private external fun wgGetSocketV6(handle: Int): Int Log.i(tag, "OpenVPNThreadv3 start")
@JvmStatic }
private external fun wgTurnOff(handle: Int)
@JvmStatic companion object {
private external fun wgTurnOn(ifName: String, tunFd: Int, settings: String): Int @JvmStatic
@JvmStatic fun startService(c: Context) {
private external fun wgVersion(): String? c.applicationContext.startService(
} Intent(c.applicationContext, VPNService::class.java).apply {
} putExtra("startOnly", true)
}
)
}
@JvmStatic
private external fun wgGetConfig(handle: Int): String?
@JvmStatic
private external fun wgGetSocketV4(handle: Int): Int
@JvmStatic
private external fun wgGetSocketV6(handle: Int): Int
@JvmStatic
private external fun wgTurnOff(handle: Int)
@JvmStatic
private external fun wgTurnOn(ifName: String, tunFd: Int, settings: String): Int
@JvmStatic
private external fun wgVersion(): String?
}
}

View file

@ -1,6 +1,6 @@
/* This Source Code Form is subject to the terms of the Mozilla Public /* 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 * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.amnezia.vpn package org.amnezia.vpn
import android.os.Binder import android.os.Binder
@ -19,8 +19,8 @@ class VPNServiceBinder(service: VPNService) : Binder() {
private var mResumeConfig: JSONObject? = null private var mResumeConfig: JSONObject? = null
/** /**
* The codes this Binder does accept in [onTransact] * The codes this Binder does accept in [onTransact]
*/ */
object ACTIONS { object ACTIONS {
const val activate = 1 const val activate = 1
const val deactivate = 2 const val deactivate = 2
@ -31,17 +31,18 @@ class VPNServiceBinder(service: VPNService) : Binder() {
const val resumeActivate = 7 const val resumeActivate = 7
const val setNotificationText = 8 const val setNotificationText = 8
const val setFallBackNotification = 9 const val setFallBackNotification = 9
const val myLog = 10
} }
/** /**
* Gets called when the VPNServiceBinder gets a request from a Client. * Gets called when the VPNServiceBinder gets a request from a Client.
* The [code] determines what action is requested. - see [ACTIONS] * The [code] determines what action is requested. - see [ACTIONS]
* [data] may contain a utf-8 encoded json string with optional args or is null. * [data] may contain a utf-8 encoded json string with optional args or is null.
* [reply] is a pointer to a buffer in the clients memory, to reply results. * [reply] is a pointer to a buffer in the clients memory, to reply results.
* we use this to send result data. * we use this to send result data.
* *
* returns true if the [code] was accepted * returns true if the [code] was accepted
*/ */
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean { override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
Log.i(tag, "GOT TRANSACTION $code") Log.i(tag, "GOT TRANSACTION $code")
@ -49,122 +50,124 @@ class VPNServiceBinder(service: VPNService) : Binder() {
ACTIONS.activate -> { ACTIONS.activate -> {
try { try {
Log.i(tag, "Activiation Requested, parsing Config") Log.i(tag, "Activiation Requested, parsing Config")
// [data] is here a json containing the wireguard conf // [data] is here a json containing the wireguard/openvpn conf
val buffer = data.createByteArray() val buffer = data.createByteArray()
val json = buffer?.let { String(it) } val json = buffer?.let { String(it) }
val config = JSONObject(json) val config = JSONObject(json)
Log.v(tag, "config: " + config.toString())
Log.v(tag, "Stored new Tunnel config in Service") Log.v(tag, "Stored new Tunnel config in Service")
if (!mService.checkPermissions()) { if (!mService.checkPermissions()) {
mResumeConfig = config mResumeConfig = config
// The Permission prompt was already // The Permission prompt was already
// send, in case it's accepted we will // send, in case it's accepted we will
// receive ACTIONS.resumeActivate // receive ACTIONS.resumeActivate
return true return true
} }
this.mService.turnOn(config) this.mService.turnOn(config)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(tag, "An Error occurred while enabling the VPN: ${e.localizedMessage}") Log.e(tag, "An Error occurred while enabling the VPN: ${e.localizedMessage}")
dispatchEvent(EVENTS.activationError, e.localizedMessage) dispatchEvent(EVENTS.activationError, e.localizedMessage)
}
return true
} }
return true
}
ACTIONS.resumeActivate -> { ACTIONS.resumeActivate -> {
// [data] is empty // [data] is empty
// Activate the current tunnel // Activate the current tunnel
try { try {
mResumeConfig?.let { this.mService.turnOn(it) } mResumeConfig?.let { this.mService.turnOn(it) }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(tag, "An Error occurred while enabling the VPN: ${e.localizedMessage}") Log.e(tag, "An Error occurred while enabling the VPN: ${e.localizedMessage}")
}
return true
}
ACTIONS.deactivate -> {
// [data] here is empty
this.mService.turnOff()
return true
}
ACTIONS.registerEventListener -> {
// [data] contains the Binder that we need to dispatch the Events
val binder = data.readStrongBinder()
mListener = binder
val obj = JSONObject()
obj.put("connected", mService.isUp)
obj.put("time", mService.connectionTime)
dispatchEvent(EVENTS.init, obj.toString())
Log.i(tag, "ACTIONS.registerEventListener")
return true
}
ACTIONS.requestStatistic -> {
dispatchEvent(EVENTS.statisticUpdate, mService.status.toString())
return true
}
ACTIONS.requestGetLog -> {
// Grabs all the Logs and dispatch new Log Event
dispatchEvent(EVENTS.backendLogs, Log.getContent())
return true
}
ACTIONS.requestCleanupLog -> {
Log.clearFile()
return true
}
ACTIONS.setNotificationText -> {
NotificationUtil.update(data)
return true
}
ACTIONS.setFallBackNotification -> {
// NotificationUtil.saveFallBackMessage(data, mService)
return true
}
IBinder.LAST_CALL_TRANSACTION -> {
Log.e(tag, "The OS Requested to shut down the VPN")
this.mService.turnOff()
return true
}
else -> {
Log.e(tag, "Received invalid bind request \t Code -> $code")
// If we're hitting this there is probably something wrong in the client.
return false
}
} }
return true
}
ACTIONS.deactivate -> {
// [data] here is empty
this.mService.turnOff()
return true
}
ACTIONS.registerEventListener -> {
// [data] contains the Binder that we need to dispatch the Events
val binder = data.readStrongBinder()
mListener = binder
val obj = JSONObject()
obj.put("connected", mService.isUp)
obj.put("time", mService.connectionTime)
dispatchEvent(EVENTS.init, obj.toString())
return true
}
ACTIONS.requestStatistic -> {
dispatchEvent(EVENTS.statisticUpdate, mService.status.toString())
return true
}
ACTIONS.requestGetLog -> {
// Grabs all the Logs and dispatch new Log Event
dispatchEvent(EVENTS.backendLogs, Log.getContent())
return true
}
ACTIONS.requestCleanupLog -> {
Log.clearFile()
return true
}
ACTIONS.setNotificationText -> {
NotificationUtil.update(data)
return true
}
ACTIONS.setFallBackNotification -> {
NotificationUtil.saveFallBackMessage(data, mService)
return true
}
IBinder.LAST_CALL_TRANSACTION -> {
Log.e(tag, "The OS Requested to shut down the VPN")
this.mService.turnOff()
return true
}
else -> {
Log.e(tag, "Received invalid bind request \t Code -> $code")
// If we're hitting this there is probably something wrong in the client.
return false return false
} }
}
return false
}
/** /**
* Dispatches an Event to all registered Binders * Dispatches an Event to all registered Binders
* [code] the Event that happened - see [EVENTS] * [code] the Event that happened - see [EVENTS]
* To register an Eventhandler use [onTransact] with * To register an Eventhandler use [onTransact] with
* [ACTIONS.registerEventListener] * [ACTIONS.registerEventListener]
*/ */
fun dispatchEvent(code: Int, payload: String?) { fun dispatchEvent(code: Int, payload: String?) {
try { try {
mListener?.let { mListener?.let {
if (it.isBinderAlive) { if (it.isBinderAlive) {
val data = Parcel.obtain() val data = Parcel.obtain()
data.writeByteArray(payload?.toByteArray(charset("UTF-8"))) data.writeByteArray(payload?.toByteArray(charset("UTF-8")))
it.transact(code, data, Parcel.obtain(), 0) it.transact(code, data, Parcel.obtain(), 0)
}
}
} catch (e: DeadObjectException) {
// If the QT Process is killed (not just inactive)
// we cant access isBinderAlive, so nothing to do here.
}
}
/**
* The codes we Are Using in case of [dispatchEvent]
*/
object EVENTS {
const val init = 0
const val connected = 1
const val disconnected = 2
const val statisticUpdate = 3
const val backendLogs = 4
const val activationError = 5
} }
} }
} catch (e: DeadObjectException) {
// If the QT Process is killed (not just inactive)
// we cant access isBinderAlive, so nothing to do here.
}
}
/**
* The codes we Are Using in case of [dispatchEvent]
*/
object EVENTS {
const val init = 0
const val connected = 1
const val disconnected = 2
const val statisticUpdate = 3
const val backendLogs = 4
const val activationError = 5
}
}

View file

@ -6,14 +6,18 @@ package org.amnezia.vpn.qt
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.util.Log
class VPNPermissionHelper : android.net.VpnService() { class VPNPermissionHelper : android.net.VpnService() {
private val tag = "VPNPermissionHelper"
/** /**
* This small service does nothing else then checking if the vpn permission * This small service does nothing else then checking if the vpn permission
* is present and prompting if not. * is present and prompting if not.
*/ */
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val intent = prepare(this.applicationContext) val intent = prepare(this.applicationContext)
Log.i(tag, "VPNPermissionHelper onStartCommand")
if (intent != null) { if (intent != null) {
startActivityForResult(intent) startActivityForResult(intent)
} }

View file

@ -271,63 +271,69 @@ ErrorCode VpnConnection::connectToVpn(int serverIndex,
m_vpnProtocol.reset(); m_vpnProtocol.reset();
} }
if (container == DockerContainer::None || container == DockerContainer::OpenVpn) { // if (container == DockerContainer::None || container == DockerContainer::OpenVpn) {
ErrorCode e = createVpnConfiguration(serverIndex, credentials, DockerContainer::OpenVpn, containerConfig); // ErrorCode e = createVpnConfiguration(serverIndex, credentials, DockerContainer::OpenVpn, containerConfig);
if (e) { // if (e) {
emit connectionStateChanged(VpnProtocol::Error); // emit connectionStateChanged(VpnProtocol::Error);
return e; // return e;
} // }
m_vpnProtocol.reset(new OpenVpnProtocol(m_vpnConfiguration)); // m_vpnProtocol.reset(new OpenVpnProtocol(m_vpnConfiguration));
e = static_cast<OpenVpnProtocol *>(m_vpnProtocol.data())->checkAndSetupTapDriver(); // e = static_cast<OpenVpnProtocol *>(m_vpnProtocol.data())->checkAndSetupTapDriver();
if (e) { // if (e) {
emit connectionStateChanged(VpnProtocol::Error); // emit connectionStateChanged(VpnProtocol::Error);
return e; // return e;
} // }
} // }
else if (container == DockerContainer::ShadowSocks) { // else if (container == DockerContainer::ShadowSocks) {
ErrorCode e = createVpnConfiguration(serverIndex, credentials, DockerContainer::ShadowSocks, containerConfig); // ErrorCode e = createVpnConfiguration(serverIndex, credentials, DockerContainer::ShadowSocks, containerConfig);
if (e) { // if (e) {
emit connectionStateChanged(VpnProtocol::Error); // emit connectionStateChanged(VpnProtocol::Error);
return e; // return e;
} // }
m_vpnProtocol.reset(new ShadowSocksVpnProtocol(m_vpnConfiguration)); // m_vpnProtocol.reset(new ShadowSocksVpnProtocol(m_vpnConfiguration));
e = static_cast<OpenVpnProtocol *>(m_vpnProtocol.data())->checkAndSetupTapDriver(); // e = static_cast<OpenVpnProtocol *>(m_vpnProtocol.data())->checkAndSetupTapDriver();
if (e) { // if (e) {
emit connectionStateChanged(VpnProtocol::Error); // emit connectionStateChanged(VpnProtocol::Error);
return e; // return e;
} // }
} // }
else if (container == DockerContainer::Cloak) { // else if (container == DockerContainer::Cloak) {
ErrorCode e = createVpnConfiguration(serverIndex, credentials, DockerContainer::Cloak, containerConfig); // ErrorCode e = createVpnConfiguration(serverIndex, credentials, DockerContainer::Cloak, containerConfig);
if (e) { // if (e) {
emit connectionStateChanged(VpnProtocol::Error); // emit connectionStateChanged(VpnProtocol::Error);
return e; // return e;
} // }
m_vpnProtocol.reset(new OpenVpnOverCloakProtocol(m_vpnConfiguration)); // m_vpnProtocol.reset(new OpenVpnOverCloakProtocol(m_vpnConfiguration));
e = static_cast<OpenVpnProtocol *>(m_vpnProtocol.data())->checkAndSetupTapDriver(); // e = static_cast<OpenVpnProtocol *>(m_vpnProtocol.data())->checkAndSetupTapDriver();
if (e) { // if (e) {
emit connectionStateChanged(VpnProtocol::Error); // emit connectionStateChanged(VpnProtocol::Error);
return e; // return e;
} // }
} // }
else if (container == DockerContainer::WireGuard) { // else if (container == DockerContainer::WireGuard) {
ErrorCode e = createVpnConfiguration(serverIndex, credentials, DockerContainer::WireGuard, containerConfig); // ErrorCode e = createVpnConfiguration(serverIndex, credentials, DockerContainer::WireGuard, containerConfig);
if (e) { // if (e) {
emit connectionStateChanged(VpnProtocol::Error); // emit connectionStateChanged(VpnProtocol::Error);
return e; // return e;
} // }
#ifdef Q_OS_ANDROID //#ifdef Q_OS_ANDROID
AndroidVpnProtocol *androidVpnProtocol = new AndroidVpnProtocol(Protocol::WireGuard, m_vpnConfiguration); // AndroidVpnProtocol *androidVpnProtocol = new AndroidVpnProtocol(Protocol::WireGuard, m_vpnConfiguration);
androidVpnProtocol->initialize(); // androidVpnProtocol->initialize();
m_vpnProtocol.reset(androidVpnProtocol); // m_vpnProtocol.reset(androidVpnProtocol);
#else //#else
m_vpnProtocol.reset(new WireguardProtocol(m_vpnConfiguration)); // m_vpnProtocol.reset(new WireguardProtocol(m_vpnConfiguration));
#endif //#endif
} // }
AndroidVpnProtocol *androidVpnProtocol = new AndroidVpnProtocol(Protocol::OpenVpn, m_vpnConfiguration);
androidVpnProtocol->initialize();
m_vpnProtocol.reset(androidVpnProtocol);
connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError); connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(VpnProtocol::ConnectionState)), this, SLOT(onConnectionStateChanged(VpnProtocol::ConnectionState))); connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(VpnProtocol::ConnectionState)), this, SLOT(onConnectionStateChanged(VpnProtocol::ConnectionState)));