summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--android/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt10
-rw-r--r--mullvad-jni/Cargo.toml2
-rw-r--r--mullvad-jni/src/lib.rs1
-rw-r--r--mullvad-jni/src/talpid_vpn_service.rs177
-rw-r--r--talpid-core/src/tunnel/tun_provider/android/mod.rs148
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,