diff options
| -rw-r--r-- | android/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt | 10 | ||||
| -rw-r--r-- | mullvad-jni/Cargo.toml | 2 | ||||
| -rw-r--r-- | mullvad-jni/src/lib.rs | 1 | ||||
| -rw-r--r-- | mullvad-jni/src/talpid_vpn_service.rs | 177 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/tun_provider/android/mod.rs | 148 |
5 files changed, 190 insertions, 148 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..24bcd16b30 100644 --- a/android/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt +++ b/android/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt @@ -41,8 +41,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 +62,6 @@ open class TalpidVpnService : VpnService() { else -> throw RuntimeException("Invalid IP address (not IPv4 nor IPv6)") } } + + private external fun waitForTunnelUp(tunFd: Int, isIpv6Enabled: Boolean) } 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..0e266970ba --- /dev/null +++ b/mullvad-jni/src/talpid_vpn_service.rs @@ -0,0 +1,177 @@ +use ipnetwork::IpNetwork; +use jnix::jni::{ + objects::JObject, + sys::{jboolean, jint, JNI_FALSE}, + JNIEnv, +}; +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_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_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/src/tunnel/tun_provider/android/mod.rs b/talpid-core/src/tunnel/tun_provider/android/mod.rs index 7558f067b0..2c1c8561c7 100644 --- a/talpid-core/src/tunnel/tun_provider/android/mod.rs +++ b/talpid-core/src/tunnel/tun_provider/android/mod.rs @@ -11,14 +11,11 @@ use jnix::{ }, IntoJava, JnixEnv, }; -use rand::{seq::SliceRandom, thread_rng, Rng}; use std::{ fs::File, - io, - net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket}, + net::{IpAddr, Ipv4Addr, Ipv6Addr}, os::unix::io::{AsRawFd, FromRawFd, RawFd}, sync::Arc, - time::{Duration, Instant}, }; use talpid_types::android::AndroidContext; @@ -49,18 +46,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, @@ -142,99 +127,6 @@ 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 @@ -343,7 +235,6 @@ impl AndroidTunProvider { 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) }; self.active_tun = Some(tun); @@ -359,43 +250,6 @@ impl AndroidTunProvider { } } -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)) -} - /// Handle to a tunnel device on Android. pub struct VpnServiceTun { tunnel: RawFd, |
