diff options
| author | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2019-05-29 11:50:07 -0300 |
|---|---|---|
| committer | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2019-05-29 11:50:07 -0300 |
| commit | 6188daf9e1d3446084dcb2e11efd42806c79e0ab (patch) | |
| tree | 58c4784ca316b8263f1596c475b8cc7f23e7d516 | |
| parent | 0eb13af5be6a204f9c94783bd16498101bfbb97d (diff) | |
| parent | dcb94332ab4b9f881df1ba2fcf5d1ef449704fd4 (diff) | |
| download | mullvadvpn-6188daf9e1d3446084dcb2e11efd42806c79e0ab.tar.xz mullvadvpn-6188daf9e1d3446084dcb2e11efd42806c79e0ab.zip | |
Merge branch 'vpn-service-tun-provider'
| -rw-r--r-- | Cargo.lock | 1 | ||||
| -rw-r--r-- | android/src/main/AndroidManifest.xml | 9 | ||||
| -rw-r--r-- | android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt | 30 | ||||
| -rw-r--r-- | android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt | 2 | ||||
| -rw-r--r-- | android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt | 6 | ||||
| -rw-r--r-- | android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadVpnService.kt | 79 | ||||
| -rw-r--r-- | android/src/main/kotlin/net/mullvad/mullvadvpn/model/InetNetwork.kt | 5 | ||||
| -rw-r--r-- | android/src/main/kotlin/net/mullvad/mullvadvpn/model/TunConfig.kt | 10 | ||||
| -rw-r--r-- | mullvad-jni/Cargo.toml | 1 | ||||
| -rw-r--r-- | mullvad-jni/src/into_java.rs | 90 | ||||
| -rw-r--r-- | mullvad-jni/src/lib.rs | 53 | ||||
| -rw-r--r-- | mullvad-jni/src/vpn_service_tun_provider.rs | 150 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/tun_provider/mod.rs | 8 |
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", ¶meters) + .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", ¶meters) + .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. |
