Skip to content

Commit

Permalink
Wait for routes after invoking establish (openTun)
Browse files Browse the repository at this point in the history
  • Loading branch information
Rawa committed Feb 12, 2025
1 parent a7ec806 commit fed4a5f
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 31 deletions.
48 changes: 33 additions & 15 deletions talpid-routing/src/unix/android.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::collections::HashSet;
use std::ops::{ControlFlow, Not};
use std::ops::{ControlFlow};
use std::sync::Mutex;

use futures::channel::mpsc::{self, UnboundedReceiver, UnboundedSender};
Expand Down Expand Up @@ -51,7 +51,7 @@ pub struct RouteManagerImpl {
last_state: Option<NetworkState>,

/// Clients waiting on response to [RouteManagerCommand::WaitForRoutes].
waiting_for_routes: Vec<oneshot::Sender<()>>,
waiting_for_routes: Vec<(oneshot::Sender<()>, Vec<Route>)>,
}

impl RouteManagerImpl {
Expand All @@ -64,7 +64,7 @@ impl RouteManagerImpl {

// Try to poll for the current network state at startup.
// This will most likely be null, but it covers the edge case where a NetworkState
// update has been emitted before we anyone starts to listen for route updates some
// update has been emitted before anyone starts to listen for route updates some
// time in the future (when connecting).
let last_state = match current_network_state(android_context) {
Ok(initial_state) => initial_state,
Expand Down Expand Up @@ -105,12 +105,17 @@ impl RouteManagerImpl {
// update the last known NetworkState
self.last_state = network_state;

if has_routes(self.last_state.as_ref()) {
// notify waiting clients that routes exist
for client in self.waiting_for_routes.drain(..) {
// notify waiting clients that routes exist
let mut unused_routes: Vec<(oneshot::Sender<()>, Vec<Route>)> = Vec::new();
let ret = for (client, expected_routes) in self.waiting_for_routes.drain(..) {
if has_routes(self.last_state.as_ref(), expected_routes.clone()) {
let _ = client.send(());
} else {
unused_routes.push((client, expected_routes));
}
}
};
self.waiting_for_routes = unused_routes;
ret
}
}
}
Expand All @@ -126,31 +131,44 @@ impl RouteManagerImpl {
let _ = tx.send(());
return ControlFlow::Break(());
}
RouteManagerCommand::WaitForRoutes(response_tx) => {
RouteManagerCommand::WaitForRoutes(response_tx, expected_routes) => {
// check if routes have already been configured on the Android system.
// otherwise, register a listener for network state changes.
// routes may come in at any moment in the future.
if has_routes(self.last_state.as_ref()) {
if has_routes(self.last_state.as_ref(), expected_routes.clone()) {
let _ = response_tx.send(());
} else {
self.waiting_for_routes.push(response_tx);
self.waiting_for_routes.push((response_tx, expected_routes));
}
}
RouteManagerCommand::ClearRoutes(tx) => {
self.clear_routes();
let _ = tx.send(());
}
}

ControlFlow::Continue(())
}

pub fn clear_routes(&mut self) {
self.last_state = None;
}
}

/// Check whether the [NetworkState] contains any routes.
/// Check whether the [NetworkState] contains expected routes.
///
/// Since we are the ones telling Android what routes to set, we make the assumption that:
/// If any routes exist whatsoever, they are the the routes we specified.
fn has_routes(state: Option<&NetworkState>) -> bool {
/// Matches the routes reported from Android and checks if all the routes we expect to be there is
/// present.
fn has_routes(state: Option<&NetworkState>, expected_routes: Vec<Route>) -> bool {
let Some(network_state) = state else {
return false;
};
configured_routes(network_state).is_empty().not()

let routes = configured_routes(network_state);
if routes.is_empty() {
return false;
}
routes.is_superset(&HashSet::from_iter(expected_routes))
}

fn configured_routes(state: &NetworkState) -> HashSet<Route> {
Expand Down
19 changes: 16 additions & 3 deletions talpid-routing/src/unix/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ mod imp;
#[path = "android.rs"]
mod imp;

#[cfg(target_os = "android")]
use crate::Route;
#[cfg(any(target_os = "macos", target_os = "linux"))]
pub use imp::Error as PlatformError;

Expand Down Expand Up @@ -103,7 +105,8 @@ pub(crate) enum RouteManagerCommand {
#[cfg(target_os = "android")]
#[derive(Debug)]
pub(crate) enum RouteManagerCommand {
WaitForRoutes(oneshot::Sender<()>),
ClearRoutes(oneshot::Sender<()>),
WaitForRoutes(oneshot::Sender<()>, Vec<Route>),
Shutdown(oneshot::Sender<()>),
}

Expand Down Expand Up @@ -215,7 +218,7 @@ impl RouteManagerHandle {
/// This function is guaranteed to *not* wait for longer than 2 seconds.
/// Please, see the implementation of this function for further details.
#[cfg(target_os = "android")]
pub async fn wait_for_routes(&self) -> Result<(), Error> {
pub async fn wait_for_routes(&self, expect_routes: Vec<Route>) -> Result<(), Error> {
use std::time::Duration;
use tokio::time::timeout;
/// Maximum time to wait for routes to come up. The expected mean time is low (~200 ms), but
Expand All @@ -224,7 +227,7 @@ impl RouteManagerHandle {

let (result_tx, result_rx) = oneshot::channel();
self.tx
.unbounded_send(RouteManagerCommand::WaitForRoutes(result_tx))
.unbounded_send(RouteManagerCommand::WaitForRoutes(result_tx, expect_routes))
.map_err(|_| Error::RouteManagerDown)?;

timeout(WAIT_FOR_ROUTES_TIMEOUT, result_rx)
Expand All @@ -247,6 +250,16 @@ impl RouteManagerHandle {
Ok(())
}

/// xD
pub async fn clear_android_routes(&self) -> Result<(), Error> {
let (result_tx, result_rx) = oneshot::channel();
self.tx
.unbounded_send(RouteManagerCommand::ClearRoutes(result_tx))
.map_err(|_| Error::RouteManagerDown)?;
let _ = result_rx.await;
Ok(())
}

/// Listen for non-tunnel default route changes.
#[cfg(target_os = "macos")]
pub async fn default_route_listener(
Expand Down
23 changes: 21 additions & 2 deletions talpid-tunnel/src/tun_provider/android/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use std::{
os::unix::io::{AsRawFd, RawFd},
sync::Arc,
};
use talpid_routing::Route;
use talpid_types::net::{ALLOWED_LAN_MULTICAST_NETS, ALLOWED_LAN_NETS};
use talpid_types::{android::AndroidContext, ErrorExt};

Expand Down Expand Up @@ -188,6 +189,14 @@ impl AndroidTunProvider {
}
}

pub fn real_routes(&self) -> Vec<Route> {
self.config
.real_routes()
.iter()
.map(|ip_network| Route::new(ip_network.clone()))
.collect()
}

fn call_method(
&self,
name: &'static str,
Expand Down Expand Up @@ -221,7 +230,7 @@ impl AndroidTunProvider {
/// Configuration to use for VpnService
#[derive(Clone, Debug, Eq, PartialEq, IntoJava)]
#[jnix(class_name = "net.mullvad.talpid.model.TunConfig")]
struct VpnServiceConfig {
pub struct VpnServiceConfig {
/// IP addresses for the tunnel interface.
pub addresses: Vec<IpAddr>,

Expand Down Expand Up @@ -318,7 +327,7 @@ impl VpnServiceConfig {

#[derive(Clone, Debug, Eq, PartialEq, IntoJava)]
#[jnix(package = "net.mullvad.talpid.model")]
struct InetNetwork {
pub struct InetNetwork {
address: IpAddr,
prefix: i16,
}
Expand All @@ -332,6 +341,16 @@ impl From<IpNetwork> for InetNetwork {
}
}

impl From<&InetNetwork> for IpNetwork {
fn from(inet_network: &InetNetwork) -> Self {
IpNetwork::new(
inet_network.address,
inet_network.prefix.to_be_bytes().last().unwrap().clone(),
)
.unwrap()
}
}

/// Handle to a tunnel device on Android.
pub struct VpnServiceTun {
tunnel: RawFd,
Expand Down
13 changes: 13 additions & 0 deletions talpid-tunnel/src/tun_provider/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#[cfg(target_os = "android")]
use crate::tun_provider::imp::VpnServiceConfig;
use cfg_if::cfg_if;
use ipnetwork::IpNetwork;
use std::{
Expand Down Expand Up @@ -73,6 +75,17 @@ impl TunConfig {
}
servers
}

/// Routes to configure for the tunnel.
#[cfg(target_os = "android")]
pub fn real_routes(&self) -> Vec<IpNetwork> {
VpnServiceConfig::new(self.clone())
.routes
.clone()
.iter()
.map(|x| IpNetwork::from(x))
.collect()
}
}

/// Return a tunnel configuration that routes all traffic inside the tunnel.
Expand Down
13 changes: 6 additions & 7 deletions talpid-wireguard/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ use talpid_tunnel::{
tun_provider::TunProvider, EventHook, TunnelArgs, TunnelEvent, TunnelMetadata,
};

#[cfg(target_os = "android")]
use talpid_routing::RouteManagerHandle;
#[cfg(daita)]
use talpid_tunnel_config_client::DaitaSettings;
use talpid_types::{
Expand Down Expand Up @@ -434,6 +436,7 @@ impl WireguardMonitor {
&config,
log_path,
args.tun_provider.clone(),
args.route_manager,
// In case we should negotiate an ephemeral peer, we should specify via AllowedIPs
// that we only allows traffic to/from the gateway. This is only needed on Android
// since we lack a firewall there.
Expand Down Expand Up @@ -465,13 +468,6 @@ impl WireguardMonitor {
.on_event(TunnelEvent::InterfaceUp(metadata.clone(), allowed_traffic))
.await;

// Wait for routes to come up
args.route_manager
.wait_for_routes()
.await
.map_err(Error::SetupRoutingError)
.map_err(CloseMsg::SetupError)?;

if should_negotiate_ephemeral_peer {
let ephemeral_obfs_sender = close_obfs_sender.clone();

Expand Down Expand Up @@ -743,6 +739,7 @@ impl WireguardMonitor {
config: &Config,
log_path: Option<&Path>,
#[cfg(unix)] tun_provider: Arc<Mutex<TunProvider>>,
#[cfg(target_os = "android")] route_manager: RouteManagerHandle,
#[cfg(windows)] setup_done_tx: mpsc::Sender<std::result::Result<(), BoxedError>>,
#[cfg(windows)] route_manager: talpid_routing::RouteManagerHandle,
#[cfg(target_os = "android")] gateway_only: bool,
Expand Down Expand Up @@ -780,6 +777,7 @@ impl WireguardMonitor {
exit_peer,
log_path,
tun_provider,
route_manager,
routes,
cancel_receiver,
)
Expand All @@ -791,6 +789,7 @@ impl WireguardMonitor {
&config,
log_path,
tun_provider,
route_manager,
routes,
cancel_receiver,
)
Expand Down
Loading

0 comments on commit fed4a5f

Please sign in to comment.