summaryrefslogtreecommitdiffhomepage
path: root/talpid-core
diff options
context:
space:
mode:
authorEmīls <emils@mullvad.net>2019-12-09 23:50:34 +0000
committerEmīls <emils@mullvad.net>2019-12-13 18:07:17 +0000
commitb00a47aa2bd0feee35eefaa8d0689518dd5eba4c (patch)
tree0bc60e80de6ef7af9bc53b1d6549d2c4be02d4ad /talpid-core
parent6868537a5b81a194e261d4c57a45a87974640e32 (diff)
downloadmullvadvpn-b00a47aa2bd0feee35eefaa8d0689518dd5eba4c.tar.xz
mullvadvpn-b00a47aa2bd0feee35eefaa8d0689518dd5eba4c.zip
Add blocking wait for TunnelDevice to come up on Android
Diffstat (limited to 'talpid-core')
-rw-r--r--talpid-core/Cargo.toml1
-rw-r--r--talpid-core/src/tunnel/tun_provider/android.rs149
2 files changed, 149 insertions, 1 deletions
diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml
index 1df24dd7c4..bac2c9598c 100644
--- a/talpid-core/Cargo.toml
+++ b/talpid-core/Cargo.toml
@@ -39,6 +39,7 @@ tokio-io = "0.1"
[target.'cfg(target_os = "android")'.dependencies]
jnix = { version = "0.1", features = ["derive"] }
+rand = "0.7"
[target.'cfg(target_os = "linux")'.dependencies]
diff --git a/talpid-core/src/tunnel/tun_provider/android.rs b/talpid-core/src/tunnel/tun_provider/android.rs
index 09adef6c35..84526cd709 100644
--- a/talpid-core/src/tunnel/tun_provider/android.rs
+++ b/talpid-core/src/tunnel/tun_provider/android.rs
@@ -8,11 +8,14 @@ use jnix::{
},
IntoJava, JnixEnv,
};
+use rand::{seq::SliceRandom, thread_rng, Rng};
use std::{
fs::File,
- net::{IpAddr, Ipv4Addr, Ipv6Addr},
+ io,
+ net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket},
os::unix::io::{AsRawFd, FromRawFd, RawFd},
sync::Arc,
+ time::{Duration, Instant},
};
use talpid_types::android::AndroidContext;
@@ -42,6 +45,18 @@ pub enum Error {
_1
)]
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,
}
/// Factory of tunnel devices on Android.
@@ -103,6 +118,100 @@ 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
+ {
+ let elapsed = start.elapsed();
+ 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
@@ -164,6 +273,7 @@ impl AndroidTunProvider {
match result {
JValue::Int(fd) => {
+ Self::wait_for_tunnel_up(fd, &config)?;
let tun = unsafe { File::from_raw_fd(fd) };
self.active_tun = Some(tun);
@@ -179,6 +289,43 @@ 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,