summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2019-05-29 11:50:07 -0300
committerJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2019-05-29 11:50:07 -0300
commit6188daf9e1d3446084dcb2e11efd42806c79e0ab (patch)
tree58c4784ca316b8263f1596c475b8cc7f23e7d516
parent0eb13af5be6a204f9c94783bd16498101bfbb97d (diff)
parentdcb94332ab4b9f881df1ba2fcf5d1ef449704fd4 (diff)
downloadmullvadvpn-6188daf9e1d3446084dcb2e11efd42806c79e0ab.tar.xz
mullvadvpn-6188daf9e1d3446084dcb2e11efd42806c79e0ab.zip
Merge branch 'vpn-service-tun-provider'
-rw-r--r--Cargo.lock1
-rw-r--r--android/src/main/AndroidManifest.xml9
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt30
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt2
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt6
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadVpnService.kt79
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/model/InetNetwork.kt5
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/model/TunConfig.kt10
-rw-r--r--mullvad-jni/Cargo.toml1
-rw-r--r--mullvad-jni/src/into_java.rs90
-rw-r--r--mullvad-jni/src/lib.rs53
-rw-r--r--mullvad-jni/src/vpn_service_tun_provider.rs150
-rw-r--r--talpid-core/src/tunnel/tun_provider/mod.rs8
13 files changed, 419 insertions, 25 deletions
diff --git a/Cargo.lock b/Cargo.lock
index d6232ef395..21b6d12bae 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1166,6 +1166,7 @@ version = "0.1.0"
dependencies = [
"err-derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ipnetwork 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
"jni 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-client-core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
index 450c316618..d025f278a5 100644
--- a/android/src/main/AndroidManifest.xml
+++ b/android/src/main/AndroidManifest.xml
@@ -22,5 +22,14 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
+
+ <service
+ android:name=".MullvadVpnService$InnerVpnService"
+ android:permission="android.permission.BIND_VPN_SERVICE"
+ >
+ <intent-filter>
+ <action android:name="android.net.VpnService" />
+ </intent-filter>
+ </service>
</application>
</manifest>
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt
index 4a853b2f7b..b288ecfdbc 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt
@@ -7,7 +7,10 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
+import android.app.Activity
import android.content.Context
+import android.content.Intent
+import android.net.VpnService
import android.os.Bundle
import android.os.Handler
import android.support.v4.app.Fragment
@@ -24,7 +27,10 @@ class ConnectFragment : Fragment() {
private lateinit var notificationBanner: NotificationBanner
private lateinit var status: ConnectionStatus
+ private lateinit var parentActivity: MainActivity
+
private var daemon = CompletableDeferred<MullvadDaemon>()
+ private var vpnPermission = CompletableDeferred<Unit>()
private var generateWireguardKeyJob = generateWireguardKey()
@@ -36,7 +42,8 @@ class ConnectFragment : Fragment() {
override fun onAttach(context: Context) {
super.onAttach(context)
- waitForDaemonJob = waitForDaemon((context as MainActivity).asyncDaemon)
+ parentActivity = context as MainActivity
+ waitForDaemonJob = waitForDaemon(parentActivity.asyncDaemon)
}
override fun onCreateView(
@@ -75,6 +82,12 @@ class ConnectFragment : Fragment() {
super.onDestroyView()
}
+ override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
+ if (resultCode == Activity.RESULT_OK) {
+ vpnPermission.complete(Unit)
+ }
+ }
+
private fun waitForDaemon(asyncDaemon: Deferred<MullvadDaemon>) =
GlobalScope.launch(Dispatchers.Default) {
daemon.complete(asyncDaemon.await())
@@ -97,11 +110,26 @@ class ConnectFragment : Fragment() {
}
}
+ private fun requestVpnPermission() {
+ val intent = VpnService.prepare(parentActivity)
+
+ vpnPermission = CompletableDeferred<Unit>()
+
+ if (intent != null) {
+ startActivityForResult(intent, 0)
+ } else {
+ onActivityResult(0, Activity.RESULT_OK, null)
+ }
+ }
+
private fun connect() {
updateViewToPreConnecting()
activeAction?.cancel()
+ requestVpnPermission()
+
activeAction = GlobalScope.launch(Dispatchers.Default) {
+ vpnPermission.await()
generateWireguardKeyJob.join()
daemon.await().connect()
}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt
index 6e3d34730b..320b2e1c94 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt
@@ -68,7 +68,7 @@ class MainActivity : FragmentActivity() {
private fun startDaemon() = GlobalScope.async(Dispatchers.Default) {
activityCreated.await()
ApiRootCaFile().extract(this@MainActivity)
- MullvadDaemon()
+ MullvadDaemon(MullvadVpnService(this@MainActivity))
}
private fun fetchRelayList() = GlobalScope.async(Dispatchers.Default) {
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt
index e9f54769b2..ff3abeea80 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt
@@ -7,10 +7,10 @@ import net.mullvad.mullvadvpn.model.RelaySettingsUpdate
import net.mullvad.mullvadvpn.model.Settings
import net.mullvad.mullvadvpn.model.TunnelStateTransition
-class MullvadDaemon {
+class MullvadDaemon(val vpnService: MullvadVpnService) {
init {
System.loadLibrary("mullvad_jni")
- initialize()
+ initialize(vpnService)
}
var onTunnelStateChange: ((TunnelStateTransition) -> Unit)? = null
@@ -25,7 +25,7 @@ class MullvadDaemon {
external fun setAccount(accountToken: String?)
external fun updateRelaySettings(update: RelaySettingsUpdate)
- private external fun initialize()
+ private external fun initialize(vpnService: MullvadVpnService)
private fun notifyTunnelStateEvent(event: TunnelStateTransition) {
onTunnelStateChange?.invoke(event)
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadVpnService.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadVpnService.kt
new file mode 100644
index 0000000000..dcd4935ee4
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadVpnService.kt
@@ -0,0 +1,79 @@
+package net.mullvad.mullvadvpn
+
+import java.net.InetAddress
+
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.CompletableDeferred
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.net.VpnService
+
+import net.mullvad.mullvadvpn.model.TunConfig
+
+var INNER_VPN_SERVICE = CompletableDeferred<MullvadVpnService.InnerVpnService>()
+var SERVICE_NOT_RUNNING = true
+
+class MullvadVpnService(val context: Context) {
+ class InnerVpnService : VpnService() {
+ override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
+ INNER_VPN_SERVICE.complete(this)
+
+ return super.onStartCommand(intent, flags, startId)
+ }
+
+ override fun onDestroy() {
+ INNER_VPN_SERVICE = CompletableDeferred<MullvadVpnService.InnerVpnService>()
+ SERVICE_NOT_RUNNING = true
+ super.onDestroy()
+ }
+
+ fun builder(): Builder {
+ return Builder()
+ }
+ }
+
+ fun createTun(config: TunConfig): Int {
+ return createTun(config, startService())
+ }
+
+ fun bypass(socket: Int): Boolean {
+ return startService().protect(socket)
+ }
+
+ private fun startService(): InnerVpnService {
+ lateinit var service: InnerVpnService
+
+ if (SERVICE_NOT_RUNNING) {
+ SERVICE_NOT_RUNNING = false
+ context.startService(Intent(context, InnerVpnService::class.java))
+ }
+
+ runBlocking { service = INNER_VPN_SERVICE.await() }
+
+ return service
+ }
+
+ private fun createTun(config: TunConfig, service: InnerVpnService): Int {
+ val builder = service.builder().apply {
+ for (address in config.addresses) {
+ addAddress(address, 32)
+ }
+
+ for (dnsServer in config.dnsServers) {
+ addDnsServer(dnsServer)
+ }
+
+ for (route in config.routes) {
+ addRoute(route.address, route.prefixLength as Int)
+ }
+
+ setMtu(config.mtu)
+ }
+
+ val vpnInterface = builder.establish()
+
+ return vpnInterface.detachFd()
+ }
+}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/model/InetNetwork.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/InetNetwork.kt
new file mode 100644
index 0000000000..eb3030880f
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/InetNetwork.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.model
+
+import java.net.InetAddress
+
+data class InetNetwork(val address: InetAddress, val prefixLength: Short)
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/model/TunConfig.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/TunConfig.kt
new file mode 100644
index 0000000000..929e03b99a
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/TunConfig.kt
@@ -0,0 +1,10 @@
+package net.mullvad.mullvadvpn.model
+
+import java.net.InetAddress
+
+data class TunConfig(
+ val addresses: List<InetAddress>,
+ val dnsServers: List<InetAddress>,
+ val routes: List<InetNetwork>,
+ val mtu: Int
+)
diff --git a/mullvad-jni/Cargo.toml b/mullvad-jni/Cargo.toml
index 9b1482f315..5792fe27d1 100644
--- a/mullvad-jni/Cargo.toml
+++ b/mullvad-jni/Cargo.toml
@@ -12,6 +12,7 @@ crate_type = ["cdylib"]
[dependencies]
err-derive = "0.1.5"
futures = "0.1"
+ipnetwork = "0.14"
jni = "0.12"
jsonrpc-client-core = "0.5"
lazy_static = "1"
diff --git a/mullvad-jni/src/into_java.rs b/mullvad-jni/src/into_java.rs
index 92d75b4a27..1ab7cd54e6 100644
--- a/mullvad-jni/src/into_java.rs
+++ b/mullvad-jni/src/into_java.rs
@@ -1,7 +1,9 @@
use crate::get_class;
+use ipnetwork::IpNetwork;
use jni::{
objects::{JList, JObject, JString, JValue},
- sys::{jint, jsize},
+ signature::JavaType,
+ sys::{jint, jshort, jsize},
JNIEnv,
};
use mullvad_types::{
@@ -11,7 +13,8 @@ use mullvad_types::{
settings::Settings,
CustomTunnelEndpoint,
};
-use std::fmt::Debug;
+use std::{fmt::Debug, net::IpAddr};
+use talpid_core::tunnel::tun_provider::TunConfig;
use talpid_types::{net::wireguard::PublicKey, tunnel::TunnelStateTransition};
pub trait IntoJava<'env> {
@@ -91,6 +94,76 @@ impl<'array, 'env> IntoJava<'env> for &'array [u8] {
}
}
+impl<'env> IntoJava<'env> for IpAddr {
+ type JavaType = JObject<'env>;
+
+ fn into_java(self, env: &JNIEnv<'env>) -> Self::JavaType {
+ let class = get_class("java/net/InetAddress");
+
+ let constructor = env
+ .get_static_method_id(&class, "getByAddress", "([B)Ljava/net/InetAddress;")
+ .expect("Failed to get InetAddress.getByAddress method ID");
+
+ let octet_count = if self.is_ipv4() { 4 } else { 16 };
+ let octets_array = env
+ .new_byte_array(octet_count)
+ .expect("Failed to create byte array to store IP address");
+
+ let octet_data: Vec<i8> = match self {
+ IpAddr::V4(address) => address
+ .octets()
+ .into_iter()
+ .map(|octet| *octet as i8)
+ .collect(),
+ IpAddr::V6(address) => address
+ .octets()
+ .into_iter()
+ .map(|octet| *octet as i8)
+ .collect(),
+ };
+
+ env.set_byte_array_region(octets_array, 0, &octet_data)
+ .expect("Failed to copy IP address octets to byte array");
+
+ let octets = env.auto_local(JObject::from(octets_array));
+ let result = env
+ .call_static_method_unchecked(
+ "java/net/InetAddress",
+ constructor,
+ JavaType::Object("java/net/InetAddress".to_owned()),
+ &[JValue::Object(octets.as_obj())],
+ )
+ .expect("Failed to create InetAddress Java object");
+
+ match result {
+ JValue::Object(object) => object,
+ value => {
+ panic!(
+ "InetAddress.getByAddress returned an invalid value: {:?}",
+ value
+ );
+ }
+ }
+ }
+}
+
+impl<'env> IntoJava<'env> for IpNetwork {
+ type JavaType = JObject<'env>;
+
+ fn into_java(self, env: &JNIEnv<'env>) -> Self::JavaType {
+ let class = get_class("net/mullvad/mullvadvpn/model/InetNetwork");
+ let address = env.auto_local(self.ip().into_java(env));
+ let prefix_length = self.prefix() as jshort;
+ let parameters = [
+ JValue::Object(address.as_obj()),
+ JValue::Short(prefix_length),
+ ];
+
+ env.new_object(&class, "(Ljava/net/InetAddress;S)V", &parameters)
+ .expect("Failed to create InetNetwork Java object")
+ }
+}
+
impl<'env> IntoJava<'env> for PublicKey {
type JavaType = JObject<'env>;
@@ -117,6 +190,19 @@ impl<'env> IntoJava<'env> for AccountData {
}
}
+impl<'env> IntoJava<'env> for TunConfig {
+ type JavaType = JObject<'env>;
+
+ fn into_java(self, env: &JNIEnv<'env>) -> Self::JavaType {
+ let class = get_class("net/mullvad/mullvadvpn/model/TunConfig");
+ let addresses = env.auto_local(self.addresses.into_java(env));
+ let parameters = [JValue::Object(addresses.as_obj())];
+
+ env.new_object(&class, "(Ljava/util/List;)V", &parameters)
+ .expect("Failed to create TunConfig Java object")
+ }
+}
+
impl<'env> IntoJava<'env> for RelayList {
type JavaType = JObject<'env>;
diff --git a/mullvad-jni/src/lib.rs b/mullvad-jni/src/lib.rs
index b35ec3efbf..e8c7e62f49 100644
--- a/mullvad-jni/src/lib.rs
+++ b/mullvad-jni/src/lib.rs
@@ -5,10 +5,11 @@ mod from_java;
mod into_java;
mod is_null;
mod jni_event_listener;
+mod vpn_service_tun_provider;
use crate::{
daemon_interface::DaemonInterface, from_java::FromJava, into_java::IntoJava,
- jni_event_listener::JniEventListener,
+ jni_event_listener::JniEventListener, vpn_service_tun_provider::VpnServiceTunProvider,
};
use jni::{
objects::{GlobalRef, JObject, JString},
@@ -19,16 +20,17 @@ use lazy_static::lazy_static;
use mullvad_daemon::{logging, version, Daemon, DaemonCommandSender};
use parking_lot::{Mutex, RwLock};
use std::{collections::HashMap, path::PathBuf, sync::mpsc, thread};
-use talpid_core::tunnel::tun_provider::StubTunProvider;
use talpid_types::ErrorExt;
const LOG_FILENAME: &str = "daemon.log";
const CLASSES_TO_LOAD: &[&str] = &[
+ "java/net/InetAddress",
"java/util/ArrayList",
"net/mullvad/mullvadvpn/model/AccountData",
"net/mullvad/mullvadvpn/model/Constraint$Any",
"net/mullvad/mullvadvpn/model/Constraint$Only",
+ "net/mullvad/mullvadvpn/model/InetNetwork",
"net/mullvad/mullvadvpn/model/LocationConstraint$City",
"net/mullvad/mullvadvpn/model/LocationConstraint$Country",
"net/mullvad/mullvadvpn/model/LocationConstraint$Hostname",
@@ -42,12 +44,14 @@ const CLASSES_TO_LOAD: &[&str] = &[
"net/mullvad/mullvadvpn/model/RelaySettingsUpdate$CustomTunnelEndpoint",
"net/mullvad/mullvadvpn/model/RelaySettingsUpdate$RelayConstraintsUpdate",
"net/mullvad/mullvadvpn/model/Settings",
+ "net/mullvad/mullvadvpn/model/TunConfig",
"net/mullvad/mullvadvpn/model/TunnelStateTransition$Blocked",
"net/mullvad/mullvadvpn/model/TunnelStateTransition$Connected",
"net/mullvad/mullvadvpn/model/TunnelStateTransition$Connecting",
"net/mullvad/mullvadvpn/model/TunnelStateTransition$Disconnected",
"net/mullvad/mullvadvpn/model/TunnelStateTransition$Disconnecting",
"net/mullvad/mullvadvpn/MullvadDaemon",
+ "net/mullvad/mullvadvpn/MullvadVpnService",
];
lazy_static! {
@@ -58,6 +62,9 @@ lazy_static! {
#[derive(Debug, err_derive::Error)]
pub enum Error {
+ #[error(display = "Failed to create VpnService tunnel provider")]
+ CreateVpnServiceTunProvider(#[error(cause)] vpn_service_tun_provider::Error),
+
#[error(display = "Failed to get cache directory path")]
GetCacheDir(#[error(cause)] mullvad_paths::Error),
@@ -73,12 +80,13 @@ pub enum Error {
pub extern "system" fn Java_net_mullvad_mullvadvpn_MullvadDaemon_initialize(
env: JNIEnv,
this: JObject,
+ vpnService: JObject,
) {
let log_dir = start_logging();
load_classes(&env);
- if let Err(error) = initialize(&env, &this, log_dir) {
+ if let Err(error) = initialize(&env, &this, &vpnService, log_dir) {
log::error!("{}", error.display_chain());
}
}
@@ -112,8 +120,15 @@ fn load_class_reference(env: &JNIEnv, name: &str) -> GlobalRef {
.expect("Failed to convert local reference to Java class into a global reference")
}
-fn initialize(env: &JNIEnv, this: &JObject, log_dir: PathBuf) -> Result<(), Error> {
- let daemon_command_sender = spawn_daemon(env, this, log_dir)?;
+fn initialize(
+ env: &JNIEnv,
+ this: &JObject,
+ vpn_service: &JObject,
+ log_dir: PathBuf,
+) -> Result<(), Error> {
+ let tun_provider =
+ VpnServiceTunProvider::new(env, vpn_service).map_err(Error::CreateVpnServiceTunProvider)?;
+ let daemon_command_sender = spawn_daemon(env, this, tun_provider, log_dir)?;
DAEMON_INTERFACE
.lock()
@@ -125,29 +140,33 @@ fn initialize(env: &JNIEnv, this: &JObject, log_dir: PathBuf) -> Result<(), Erro
fn spawn_daemon(
env: &JNIEnv,
this: &JObject,
+ tun_provider: VpnServiceTunProvider,
log_dir: PathBuf,
) -> Result<DaemonCommandSender, Error> {
let listener = JniEventListener::spawn(env, this).map_err(Error::SpawnJniEventListener)?;
let (tx, rx) = mpsc::channel();
- thread::spawn(move || match create_daemon(listener, log_dir) {
- Ok(daemon) => {
- let _ = tx.send(Ok(daemon.command_sender()));
- match daemon.run() {
- Ok(()) => log::info!("Mullvad daemon has stopped"),
- Err(error) => log::error!("{}", error.display_chain()),
+ thread::spawn(
+ move || match create_daemon(listener, tun_provider, log_dir) {
+ Ok(daemon) => {
+ let _ = tx.send(Ok(daemon.command_sender()));
+ match daemon.run() {
+ Ok(()) => log::info!("Mullvad daemon has stopped"),
+ Err(error) => log::error!("{}", error.display_chain()),
+ }
}
- }
- Err(error) => {
- let _ = tx.send(Err(error));
- }
- });
+ Err(error) => {
+ let _ = tx.send(Err(error));
+ }
+ },
+ );
rx.recv().unwrap()
}
fn create_daemon(
listener: JniEventListener,
+ tun_provider: VpnServiceTunProvider,
log_dir: PathBuf,
) -> Result<Daemon<JniEventListener>, Error> {
let resource_dir = mullvad_paths::get_resource_dir();
@@ -155,7 +174,7 @@ fn create_daemon(
let daemon = Daemon::start_with_event_listener_and_tun_provider(
listener,
- StubTunProvider,
+ tun_provider,
Some(log_dir),
resource_dir,
cache_dir,
diff --git a/mullvad-jni/src/vpn_service_tun_provider.rs b/mullvad-jni/src/vpn_service_tun_provider.rs
new file mode 100644
index 0000000000..3d422d83e0
--- /dev/null
+++ b/mullvad-jni/src/vpn_service_tun_provider.rs
@@ -0,0 +1,150 @@
+use crate::{get_class, into_java::IntoJava};
+use jni::{
+ objects::{GlobalRef, JObject, JValue},
+ signature::{JavaType, Primitive},
+ JNIEnv, JavaVM,
+};
+use std::os::unix::io::{AsRawFd, RawFd};
+use talpid_core::tunnel::tun_provider::{Tun, TunConfig, TunProvider};
+use talpid_types::BoxedError;
+
+/// Errors that occur while setting up VpnService tunnel.
+#[derive(Debug, err_derive::Error)]
+#[error(display = "Failed to set up the VpnService")]
+pub enum Error {
+ #[error(display = "Failed to attach Java VM to tunnel thread")]
+ AttachJvmToThread(#[error(cause)] jni::errors::Error),
+
+ #[error(display = "Failed to allow socket to bypass tunnel")]
+ Bypass,
+
+ #[error(display = "Failed to call Java method {}", _0)]
+ CallMethod(&'static str, #[error(cause)] jni::errors::Error),
+
+ #[error(display = "Failed to create global reference to MullvadVpnService instance")]
+ CreateGlobalReference(#[error(cause)] jni::errors::Error),
+
+ #[error(display = "Failed to find {} method", _0)]
+ FindMethod(&'static str, #[error(cause)] jni::errors::Error),
+
+ #[error(display = "Failed to get Java VM instance")]
+ GetJvmInstance(#[error(cause)] jni::errors::Error),
+
+ #[error(display = "Received an invalid result from {}: {}", _0, _1)]
+ InvalidMethodResult(&'static str, String),
+}
+
+/// Factory of tunnel devices on Android.
+pub struct VpnServiceTunProvider {
+ jvm: JavaVM,
+ class: GlobalRef,
+ object: GlobalRef,
+}
+
+impl VpnServiceTunProvider {
+ /// Create a new VpnServiceTunProvider interfacing with Android's VpnService.
+ pub fn new(env: &JNIEnv, mullvad_vpn_service: &JObject) -> Result<Self, Error> {
+ let jvm = env.get_java_vm().map_err(Error::GetJvmInstance)?;
+ let class = get_class("net/mullvad/mullvadvpn/MullvadVpnService");
+ let object = env
+ .new_global_ref(*mullvad_vpn_service)
+ .map_err(Error::CreateGlobalReference)?;
+
+ Ok(VpnServiceTunProvider { jvm, class, object })
+ }
+}
+
+impl TunProvider for VpnServiceTunProvider {
+ fn create_tun(&self, config: TunConfig) -> Result<Box<dyn Tun>, BoxedError> {
+ let env = self
+ .jvm
+ .attach_current_thread()
+ .map_err(|cause| BoxedError::new(Error::AttachJvmToThread(cause)))?;
+ let create_tun_method = env
+ .get_method_id(
+ &self.class,
+ "createTun",
+ "(Lnet/mullvad/mullvadvpn/model/TunConfig;)I",
+ )
+ .map_err(|cause| {
+ BoxedError::new(Error::FindMethod("MullvadVpnService.createTun", cause))
+ })?;
+
+ let result = env
+ .call_method_unchecked(
+ self.object.as_obj(),
+ create_tun_method,
+ JavaType::Primitive(Primitive::Int),
+ &[JValue::Object(config.into_java(&env))],
+ )
+ .map_err(|cause| {
+ BoxedError::new(Error::CallMethod("MullvadVpnService.createTun", cause))
+ })?;
+
+ match result {
+ JValue::Int(fd) => Ok(Box::new(VpnServiceTun {
+ tunnel: fd,
+ jvm: env
+ .get_java_vm()
+ .map_err(|cause| BoxedError::new(Error::GetJvmInstance(cause)))?,
+ class: self.class.clone(),
+ object: self.object.clone(),
+ })),
+ value => Err(BoxedError::new(Error::InvalidMethodResult(
+ "MullvadVpnService.createTun",
+ format!("{:?}", value),
+ ))),
+ }
+ }
+}
+
+struct VpnServiceTun {
+ tunnel: RawFd,
+ jvm: JavaVM,
+ class: GlobalRef,
+ object: GlobalRef,
+}
+
+impl AsRawFd for VpnServiceTun {
+ fn as_raw_fd(&self) -> RawFd {
+ self.tunnel
+ }
+}
+
+impl Tun for VpnServiceTun {
+ fn interface_name(&self) -> &str {
+ "tun"
+ }
+
+ fn bypass(&mut self, socket: RawFd) -> Result<(), BoxedError> {
+ let env = self
+ .jvm
+ .attach_current_thread()
+ .map_err(|cause| BoxedError::new(Error::AttachJvmToThread(cause)))?;
+ let create_tun_method =
+ env.get_method_id(&self.class, "bypass", "(I)Z")
+ .map_err(|cause| {
+ BoxedError::new(Error::FindMethod("MullvadVpnService.bypass", cause))
+ })?;
+
+ let result = env
+ .call_method_unchecked(
+ self.object.as_obj(),
+ create_tun_method,
+ JavaType::Primitive(Primitive::Boolean),
+ &[JValue::Int(socket)],
+ )
+ .map_err(|cause| {
+ BoxedError::new(Error::CallMethod("MullvadVpnService.bypass", cause))
+ })?;
+
+ match result {
+ JValue::Bool(0) => Err(BoxedError::new(Error::Bypass)),
+ JValue::Bool(_) => Ok(()),
+ value => Err(BoxedError::new(Error::InvalidMethodResult(
+ "MullvadVpnService.bypass",
+ format!("{:?}", value),
+ ))),
+ }
+ }
+}
diff --git a/talpid-core/src/tunnel/tun_provider/mod.rs b/talpid-core/src/tunnel/tun_provider/mod.rs
index 69ff7d8b62..d9578ebbd1 100644
--- a/talpid-core/src/tunnel/tun_provider/mod.rs
+++ b/talpid-core/src/tunnel/tun_provider/mod.rs
@@ -3,6 +3,8 @@ use ipnetwork::IpNetwork;
use std::net::IpAddr;
#[cfg(unix)]
use std::os::unix::io::AsRawFd;
+#[cfg(target_os = "android")]
+use std::os::unix::io::RawFd;
use talpid_types::BoxedError;
cfg_if! {
@@ -18,7 +20,7 @@ cfg_if! {
pub type PlatformTunProvider = UnixTunProvider;
} else {
mod stub;
- pub use self::stub::StubTunProvider;
+ use self::stub::StubTunProvider;
/// Default stub implementation of `TunProvider` for Android and Windows.
pub type PlatformTunProvider = StubTunProvider;
@@ -32,6 +34,10 @@ cfg_if! {
pub trait Tun: AsRawFd + Send {
/// Retrieve the tunnel interface name.
fn interface_name(&self) -> &str;
+
+ /// Allow a socket to bypass the tunnel.
+ #[cfg(target_os = "android")]
+ fn bypass(&mut self, socket: RawFd) -> Result<(), BoxedError>;
}
/// Stub tunnel device.