summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2020-07-20 10:33:30 -0300
committerJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2020-07-20 10:33:30 -0300
commita358f925e83ebbdd838aa6a2162c48fe15a9d025 (patch)
tree5bafc4263a9218576af8b2d7861a15466979a83e
parent77e129e184c783473acad21c29901288eff10df1 (diff)
parent1774c36b7d53d88050dd068cbf3fcf733d78e2d2 (diff)
downloadmullvadvpn-a358f925e83ebbdd838aa6a2162c48fe15a9d025.tar.xz
mullvadvpn-a358f925e83ebbdd838aa6a2162c48fe15a9d025.zip
Merge branch 'manage-tun-from-java-side'
-rw-r--r--android/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt69
-rw-r--r--android/src/main/kotlin/net/mullvad/talpid/tun_provider/InetNetwork.kt5
-rw-r--r--mullvad-jni/Cargo.toml2
-rw-r--r--mullvad-jni/src/lib.rs1
-rw-r--r--mullvad-jni/src/talpid_vpn_service.rs192
-rw-r--r--talpid-core/Cargo.toml2
-rw-r--r--talpid-core/src/tunnel/mod.rs3
-rw-r--r--talpid-core/src/tunnel/tun_provider/android/mod.rs345
8 files changed, 393 insertions, 226 deletions
diff --git a/android/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt b/android/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt
index f960f0c13f..8f095641a1 100644
--- a/android/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt
+++ b/android/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt
@@ -1,12 +1,22 @@
package net.mullvad.talpid
import android.net.VpnService
+import android.os.ParcelFileDescriptor
import java.net.Inet4Address
import java.net.Inet6Address
import java.net.InetAddress
+import kotlin.properties.Delegates.observable
import net.mullvad.talpid.tun_provider.TunConfig
open class TalpidVpnService : VpnService() {
+ private var activeTunDevice by observable<Int?>(null) { _, oldTunDevice, _ ->
+ oldTunDevice?.let { oldTunFd ->
+ ParcelFileDescriptor.adoptFd(oldTunFd).close()
+ }
+ }
+
+ private var currentTunConfig = defaultTunConfig()
+
val connectivityListener = ConnectivityListener()
override fun onCreate() {
@@ -17,7 +27,53 @@ open class TalpidVpnService : VpnService() {
connectivityListener.unregister(this)
}
- fun createTun(config: TunConfig): Int {
+ fun getTun(config: TunConfig): Int {
+ synchronized(this) {
+ val tunDevice = activeTunDevice
+
+ if (config == currentTunConfig && tunDevice != null) {
+ return tunDevice
+ } else {
+ val newTunDevice = createTun(config)
+
+ currentTunConfig = config
+ activeTunDevice = newTunDevice
+
+ return newTunDevice
+ }
+ }
+ }
+
+ fun createTun() {
+ synchronized(this) {
+ activeTunDevice = createTun(currentTunConfig)
+ }
+ }
+
+ fun createTunIfClosed() {
+ synchronized(this) {
+ if (activeTunDevice == null) {
+ activeTunDevice = createTun(currentTunConfig)
+ }
+ }
+ }
+
+ fun recreateTunIfOpen(config: TunConfig) {
+ synchronized(this) {
+ if (activeTunDevice != null) {
+ currentTunConfig = config
+ activeTunDevice = createTun(config)
+ }
+ }
+ }
+
+ fun closeTun() {
+ synchronized(this) {
+ activeTunDevice = null
+ }
+ }
+
+ private fun createTun(config: TunConfig): Int {
if (VpnService.prepare(this) != null) {
// VPN permission wasn't granted
return -1
@@ -41,8 +97,14 @@ open class TalpidVpnService : VpnService() {
}
val vpnInterface = builder.establish()
+ val tunFd = vpnInterface?.detachFd()
- return vpnInterface?.detachFd() ?: 0
+ if (tunFd != null) {
+ waitForTunnelUp(tunFd, config.routes.any { route -> route.isIpv6 })
+ return tunFd
+ } else {
+ return 0
+ }
}
fun bypass(socket: Int): Boolean {
@@ -56,4 +118,7 @@ open class TalpidVpnService : VpnService() {
else -> throw RuntimeException("Invalid IP address (not IPv4 nor IPv6)")
}
}
+
+ private external fun defaultTunConfig(): TunConfig
+ private external fun waitForTunnelUp(tunFd: Int, isIpv6Enabled: Boolean)
}
diff --git a/android/src/main/kotlin/net/mullvad/talpid/tun_provider/InetNetwork.kt b/android/src/main/kotlin/net/mullvad/talpid/tun_provider/InetNetwork.kt
index 9b133d352b..a8490b48bf 100644
--- a/android/src/main/kotlin/net/mullvad/talpid/tun_provider/InetNetwork.kt
+++ b/android/src/main/kotlin/net/mullvad/talpid/tun_provider/InetNetwork.kt
@@ -1,5 +1,8 @@
package net.mullvad.talpid.tun_provider
+import java.net.Inet6Address
import java.net.InetAddress
-data class InetNetwork(val address: InetAddress, val prefixLength: Short)
+data class InetNetwork(val address: InetAddress, val prefixLength: Short) {
+ val isIpv6 = address is Inet6Address
+}
diff --git a/mullvad-jni/Cargo.toml b/mullvad-jni/Cargo.toml
index 4257c5789a..b65909d669 100644
--- a/mullvad-jni/Cargo.toml
+++ b/mullvad-jni/Cargo.toml
@@ -20,6 +20,8 @@ jsonrpc-core = "8"
lazy_static = "1"
log = "0.4"
log-panics = "2"
+nix = "0.17"
+rand = "0.7"
mullvad-daemon = { path = "../mullvad-daemon" }
mullvad-paths = { path = "../mullvad-paths" }
diff --git a/mullvad-jni/src/lib.rs b/mullvad-jni/src/lib.rs
index 1fd30f9661..af19f6da5c 100644
--- a/mullvad-jni/src/lib.rs
+++ b/mullvad-jni/src/lib.rs
@@ -5,6 +5,7 @@ mod classes;
mod daemon_interface;
mod is_null;
mod jni_event_listener;
+mod talpid_vpn_service;
use crate::{daemon_interface::DaemonInterface, jni_event_listener::JniEventListener};
use jnix::{
diff --git a/mullvad-jni/src/talpid_vpn_service.rs b/mullvad-jni/src/talpid_vpn_service.rs
new file mode 100644
index 0000000000..e315b55e15
--- /dev/null
+++ b/mullvad-jni/src/talpid_vpn_service.rs
@@ -0,0 +1,192 @@
+use ipnetwork::IpNetwork;
+use jnix::{
+ jni::{
+ objects::JObject,
+ sys::{jboolean, jint, JNI_FALSE},
+ JNIEnv,
+ },
+ IntoJava, JnixEnv,
+};
+use nix::sys::{
+ select::{pselect, FdSet},
+ time::{TimeSpec, TimeValLike},
+};
+use rand::{thread_rng, Rng};
+use std::{
+ io,
+ net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket},
+ os::unix::io::RawFd,
+ time::{Duration, Instant},
+};
+use talpid_core::tunnel::TunConfig;
+use talpid_types::ErrorExt;
+
+#[derive(Debug, err_derive::Error)]
+enum Error {
+ #[error(display = "Failed to verify the tunnel device")]
+ VerifyTunDevice(#[error(source)] SendRandomDataError),
+
+ #[error(display = "Failed to select() on tunnel device")]
+ Select(#[error(source)] nix::Error),
+
+ #[error(display = "Timed out while waiting for tunnel device to receive data")]
+ TunnelDeviceTimeout,
+}
+
+#[no_mangle]
+#[allow(non_snake_case)]
+pub extern "system" fn Java_net_mullvad_talpid_TalpidVpnService_defaultTunConfig<'env>(
+ env: JNIEnv<'env>,
+ _this: JObject<'_>,
+) -> JObject<'env> {
+ let env = JnixEnv::from(env);
+
+ TunConfig::default().into_java(&env).forget()
+}
+
+#[no_mangle]
+#[allow(non_snake_case)]
+pub extern "system" fn Java_net_mullvad_talpid_TalpidVpnService_waitForTunnelUp(
+ _: JNIEnv<'_>,
+ _this: JObject<'_>,
+ tunFd: jint,
+ isIpv6Enabled: jboolean,
+) {
+ let tun_fd = tunFd as RawFd;
+ let is_ipv6_enabled = isIpv6Enabled != JNI_FALSE;
+
+ if let Err(error) = wait_for_tunnel_up(tun_fd, is_ipv6_enabled) {
+ log::error!(
+ "{}",
+ error.display_chain_with_msg("Failed to wait for tunnel device to be usable")
+ );
+ }
+}
+
+fn wait_for_tunnel_up(tun_fd: RawFd, is_ipv6_enabled: bool) -> Result<(), Error> {
+ let mut fd_set = FdSet::new();
+ fd_set.insert(tun_fd);
+ let timeout = TimeSpec::microseconds(300);
+ const TIMEOUT: Duration = Duration::from_secs(60);
+ let start = Instant::now();
+ while start.elapsed() < TIMEOUT {
+ // if tunnel device is ready to be read from, traffic is being routed through it
+ if pselect(None, Some(&mut fd_set), None, None, Some(&timeout), None)? > 0 {
+ return Ok(());
+ }
+ // have to add tun_fd back into the bitset
+ fd_set.insert(tun_fd);
+ try_sending_random_udp(is_ipv6_enabled)?;
+ }
+
+ Err(Error::TunnelDeviceTimeout)
+}
+
+#[derive(Debug, err_derive::Error)]
+#[error(no_from)]
+enum SendRandomDataError {
+ #[error(display = "Failed to bind an UDP socket")]
+ BindUdpSocket(#[error(source)] io::Error),
+
+ #[error(display = "Failed to send random data through UDP socket")]
+ SendToUdpSocket(#[error(source)] io::Error),
+}
+
+fn try_sending_random_udp(is_ipv6_enabled: bool) -> Result<(), SendRandomDataError> {
+ let mut tried_ipv6 = false;
+ const TIMEOUT: Duration = Duration::from_millis(300);
+ let start = Instant::now();
+
+ while start.elapsed() < TIMEOUT {
+ // pick any random route to select between Ipv4 and Ipv6
+ // TODO: if we are to allow LAN on Android by changing the routes that are stuffed in
+ // TunConfig, then this should be revisited to be fair between IPv4 and IPv6
+ let should_generate_ipv4 = is_ipv6_enabled == false || thread_rng().gen();
+
+ let rand_port = thread_rng().gen();
+ let (local_addr, rand_dest_addr) = if should_generate_ipv4 || tried_ipv6 {
+ let mut ipv4_bytes = [0u8; 4];
+ thread_rng().fill(&mut ipv4_bytes);
+ (
+ SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0),
+ SocketAddr::new(IpAddr::from(ipv4_bytes).into(), rand_port),
+ )
+ } else {
+ let mut ipv6_bytes = [0u8; 16];
+ tried_ipv6 = true;
+ thread_rng().fill(&mut ipv6_bytes);
+ (
+ SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 0),
+ SocketAddr::new(IpAddr::from(ipv6_bytes).into(), rand_port),
+ )
+ };
+
+ // TODO: once https://github.com/rust-lang/rust/issues/27709 is resolved, please use
+ // `is_global()` to check if a new address should be attempted.
+ if !is_public_ip(rand_dest_addr.ip()) {
+ continue;
+ }
+
+ let socket = UdpSocket::bind(local_addr).map_err(SendRandomDataError::BindUdpSocket)?;
+
+ let mut buf = vec![0u8; thread_rng().gen_range(17, 214)];
+ // fill buff with random data
+ thread_rng().fill(buf.as_mut_slice());
+ match socket.send_to(&buf, rand_dest_addr) {
+ Ok(_) => return Ok(()),
+ Err(err) => {
+ if tried_ipv6 {
+ continue;
+ }
+ match err.raw_os_error() {
+ // Error code 101 - specified network is unreachable
+ // Error code 22 - specified address is not usable
+ Some(101) | Some(22) => {
+ // if we failed whilst trying to send to IPv6, we should not try
+ // IPv6 again.
+ continue;
+ }
+ _ => return Err(SendRandomDataError::SendToUdpSocket(err)),
+ }
+ }
+ };
+ }
+ Ok(())
+}
+
+fn is_public_ip(addr: IpAddr) -> bool {
+ match addr {
+ IpAddr::V4(ipv4) => {
+ // 0.x.x.x is not a publicly routable address
+ if ipv4.octets()[0] == 0u8 {
+ return false;
+ }
+ }
+ IpAddr::V6(ipv6) => {
+ if ipv6.segments()[0] == 0u16 {
+ return false;
+ }
+ }
+ }
+ // A non-exhaustive list of non-public subnets
+ let publicly_unroutable_subnets: Vec<IpNetwork> = vec![
+ // IPv4 local networks
+ "10.0.0.0/8".parse().unwrap(),
+ "172.16.0.0/12".parse().unwrap(),
+ "192.168.0.0/16".parse().unwrap(),
+ // IPv4 non-forwardable network
+ "169.254.0.0/16".parse().unwrap(),
+ "192.0.0.0/8".parse().unwrap(),
+ // Documentation networks
+ "192.0.2.0/24".parse().unwrap(),
+ "198.51.100.0/24".parse().unwrap(),
+ "203.0.113.0/24".parse().unwrap(),
+ // IPv6 publicly unroutable networks
+ "fc00::/7".parse().unwrap(),
+ "fe80::/10".parse().unwrap(),
+ ];
+
+ !publicly_unroutable_subnets
+ .iter()
+ .any(|net| net.contains(addr))
+}
diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml
index ecaf5f8fcb..07ffb7263a 100644
--- a/talpid-core/Cargo.toml
+++ b/talpid-core/Cargo.toml
@@ -48,7 +48,6 @@ tokio-io = "0.1"
[target.'cfg(target_os = "android")'.dependencies]
jnix = { version = "0.2.3", features = ["derive"] }
-rand = "0.7"
[target.'cfg(target_os = "linux")'.dependencies]
@@ -79,7 +78,6 @@ widestring = "0.4"
winreg = "0.6"
winapi = { version = "0.3.6", features = ["handleapi", "ifdef", "libloaderapi", "netioapi", "synchapi", "winbase", "winuser"] }
socket2 = "0.3"
-rand = "0.7"
pnet_packet = "0.22"
[build-dependencies]
diff --git a/talpid-core/src/tunnel/mod.rs b/talpid-core/src/tunnel/mod.rs
index 5ece67f847..9926116a48 100644
--- a/talpid-core/src/tunnel/mod.rs
+++ b/talpid-core/src/tunnel/mod.rs
@@ -13,6 +13,9 @@ use talpid_types::net::{wireguard as wireguard_types, TunnelParameters};
#[cfg(target_os = "windows")]
use talpid_types::ErrorExt;
+#[cfg(target_os = "android")]
+pub use self::tun_provider::TunConfig;
+
/// A module for all OpenVPN related tunnel management.
#[cfg(not(target_os = "android"))]
pub mod openvpn;
diff --git a/talpid-core/src/tunnel/tun_provider/android/mod.rs b/talpid-core/src/tunnel/tun_provider/android/mod.rs
index 7558f067b0..e4e8f4fbac 100644
--- a/talpid-core/src/tunnel/tun_provider/android/mod.rs
+++ b/talpid-core/src/tunnel/tun_provider/android/mod.rs
@@ -11,16 +11,12 @@ use jnix::{
},
IntoJava, JnixEnv,
};
-use rand::{seq::SliceRandom, thread_rng, Rng};
use std::{
- fs::File,
- io,
- net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket},
- os::unix::io::{AsRawFd, FromRawFd, RawFd},
+ net::{IpAddr, Ipv4Addr, Ipv6Addr},
+ os::unix::io::{AsRawFd, RawFd},
sync::Arc,
- time::{Duration, Instant},
};
-use talpid_types::android::AndroidContext;
+use talpid_types::{android::AndroidContext, ErrorExt};
/// Errors that occur while setting up VpnService tunnel.
@@ -49,18 +45,6 @@ pub enum Error {
)]
InvalidMethodResult(&'static str, String),
- #[error(display = "Failed to bind an UDP socket")]
- BindUdpSocket(#[error(source)] io::Error),
-
- #[error(display = "Failed to send random data through UDP socket")]
- SendToUdpSocket(#[error(source)] io::Error),
-
- #[error(display = "Failed to select() on tunnel device")]
- Select(#[error(source)] nix::Error),
-
- #[error(display = "Timed out while waiting for tunnel device to receive data")]
- TunnelDeviceTimeout,
-
#[error(display = "Failed to create tunnel device")]
TunnelDeviceError,
@@ -73,7 +57,6 @@ pub struct AndroidTunProvider {
jvm: Arc<JavaVM>,
class: GlobalRef,
object: GlobalRef,
- active_tun: Option<File>,
last_tun_config: TunConfig,
allow_lan: bool,
}
@@ -81,22 +64,6 @@ pub struct AndroidTunProvider {
impl AndroidTunProvider {
/// Create a new AndroidTunProvider interfacing with Android's VpnService.
pub fn new(context: AndroidContext, allow_lan: bool) -> Self {
- // Initial configuration simply intercepts all packets. The only field that matters is
- // `routes`, because it determines what must enter the tunnel. All other fields contain
- // stub values.
- let initial_tun_config = TunConfig {
- addresses: vec![IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))],
- dns_servers: Vec::new(),
- routes: vec![
- IpNetwork::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0)
- .expect("Invalid IP network prefix for IPv4 address"),
- IpNetwork::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)), 0)
- .expect("Invalid IP network prefix for IPv6 address"),
- ],
- required_routes: vec![],
- mtu: 1380,
- };
-
let env = JnixEnv::from(
context
.jvm
@@ -109,8 +76,7 @@ impl AndroidTunProvider {
jvm: context.jvm,
class: talpid_vpn_service_class,
object: context.vpn_service,
- active_tun: None,
- last_tun_config: initial_tun_config,
+ last_tun_config: TunConfig::default(),
allow_lan,
}
}
@@ -118,10 +84,7 @@ impl AndroidTunProvider {
pub fn set_allow_lan(&mut self, allow_lan: bool) -> Result<(), Error> {
if self.allow_lan != allow_lan {
self.allow_lan = allow_lan;
-
- if self.active_tun.is_some() {
- self.create_tun()?;
- }
+ self.recreate_tun_if_open()?;
}
Ok(())
@@ -129,7 +92,9 @@ impl AndroidTunProvider {
/// Retrieve a tunnel device with the provided configuration.
pub fn get_tun(&mut self, config: TunConfig) -> Result<VpnServiceTun, Error> {
- let tun_fd = self.get_tun_fd(config)?;
+ let tun_fd = self.get_tun_fd(config.clone())?;
+
+ self.last_tun_config = config;
let jvm = unsafe { JavaVM::from_raw(self.jvm.get_java_vm_pointer()) }
.map_err(Error::CloneJavaVm)?;
@@ -142,132 +107,103 @@ impl AndroidTunProvider {
})
}
- fn wait_for_tunnel_up(tun_fd: RawFd, tun_config: &TunConfig) -> Result<(), Error> {
- use nix::sys::{
- select::{pselect, FdSet},
- time::{TimeSpec, TimeValLike},
- };
- let mut fd_set = FdSet::new();
- fd_set.insert(tun_fd);
- let timeout = TimeSpec::microseconds(300);
- const TIMEOUT: Duration = Duration::from_secs(60);
- let start = Instant::now();
- while start.elapsed() < TIMEOUT {
- // if tunnel device is ready to be read from, traffic is being routed through it
- if pselect(None, Some(&mut fd_set), None, None, Some(&timeout), None)
- .map_err(Error::Select)?
- > 0
- {
- return Ok(());
- }
- // have to add tun_fd back into the bitset
- fd_set.insert(tun_fd);
- Self::try_sending_random_udp(tun_config)?;
- }
-
- Err(Error::TunnelDeviceTimeout)
- }
-
- fn try_sending_random_udp(tun_config: &TunConfig) -> Result<(), Error> {
- let mut tried_ipv6 = false;
- const TIMEOUT: Duration = Duration::from_millis(300);
- let start = Instant::now();
-
- while start.elapsed() < TIMEOUT {
- // pick any random route to select between Ipv4 and Ipv6
- // TODO: if we are to allow LAN on Android by changing the routes that are stuffed in
- // TunConfig, then this should be revisited to be fair between IPv4 and IPv6
- let should_generate_ipv4 = tun_config
- .routes
- .choose(&mut thread_rng())
- .map(|route| route.is_ipv4())
- .unwrap_or(true)
- || tried_ipv6;
-
- let rand_port = thread_rng().gen();
- let (local_addr, rand_dest_addr) = if should_generate_ipv4 || tried_ipv6 {
- let mut ipv4_bytes = [0u8; 4];
- thread_rng().fill(&mut ipv4_bytes);
- (
- SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0),
- SocketAddr::new(IpAddr::from(ipv4_bytes).into(), rand_port),
- )
- } else {
- let mut ipv6_bytes = [0u8; 16];
- tried_ipv6 = true;
- thread_rng().fill(&mut ipv6_bytes);
- (
- SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 0),
- SocketAddr::new(IpAddr::from(ipv6_bytes).into(), rand_port),
- )
- };
-
- // TODO: once https://github.com/rust-lang/rust/issues/27709 is resolved, please use
- // `is_global()` to check if a new address should be attempted.
- if !is_public_ip(rand_dest_addr.ip()) {
- continue;
- }
-
- let socket = UdpSocket::bind(local_addr).map_err(Error::BindUdpSocket)?;
-
- let mut buf = vec![0u8; thread_rng().gen_range(17, 214)];
- // fill buff with random data
- thread_rng().fill(buf.as_mut_slice());
- match socket.send_to(&buf, rand_dest_addr) {
- Ok(_) => return Ok(()),
- Err(err) => {
- if tried_ipv6 {
- continue;
- }
- match err.raw_os_error() {
- // Error code 101 - specified network is unreachable
- // Error code 22 - specified address is not usable
- Some(101) | Some(22) => {
- // if we failed whilst trying to send to IPv6, we should not try
- // IPv6 again.
- continue;
- }
- _ => return Err(Error::SendToUdpSocket(err)),
- }
- }
- };
- }
- Ok(())
- }
-
/// Open a tunnel device using the previous or the default configuration.
///
/// Will open a new tunnel if there is already an active tunnel. The previous tunnel will be
/// closed.
pub fn create_tun(&mut self) -> Result<(), Error> {
- self.open_tun(self.last_tun_config.clone())
+ let result = self.call_method(
+ "createTun",
+ "()V",
+ JavaType::Primitive(Primitive::Void),
+ &[],
+ )?;
+
+ match result {
+ JValue::Void => Ok(()),
+ value => Err(Error::InvalidMethodResult(
+ "createTun",
+ format!("{:?}", value),
+ )),
+ }
}
/// Open a tunnel device using the previous or the default configuration if there is no
/// currently active tunnel.
pub fn create_tun_if_closed(&mut self) -> Result<(), Error> {
- if self.active_tun.is_none() {
- self.create_tun()?;
- }
+ let result = self.call_method(
+ "createTunIfClosed",
+ "()V",
+ JavaType::Primitive(Primitive::Void),
+ &[],
+ )?;
- Ok(())
+ match result {
+ JValue::Void => Ok(()),
+ value => Err(Error::InvalidMethodResult(
+ "createTunIfClosed",
+ format!("{:?}", value),
+ )),
+ }
}
/// Close currently active tunnel device.
pub fn close_tun(&mut self) {
- self.active_tun = None;
+ let result = self.call_method("closeTun", "()V", JavaType::Primitive(Primitive::Void), &[]);
+
+ let error = match result {
+ Ok(JValue::Void) => None,
+ Ok(value) => Some(Error::InvalidMethodResult(
+ "closeTun",
+ format!("{:?}", value),
+ )),
+ Err(error) => Some(error),
+ };
+
+ if let Some(error) = error {
+ log::error!(
+ "{}",
+ error.display_chain_with_msg("Failed to close the tunnel")
+ );
+ }
}
fn get_tun_fd(&mut self, config: TunConfig) -> Result<RawFd, Error> {
- if self.active_tun.is_none() || self.last_tun_config != config {
- self.open_tun(config)?;
+ let env = self.env()?;
+ let actual_config = self.prepare_tun_config(config);
+ let java_config = actual_config.into_java(&env);
+
+ let result = self.call_method(
+ "getTun",
+ "(Lnet/mullvad/talpid/tun_provider/TunConfig;)I",
+ JavaType::Primitive(Primitive::Int),
+ &[JValue::Object(java_config.as_obj())],
+ )?;
+
+ match result {
+ JValue::Int(0) => Err(Error::TunnelDeviceError),
+ JValue::Int(-1) => Err(Error::PermissionDenied),
+ JValue::Int(fd) => Ok(fd),
+ value => Err(Error::InvalidMethodResult("getTun", format!("{:?}", value))),
}
+ }
+
+ fn recreate_tun_if_open(&mut self) -> Result<(), Error> {
+ let env = self.env()?;
+ let actual_config = self.prepare_tun_config(self.last_tun_config.clone());
+ let java_config = actual_config.into_java(&env);
+
+ let result = self.call_method(
+ "recreateTunIfOpen",
+ "(Lnet/mullvad/talpid/tun_provider/TunConfig;)V",
+ JavaType::Primitive(Primitive::Void),
+ &[JValue::Object(java_config.as_obj())],
+ )?;
- Ok(self
- .active_tun
- .as_ref()
- .expect("Tunnel should be configured")
- .as_raw_fd())
+ match result {
+ JValue::Void => Ok(()),
+ value => Err(Error::InvalidMethodResult("getTun", format!("{:?}", value))),
+ }
}
fn prepare_tun_config(&self, config: TunConfig) -> TunConfig {
@@ -313,87 +249,34 @@ impl AndroidTunProvider {
}
}
- fn open_tun(&mut self, config: TunConfig) -> Result<(), Error> {
- let actual_config = self.prepare_tun_config(config.clone());
-
+ fn call_method(
+ &self,
+ name: &'static str,
+ signature: &str,
+ return_type: JavaType,
+ parameters: &[JValue<'_>],
+ ) -> Result<JValue<'_>, Error> {
let env = JnixEnv::from(
self.jvm
.attach_current_thread_as_daemon()
.map_err(Error::AttachJvmToThread)?,
);
- let create_tun_method = env
- .get_method_id(
- &self.class,
- "createTun",
- "(Lnet/mullvad/talpid/tun_provider/TunConfig;)I",
- )
- .map_err(|cause| Error::FindMethod("createTun", cause))?;
-
- let java_config = actual_config.clone().into_java(&env);
- let result = env
- .call_method_unchecked(
- self.object.as_obj(),
- create_tun_method,
- JavaType::Primitive(Primitive::Int),
- &[JValue::Object(java_config.as_obj())],
- )
- .map_err(|cause| Error::CallMethod("createTun", cause))?;
-
- match result {
- JValue::Int(0) => Err(Error::TunnelDeviceError),
- JValue::Int(-1) => Err(Error::PermissionDenied),
- JValue::Int(fd) => {
- Self::wait_for_tunnel_up(fd, &config)?;
- let tun = unsafe { File::from_raw_fd(fd) };
+ let method_id = env
+ .get_method_id(&self.class, name, signature)
+ .map_err(|cause| Error::FindMethod(name, cause))?;
- self.active_tun = Some(tun);
- self.last_tun_config = config;
-
- Ok(())
- }
- value => Err(Error::InvalidMethodResult(
- "createTun",
- format!("{:?}", value),
- )),
- }
+ env.call_method_unchecked(self.object.as_obj(), method_id, return_type, parameters)
+ .map_err(|cause| Error::CallMethod(name, cause))
}
-}
-fn is_public_ip(addr: IpAddr) -> bool {
- match addr {
- IpAddr::V4(ipv4) => {
- // 0.x.x.x is not a publicly routable address
- if ipv4.octets()[0] == 0u8 {
- return false;
- }
- }
- IpAddr::V6(ipv6) => {
- if ipv6.segments()[0] == 0u16 {
- return false;
- }
- }
- }
- // A non-exhaustive list of non-public subnets
- let publicly_unroutable_subnets: Vec<IpNetwork> = vec![
- // IPv4 local networks
- "10.0.0.0/8".parse().unwrap(),
- "172.16.0.0/12".parse().unwrap(),
- "192.168.0.0/16".parse().unwrap(),
- // IPv4 non-forwardable network
- "169.254.0.0/16".parse().unwrap(),
- "192.0.0.0/8".parse().unwrap(),
- // Documentation networks
- "192.0.2.0/24".parse().unwrap(),
- "198.51.100.0/24".parse().unwrap(),
- "203.0.113.0/24".parse().unwrap(),
- // IPv6 publicly unroutable networks
- "fc00::/7".parse().unwrap(),
- "fe80::/10".parse().unwrap(),
- ];
+ fn env(&self) -> Result<JnixEnv<'_>, Error> {
+ let jni_env = self
+ .jvm
+ .attach_current_thread_as_daemon()
+ .map_err(Error::AttachJvmToThread)?;
- !publicly_unroutable_subnets
- .iter()
- .any(|net| net.contains(addr))
+ Ok(JnixEnv::from(jni_env))
+ }
}
/// Handle to a tunnel device on Android.
@@ -443,3 +326,23 @@ impl AsRawFd for VpnServiceTun {
self.tunnel
}
}
+
+impl Default for TunConfig {
+ fn default() -> Self {
+ // Default configuration simply intercepts all packets. The only field that matters is
+ // `routes`, because it determines what must enter the tunnel. All other fields contain
+ // stub values.
+ TunConfig {
+ addresses: vec![IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))],
+ dns_servers: Vec::new(),
+ routes: vec![
+ IpNetwork::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0)
+ .expect("Invalid IP network prefix for IPv4 address"),
+ IpNetwork::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)), 0)
+ .expect("Invalid IP network prefix for IPv6 address"),
+ ],
+ required_routes: vec![],
+ mtu: 1380,
+ }
+ }
+}