diff --git a/Cargo.lock b/Cargo.lock index 6b387409b427..d955c2ca589d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4041,6 +4041,7 @@ dependencies = [ "jnix", "log", "nix 0.23.2", + "once_cell", "talpid-routing", "talpid-types", "talpid-windows", @@ -4078,6 +4079,7 @@ dependencies = [ "ipnetwork", "jnix", "log", + "once_cell", "serde", "thiserror", "x25519-dalek", diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt index 2a854f7e5cce..1c4b51a52038 100644 --- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt @@ -26,10 +26,7 @@ open class TalpidVpnService : LifecycleVpnService() { } } - private val tunIsOpen - get() = activeTunStatus?.isOpen ?: false - - private var currentTunConfig = defaultTunConfig() + private var currentTunConfig: TunConfig? = null // Used by JNI val connectivityListener = ConnectivityListener() @@ -46,36 +43,33 @@ open class TalpidVpnService : LifecycleVpnService() { connectivityListener.unregister() } - fun getTun(config: TunConfig): CreateTunResult { + fun openTun(config: TunConfig): CreateTunResult { synchronized(this) { val tunStatus = activeTunStatus - if (config == currentTunConfig && tunIsOpen) { - return tunStatus!! + if (config == currentTunConfig && tunStatus != null && tunStatus.isOpen) { + return tunStatus } else { - val newTunStatus = createTun(config) - - currentTunConfig = config - activeTunStatus = newTunStatus - - return newTunStatus + return openTunImpl(config) } } } - fun createTun() { - synchronized(this) { activeTunStatus = createTun(currentTunConfig) } - } - - fun recreateTunIfOpen(config: TunConfig) { + fun openTunForced(config: TunConfig): CreateTunResult { synchronized(this) { - if (tunIsOpen) { - currentTunConfig = config - activeTunStatus = createTun(config) - } + return openTunImpl(config) } } + private fun openTunImpl(config: TunConfig): CreateTunResult { + val newTunStatus = createTun(config) + + currentTunConfig = config + activeTunStatus = newTunStatus + + return newTunStatus + } + fun closeTun() { synchronized(this) { activeTunStatus = null } } @@ -151,8 +145,6 @@ open class TalpidVpnService : LifecycleVpnService() { } } - private external fun defaultTunConfig(): TunConfig - private external fun waitForTunnelUp(tunFd: Int, isIpv6Enabled: Boolean) companion object { diff --git a/mullvad-jni/src/talpid_vpn_service.rs b/mullvad-jni/src/talpid_vpn_service.rs index a192444e7b6c..ea6928538a86 100644 --- a/mullvad-jni/src/talpid_vpn_service.rs +++ b/mullvad-jni/src/talpid_vpn_service.rs @@ -1,11 +1,8 @@ use ipnetwork::IpNetwork; -use jnix::{ - jni::{ - objects::JObject, - sys::{jboolean, jint, JNI_FALSE}, - JNIEnv, - }, - IntoJava, JnixEnv, +use jnix::jni::{ + objects::JObject, + sys::{jboolean, jint, JNI_FALSE}, + JNIEnv, }; use nix::sys::{ select::{pselect, FdSet}, @@ -18,7 +15,6 @@ use std::{ os::unix::io::RawFd, time::{Duration, Instant}, }; -use talpid_tunnel::tun_provider::TunConfig; use talpid_types::ErrorExt; #[derive(Debug, thiserror::Error)] @@ -33,17 +29,6 @@ enum Error { 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( diff --git a/talpid-core/src/firewall/linux.rs b/talpid-core/src/firewall/linux.rs index 7c3cb26ec7b0..accac083c327 100644 --- a/talpid-core/src/firewall/linux.rs +++ b/talpid-core/src/firewall/linux.rs @@ -12,7 +12,10 @@ use std::{ fs, io, net::{IpAddr, Ipv4Addr}, }; -use talpid_types::net::{AllowedEndpoint, AllowedTunnelTraffic, Endpoint, TransportProtocol}; +use talpid_types::net::{ + AllowedEndpoint, AllowedTunnelTraffic, Endpoint, TransportProtocol, ALLOWED_LAN_MULTICAST_NETS, + ALLOWED_LAN_NETS, +}; /// Priority for rules that tag split tunneling packets. Equals NF_IP_PRI_MANGLE. const MANGLE_CHAIN_PRIORITY: i32 = libc::NF_IP_PRI_MANGLE; @@ -840,7 +843,7 @@ impl<'a> PolicyBatch<'a> { // Output and forward chains for chain in &[&self.out_chain, &self.forward_chain] { // LAN -> LAN - for net in &*super::ALLOWED_LAN_NETS { + for net in &*ALLOWED_LAN_NETS { let mut out_rule = Rule::new(chain); check_net(&mut out_rule, End::Dst, *net); add_verdict(&mut out_rule, &Verdict::Accept); @@ -848,7 +851,7 @@ impl<'a> PolicyBatch<'a> { } // LAN -> Multicast - for net in &*super::ALLOWED_LAN_MULTICAST_NETS { + for net in &*ALLOWED_LAN_MULTICAST_NETS { let mut rule = Rule::new(chain); check_net(&mut rule, End::Dst, *net); add_verdict(&mut rule, &Verdict::Accept); @@ -858,7 +861,7 @@ impl<'a> PolicyBatch<'a> { // Input chain // LAN -> LAN - for net in &*super::ALLOWED_LAN_NETS { + for net in &*ALLOWED_LAN_NETS { let mut in_rule = Rule::new(&self.in_chain); check_net(&mut in_rule, End::Src, *net); add_verdict(&mut in_rule, &Verdict::Accept); diff --git a/talpid-core/src/firewall/macos.rs b/talpid-core/src/firewall/macos.rs index c86a375b8b68..51ae7ef70843 100644 --- a/talpid-core/src/firewall/macos.rs +++ b/talpid-core/src/firewall/macos.rs @@ -8,7 +8,9 @@ use std::{ ptr, }; use subslice::SubsliceExt; -use talpid_types::net::{self, AllowedEndpoint, AllowedTunnelTraffic}; +use talpid_types::net::{ + self, AllowedEndpoint, AllowedTunnelTraffic, ALLOWED_LAN_MULTICAST_NETS, ALLOWED_LAN_NETS, +}; pub use pfctl::Error; @@ -494,7 +496,7 @@ impl Firewall { fn get_allow_lan_rules(&self) -> Result> { let mut rules = vec![]; - for net in &*super::ALLOWED_LAN_NETS { + for net in &*ALLOWED_LAN_NETS { let mut rule_builder = self.create_rule_builder(FilterRuleAction::Pass); rule_builder.quick(true); let allow_out = rule_builder @@ -510,7 +512,7 @@ impl Firewall { rules.push(allow_out); rules.push(allow_in); } - for multicast_net in &*super::ALLOWED_LAN_MULTICAST_NETS { + for multicast_net in &*ALLOWED_LAN_MULTICAST_NETS { let allow_multicast_out = self .create_rule_builder(FilterRuleAction::Pass) .quick(true) diff --git a/talpid-core/src/firewall/mod.rs b/talpid-core/src/firewall/mod.rs index 1e6f2ae24765..1ef8fd7af701 100644 --- a/talpid-core/src/firewall/mod.rs +++ b/talpid-core/src/firewall/mod.rs @@ -4,7 +4,7 @@ use std::{ fmt, net::{IpAddr, Ipv4Addr, Ipv6Addr}, }; -use talpid_types::net::{AllowedEndpoint, AllowedTunnelTraffic}; +use talpid_types::net::{AllowedEndpoint, AllowedTunnelTraffic, ALLOWED_LAN_NETS}; #[cfg(target_os = "macos")] #[path = "macos.rs"] @@ -24,39 +24,6 @@ mod imp; pub use self::imp::Error; -/// When "allow local network" is enabled the app will allow traffic to and from these networks. -pub(crate) static ALLOWED_LAN_NETS: Lazy<[IpNetwork; 6]> = Lazy::new(|| { - [ - IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(10, 0, 0, 0), 8).unwrap()), - IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(172, 16, 0, 0), 12).unwrap()), - IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(192, 168, 0, 0), 16).unwrap()), - IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(169, 254, 0, 0), 16).unwrap()), - IpNetwork::V6(Ipv6Network::new(Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0), 10).unwrap()), - IpNetwork::V6(Ipv6Network::new(Ipv6Addr::new(0xfc00, 0, 0, 0, 0, 0, 0, 0), 7).unwrap()), - ] -}); -/// When "allow local network" is enabled the app will allow traffic to these networks. -#[cfg(any(target_os = "linux", target_os = "macos", target_os = "android"))] -pub(crate) static ALLOWED_LAN_MULTICAST_NETS: Lazy<[IpNetwork; 8]> = Lazy::new(|| { - [ - // Local network broadcast. Not routable - IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(255, 255, 255, 255), 32).unwrap()), - // Local subnetwork multicast. Not routable - IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(224, 0, 0, 0), 24).unwrap()), - // Admin-local IPv4 multicast. - IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(239, 0, 0, 0), 8).unwrap()), - // Interface-local IPv6 multicast. - IpNetwork::V6(Ipv6Network::new(Ipv6Addr::new(0xff01, 0, 0, 0, 0, 0, 0, 0), 16).unwrap()), - // Link-local IPv6 multicast. IPv6 equivalent of 224.0.0.0/24 - IpNetwork::V6(Ipv6Network::new(Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0, 0), 16).unwrap()), - // Realm-local IPv6 multicast. - IpNetwork::V6(Ipv6Network::new(Ipv6Addr::new(0xff03, 0, 0, 0, 0, 0, 0, 0), 16).unwrap()), - // Admin-local IPv6 multicast. - IpNetwork::V6(Ipv6Network::new(Ipv6Addr::new(0xff04, 0, 0, 0, 0, 0, 0, 0), 16).unwrap()), - // Site-local IPv6 multicast. - IpNetwork::V6(Ipv6Network::new(Ipv6Addr::new(0xff05, 0, 0, 0, 0, 0, 0, 0), 16).unwrap()), - ] -}); #[cfg(any(target_os = "linux", target_os = "macos"))] static IPV6_LINK_LOCAL: Lazy = Lazy::new(|| Ipv6Network::new(Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0), 10).unwrap()); @@ -76,10 +43,8 @@ static SOLICITED_NODE_MULTICAST: Lazy = Lazy::new(|| Ipv6Network::new(Ipv6Addr::new(0xff02, 0, 0, 0, 0, 1, 0xFF00, 0), 104).unwrap()); static LOOPBACK_NETS: Lazy<[IpNetwork; 2]> = Lazy::new(|| { [ - IpNetwork::V4(ipnetwork::Ipv4Network::new(Ipv4Addr::new(127, 0, 0, 0), 8).unwrap()), - IpNetwork::V6( - ipnetwork::Ipv6Network::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 128).unwrap(), - ), + IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(127, 0, 0, 0), 8).unwrap()), + IpNetwork::V6(Ipv6Network::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 128).unwrap()), ] }); diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs index 8208223224f5..ff3a4fec0ad2 100644 --- a/talpid-core/src/tunnel_state_machine/connected_state.rs +++ b/talpid-core/src/tunnel_state_machine/connected_state.rs @@ -106,17 +106,11 @@ impl ConnectedState { }) } - #[allow(unused_variables)] fn get_dns_servers(&self, shared_values: &SharedTunnelStateValues) -> Vec { - if let Some(ref servers) = shared_values.dns_servers { - servers.clone() - } else { - let mut dns_ips = vec![self.metadata.ipv4_gateway.into()]; - if let Some(ipv6_gateway) = self.metadata.ipv6_gateway { - dns_ips.push(ipv6_gateway.into()); - }; - dns_ips - } + shared_values + .dns_servers + .clone() + .unwrap_or_else(|| self.metadata.gateways()) } fn get_firewall_policy(&self, shared_values: &SharedTunnelStateValues) -> FirewallPolicy { @@ -228,23 +222,34 @@ impl ConnectedState { match command { Some(TunnelCommand::AllowLan(allow_lan, complete_tx)) => { - let consequence = if let Err(error_cause) = shared_values.set_allow_lan(allow_lan) { - self.disconnect(shared_values, AfterDisconnect::Block(error_cause)) - } else { - match self.set_firewall_policy(shared_values) { - Ok(()) => { - if cfg!(target_os = "android") { - self.disconnect(shared_values, AfterDisconnect::Reconnect(0)) - } else { - SameState(self) - } + let consequence = if shared_values.set_allow_lan(allow_lan) { + #[cfg(target_os = "android")] + { + if let Err(_err) = shared_values.restart_tunnel(false) { + self.disconnect( + shared_values, + AfterDisconnect::Block(ErrorStateCause::StartTunnelError), + ) + } else { + self.disconnect(shared_values, AfterDisconnect::Reconnect(0)) } - Err(error) => self.disconnect( - shared_values, - AfterDisconnect::Block(ErrorStateCause::SetFirewallPolicyError(error)), - ), } + #[cfg(not(target_os = "android"))] + { + match self.set_firewall_policy(shared_values) { + Ok(()) => SameState(self), + Err(error) => self.disconnect( + shared_values, + AfterDisconnect::Block(ErrorStateCause::SetFirewallPolicyError( + error, + )), + ), + } + } + } else { + SameState(self) }; + let _ = complete_tx.send(()); consequence } @@ -254,8 +259,20 @@ impl ConnectedState { SameState(self) } Some(TunnelCommand::Dns(servers, complete_tx)) => { - let consequence = match shared_values.set_dns_servers(servers) { - Ok(true) => { + let consequence = if shared_values.set_dns_servers(servers) { + #[cfg(target_os = "android")] + { + if let Err(_err) = shared_values.restart_tunnel(false) { + self.disconnect( + shared_values, + AfterDisconnect::Block(ErrorStateCause::StartTunnelError), + ) + } else { + self.disconnect(shared_values, AfterDisconnect::Reconnect(0)) + } + } + #[cfg(not(target_os = "android"))] + { if let Err(error) = self.set_firewall_policy(shared_values) { return self.disconnect( shared_values, @@ -266,9 +283,6 @@ impl ConnectedState { } match self.set_dns(shared_values) { - #[cfg(target_os = "android")] - Ok(()) => self.disconnect(shared_values, AfterDisconnect::Reconnect(0)), - #[cfg(not(target_os = "android"))] Ok(()) => SameState(self), Err(error) => { log::error!( @@ -282,10 +296,8 @@ impl ConnectedState { } } } - Ok(false) => SameState(self), - Err(error_cause) => { - self.disconnect(shared_values, AfterDisconnect::Block(error_cause)) - } + } else { + SameState(self) }; let _ = complete_tx.send(()); consequence @@ -327,22 +339,21 @@ impl ConnectedState { } #[cfg(target_os = "android")] Some(TunnelCommand::SetExcludedApps(result_tx, paths)) => { - match shared_values.exclude_paths(paths) { - Ok(changed) => { - let _ = result_tx.send(Ok(())); - if changed { - self.disconnect(shared_values, AfterDisconnect::Reconnect(0)) - } else { - SameState(self) - } - } - Err(err) => { - let _ = result_tx.send(Err(err)); + if shared_values.set_excluded_paths(paths) { + if let Err(err) = shared_values.restart_tunnel(false) { + let _ = + result_tx.send(Err(crate::split_tunnel::Error::SetExcludedApps(err))); self.disconnect( shared_values, AfterDisconnect::Block(ErrorStateCause::SplitTunnelError), ) + } else { + let _ = result_tx.send(Ok(())); + self.disconnect(shared_values, AfterDisconnect::Reconnect(0)) } + } else { + let _ = result_tx.send(Ok(())); + SameState(self) } } #[cfg(target_os = "macos")] diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs index 9e6a21190cfe..d9a58a0ea723 100644 --- a/talpid-core/src/tunnel_state_machine/connecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs @@ -100,9 +100,10 @@ impl ConnectingState { } else { #[cfg(target_os = "android")] { + shared_values.prepare_tun_config(false); if retry_attempt > 0 && retry_attempt % MAX_ATTEMPTS_WITH_SAME_TUN == 0 { if let Err(error) = - { shared_values.tun_provider.lock().unwrap().create_tun() } + { shared_values.tun_provider.lock().unwrap().open_tun_forced() } { log::error!( "{}", @@ -370,6 +371,7 @@ impl ConnectingState { )) } + #[cfg(not(target_os = "android"))] fn reset_firewall( self: Box, shared_values: &mut SharedTunnelStateValues, @@ -380,13 +382,7 @@ impl ConnectingState { &self.tunnel_metadata, self.allowed_tunnel_traffic.clone(), ) { - Ok(()) => { - if cfg!(target_os = "android") { - self.disconnect(shared_values, AfterDisconnect::Reconnect(0)) - } else { - EventConsequence::SameState(self) - } - } + Ok(()) => EventConsequence::SameState(self), Err(error) => self.disconnect( shared_values, AfterDisconnect::Block(ErrorStateCause::SetFirewallPolicyError(error)), @@ -403,10 +399,22 @@ impl ConnectingState { match command { Some(TunnelCommand::AllowLan(allow_lan, complete_tx)) => { - let consequence = if let Err(error_cause) = shared_values.set_allow_lan(allow_lan) { - self.disconnect(shared_values, AfterDisconnect::Block(error_cause)) - } else { + let consequence = if shared_values.set_allow_lan(allow_lan) { + #[cfg(target_os = "android")] + { + if let Err(_err) = shared_values.restart_tunnel(false) { + self.disconnect( + shared_values, + AfterDisconnect::Block(ErrorStateCause::StartTunnelError), + ) + } else { + self.disconnect(shared_values, AfterDisconnect::Reconnect(0)) + } + } + #[cfg(not(target_os = "android"))] self.reset_firewall(shared_values) + } else { + SameState(self) }; let _ = complete_tx.send(()); consequence @@ -431,12 +439,24 @@ impl ConnectingState { SameState(self) } Some(TunnelCommand::Dns(servers, complete_tx)) => { - let consequence = match shared_values.set_dns_servers(servers) { + let consequence = if shared_values.set_dns_servers(servers) { #[cfg(target_os = "android")] - Ok(true) => self.disconnect(shared_values, AfterDisconnect::Reconnect(0)), - Ok(_) => SameState(self), - Err(cause) => self.disconnect(shared_values, AfterDisconnect::Block(cause)), + { + if let Err(_err) = shared_values.restart_tunnel(false) { + self.disconnect( + shared_values, + AfterDisconnect::Block(ErrorStateCause::StartTunnelError), + ) + } else { + self.disconnect(shared_values, AfterDisconnect::Reconnect(0)) + } + } + #[cfg(not(target_os = "android"))] + SameState(self) + } else { + SameState(self) }; + let _ = complete_tx.send(()); consequence } @@ -477,18 +497,21 @@ impl ConnectingState { } #[cfg(target_os = "android")] Some(TunnelCommand::SetExcludedApps(result_tx, paths)) => { - match shared_values.exclude_paths(paths) { - Ok(_changed) => { - let _ = result_tx.send(Ok(())); - SameState(self) - } - Err(error) => { - let _ = result_tx.send(Err(error)); + if shared_values.set_excluded_paths(paths) { + if let Err(err) = shared_values.restart_tunnel(false) { + let _ = + result_tx.send(Err(crate::split_tunnel::Error::SetExcludedApps(err))); self.disconnect( shared_values, AfterDisconnect::Block(ErrorStateCause::SplitTunnelError), ) + } else { + let _ = result_tx.send(Ok(())); + self.disconnect(shared_values, AfterDisconnect::Reconnect(0)) } + } else { + let _ = result_tx.send(Ok(())); + SameState(self) } } #[cfg(target_os = "macos")] diff --git a/talpid-core/src/tunnel_state_machine/disconnected_state.rs b/talpid-core/src/tunnel_state_machine/disconnected_state.rs index e570d3af6441..4ee19a5e16aa 100644 --- a/talpid-core/src/tunnel_state_machine/disconnected_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnected_state.rs @@ -138,13 +138,7 @@ impl TunnelState for DisconnectedState { match runtime.block_on(commands.next()) { Some(TunnelCommand::AllowLan(allow_lan, complete_tx)) => { - if shared_values.allow_lan != allow_lan { - // The only platform that can fail is Android, but Android doesn't support the - // "block when disconnected" option, so the following call never fails. - shared_values - .set_allow_lan(allow_lan) - .expect("Failed to set allow LAN parameter"); - + if shared_values.set_allow_lan(allow_lan) { Self::set_firewall_policy(shared_values, false); } let _ = complete_tx.send(()); @@ -160,9 +154,7 @@ impl TunnelState for DisconnectedState { } Some(TunnelCommand::Dns(servers, complete_tx)) => { // Same situation as allow LAN above. - shared_values - .set_dns_servers(servers) - .expect("Failed to reconnect after changing custom DNS servers"); + shared_values.set_dns_servers(servers); let _ = complete_tx.send(()); SameState(self) } @@ -218,7 +210,8 @@ impl TunnelState for DisconnectedState { } #[cfg(target_os = "android")] Some(TunnelCommand::SetExcludedApps(result_tx, paths)) => { - let _ = result_tx.send(shared_values.exclude_paths(paths).map(|_| ())); + shared_values.set_excluded_paths(paths); + let _ = result_tx.send(Ok(())); SameState(self) } #[cfg(target_os = "macos")] diff --git a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs index 099208201e99..ddcb3cebd25f 100644 --- a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs @@ -83,7 +83,8 @@ impl DisconnectingState { } #[cfg(target_os = "android")] Some(TunnelCommand::SetExcludedApps(result_tx, paths)) => { - let _ = result_tx.send(shared_values.exclude_paths(paths).map(|_| ())); + shared_values.set_excluded_paths(paths); + let _ = result_tx.send(Ok(())); AfterDisconnect::Nothing } #[cfg(target_os = "macos")] @@ -139,7 +140,8 @@ impl DisconnectingState { } #[cfg(target_os = "android")] Some(TunnelCommand::SetExcludedApps(result_tx, paths)) => { - let _ = result_tx.send(shared_values.exclude_paths(paths).map(|_| ())); + shared_values.set_excluded_paths(paths); + let _ = result_tx.send(Ok(())); AfterDisconnect::Block(reason) } #[cfg(target_os = "macos")] @@ -196,7 +198,8 @@ impl DisconnectingState { } #[cfg(target_os = "android")] Some(TunnelCommand::SetExcludedApps(result_tx, paths)) => { - let _ = result_tx.send(shared_values.exclude_paths(paths).map(|_| ())); + shared_values.set_excluded_paths(paths); + let _ = result_tx.send(Ok(())); AfterDisconnect::Reconnect(retry_attempt) } #[cfg(target_os = "macos")] diff --git a/talpid-core/src/tunnel_state_machine/error_state.rs b/talpid-core/src/tunnel_state_machine/error_state.rs index b4f642d8e2c4..99f8dc17c403 100644 --- a/talpid-core/src/tunnel_state_machine/error_state.rs +++ b/talpid-core/src/tunnel_state_machine/error_state.rs @@ -51,11 +51,12 @@ impl ErrorState { let block_failure = Self::set_firewall_policy(shared_values).err(); #[cfg(target_os = "android")] - let block_failure = if !Self::create_blocking_tun(shared_values) { + let block_failure = if shared_values.restart_tunnel(true).is_err() { Some(FirewallPolicyError::Generic) } else { None }; + ( Box::new(ErrorState { block_reason: block_reason.clone(), @@ -98,28 +99,6 @@ impl ErrorState { }) } - /// Returns true if a new tunnel device was successfully created. - #[cfg(target_os = "android")] - fn create_blocking_tun(shared_values: &mut SharedTunnelStateValues) -> bool { - match shared_values - .tun_provider - .lock() - .unwrap() - .create_blocking_tun() - { - Ok(()) => true, - Err(error) => { - log::error!( - "{}", - error.display_chain_with_msg( - "Failed to open tunnel adapter to drop packets for blocked state" - ) - ); - false - } - } - } - fn reset_dns(shared_values: &mut SharedTunnelStateValues) { if let Err(error) = shared_values.dns_monitor.reset() { log::error!("{}", error.display_chain_with_msg("Unable to reset DNS")); @@ -139,13 +118,25 @@ impl TunnelState for ErrorState { match runtime.block_on(commands.next()) { Some(TunnelCommand::AllowLan(allow_lan, complete_tx)) => { - let consequence = - if let Err(error_state_cause) = shared_values.set_allow_lan(allow_lan) { - NewState(Self::enter(shared_values, error_state_cause)) + let consequence = if shared_values.set_allow_lan(allow_lan) { + #[cfg(target_os = "android")] + if let Err(_err) = shared_values.restart_tunnel(true) { + NewState(Self::enter( + shared_values, + ErrorStateCause::StartTunnelError, + )) } else { + SameState(self) + } + #[cfg(not(target_os = "android"))] + { let _ = Self::set_firewall_policy(shared_values); SameState(self) - }; + } + } else { + SameState(self) + }; + let _ = complete_tx.send(()); consequence } @@ -155,7 +146,7 @@ impl TunnelState for ErrorState { let _ = Self::set_firewall_policy(shared_values); #[cfg(target_os = "android")] - if !Self::create_blocking_tun(shared_values) { + if let Err(_err) = shared_values.restart_tunnel(true) { let _ = tx.send(()); return NewState(Self::enter( shared_values, @@ -167,12 +158,21 @@ impl TunnelState for ErrorState { SameState(self) } Some(TunnelCommand::Dns(servers, complete_tx)) => { - let consequence = - if let Err(error_state_cause) = shared_values.set_dns_servers(servers) { - NewState(Self::enter(shared_values, error_state_cause)) - } else { + let consequence = if shared_values.set_dns_servers(servers) { + #[cfg(target_os = "android")] + { + // DNS is blocked in the error state, so only update tun config + shared_values.prepare_tun_config(true); + SameState(self) + } + #[cfg(not(target_os = "android"))] + { + let _ = Self::set_firewall_policy(shared_values); SameState(self) - }; + } + } else { + SameState(self) + }; let _ = complete_tx.send(()); consequence } @@ -213,7 +213,14 @@ impl TunnelState for ErrorState { } #[cfg(target_os = "android")] Some(TunnelCommand::SetExcludedApps(result_tx, paths)) => { - let _ = result_tx.send(shared_values.exclude_paths(paths).map(|_| ())); + if shared_values.set_excluded_paths(paths) { + if let Err(err) = shared_values.restart_tunnel(true) { + let _ = + result_tx.send(Err(crate::split_tunnel::Error::SetExcludedApps(err))); + } + } else { + let _ = result_tx.send(Ok(())); + } SameState(self) } #[cfg(windows)] diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs index f1f0340cd9e1..361280e3ae83 100644 --- a/talpid-core/src/tunnel_state_machine/mod.rs +++ b/talpid-core/src/tunnel_state_machine/mod.rs @@ -136,18 +136,7 @@ pub async fn spawn( let tun_provider = TunProvider::new( #[cfg(target_os = "android")] android_context.clone(), - #[cfg(target_os = "android")] - initial_settings.allow_lan, - #[cfg(target_os = "android")] - initial_settings.dns_servers.clone(), - #[cfg(target_os = "android")] - crate::firewall::ALLOWED_LAN_NETS - .iter() - .chain(crate::firewall::ALLOWED_LAN_MULTICAST_NETS.iter()) - .cloned() - .collect(), - #[cfg(target_os = "android")] - initial_settings.exclude_paths.clone(), + talpid_tunnel::tun_provider::blocking_config(), ); let (shutdown_tx, shutdown_rx) = oneshot::channel(); @@ -375,6 +364,8 @@ impl TunnelStateMachine { let mut shared_values = SharedTunnelStateValues { #[cfg(any(target_os = "windows", target_os = "macos"))] split_tunnel, + #[cfg(target_os = "android")] + excluded_packages: args.settings.exclude_paths, runtime, firewall, dns_monitor, @@ -463,6 +454,8 @@ struct SharedTunnelStateValues { split_tunnel: split_tunnel::SplitTunnel, #[cfg(target_os = "macos")] split_tunnel: split_tunnel::Handle, + #[cfg(target_os = "android")] + excluded_packages: Vec, runtime: tokio::runtime::Handle, firewall: Firewall, dns_monitor: DnsMonitor, @@ -554,56 +547,21 @@ impl SharedTunnelStateValues { .map_err(|error| ErrorStateCause::from(&error)) } - pub fn set_allow_lan(&mut self, allow_lan: bool) -> Result<(), ErrorStateCause> { + pub fn set_allow_lan(&mut self, allow_lan: bool) -> bool { if self.allow_lan != allow_lan { self.allow_lan = allow_lan; - - #[cfg(target_os = "android")] - { - if let Err(error) = self.tun_provider.lock().unwrap().set_allow_lan(allow_lan) { - log::error!( - "{}", - error.display_chain_with_msg(&format!( - "Failed to restart tunnel after {} LAN connections", - if allow_lan { "allowing" } else { "blocking" } - )) - ); - return Err(ErrorStateCause::StartTunnelError); - } - } + true + } else { + false } - - Ok(()) } - pub fn set_dns_servers( - &mut self, - dns_servers: Option>, - ) -> Result { + pub fn set_dns_servers(&mut self, dns_servers: Option>) -> bool { if self.dns_servers != dns_servers { self.dns_servers = dns_servers; - - #[cfg(target_os = "android")] - { - if let Err(error) = self - .tun_provider - .lock() - .unwrap() - .set_dns_servers(self.dns_servers.clone()) - { - log::error!( - "{}", - error.display_chain_with_msg( - "Failed to restart tunnel after changing DNS servers", - ) - ); - return Err(ErrorStateCause::StartTunnelError); - } - } - - Ok(true) + true } else { - Ok(false) + false } } @@ -651,27 +609,47 @@ impl SharedTunnelStateValues { } /// Update the set of excluded paths (split tunnel apps) for the tunnel provider. - /// Returns `Ok(true)` if the tunnel state machine should issue a tunnel reconnect. #[cfg(target_os = "android")] - pub fn exclude_paths(&mut self, apps: Vec) -> Result { - self.tun_provider - .lock() - .unwrap() - .set_exclude_apps(apps) - .map_err(split_tunnel::Error::SetExcludedApps) - .inspect_err(|error| { + pub fn set_excluded_paths(&mut self, apps: Vec) -> bool { + if apps != self.excluded_packages { + self.excluded_packages = apps; + true + } else { + false + } + } + + /// Update the tunnel provider config. This does not actually create any tunnel. + #[cfg(target_os = "android")] + pub fn prepare_tun_config(&self, blocking: bool) { + let mut tun_provider = self.tun_provider.lock().unwrap(); + + let config = tun_provider.config_mut(); + if blocking { + config.dns_servers = Some(vec![]); + } else { + config.dns_servers = self.dns_servers.clone(); + } + config.allow_lan = self.allow_lan; + config.excluded_packages = self.excluded_packages.clone(); + } + + /// Recreate the tunnel device. Note that this causes the current tunnel fd used by + /// the tunnel monitor to become stale, so a reconnect is needed. + #[cfg(target_os = "android")] + pub fn restart_tunnel(&self, blocking: bool) -> Result<(), talpid_tunnel::tun_provider::Error> { + self.prepare_tun_config(blocking); + + match self.tun_provider.lock().unwrap().open_tun() { + Ok(_tun) => Ok(()), + Err(error) => { log::error!( "{}", - error.display_chain_with_msg( - "Failed to restart tunnel after updating excluded apps", - ) + error.display_chain_with_msg("Failed to restart tunnel") ); - })?; - // NOTE: For now, we tell the TSM to always reconnect when this function has been - // successfully called. We still return a boolean value in case we would like to introduce - // some condition in the future, thus forcing the TSM to be ready to handle both cases - // already. - Ok(true) + Err(error) + } + } } } diff --git a/talpid-tunnel/Cargo.toml b/talpid-tunnel/Cargo.toml index 21312912f4eb..d452b2c20fa1 100644 --- a/talpid-tunnel/Cargo.toml +++ b/talpid-tunnel/Cargo.toml @@ -11,6 +11,7 @@ rust-version.workspace = true workspace = true [dependencies] +once_cell = { workspace = true } thiserror = { workspace = true } cfg-if = "1.0" ipnetwork = { workspace = true } diff --git a/talpid-tunnel/src/lib.rs b/talpid-tunnel/src/lib.rs index 8ce3dd2d0d50..53a746d10244 100644 --- a/talpid-tunnel/src/lib.rs +++ b/talpid-tunnel/src/lib.rs @@ -61,6 +61,17 @@ pub struct TunnelMetadata { pub ipv6_gateway: Option, } +impl TunnelMetadata { + /// Return a copy of all gateway addresses + pub fn gateways(&self) -> Vec { + let mut addrs = vec![self.ipv4_gateway.into()]; + if let Some(gateway) = self.ipv6_gateway { + addrs.push(gateway.into()); + } + addrs + } +} + /// Possible events from the VPN tunnel and the child process managing it. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum TunnelEvent { diff --git a/talpid-tunnel/src/tun_provider/android/mod.rs b/talpid-tunnel/src/tun_provider/android/mod.rs index cbea6c273137..21eb1afb1e52 100644 --- a/talpid-tunnel/src/tun_provider/android/mod.rs +++ b/talpid-tunnel/src/tun_provider/android/mod.rs @@ -12,10 +12,11 @@ use jnix::{ FromJava, IntoJava, JnixEnv, }; use std::{ - net::{IpAddr, Ipv4Addr, Ipv6Addr}, + net::IpAddr, os::unix::io::{AsRawFd, RawFd}, sync::Arc, }; +use talpid_types::net::{ALLOWED_LAN_MULTICAST_NETS, ALLOWED_LAN_NETS}; use talpid_types::{android::AndroidContext, ErrorExt}; /// Errors that occur while setting up VpnService tunnel. @@ -54,23 +55,12 @@ pub struct AndroidTunProvider { jvm: Arc, class: GlobalRef, object: GlobalRef, - last_tun_config: TunConfig, - allow_lan: bool, - blocking: bool, - custom_dns_servers: Option>, - allowed_lan_networks: Vec, - excluded_apps: Vec, + config: TunConfig, } impl AndroidTunProvider { /// Create a new AndroidTunProvider interfacing with Android's VpnService. - pub fn new( - context: AndroidContext, - allow_lan: bool, - custom_dns_servers: Option>, - allowed_lan_networks: Vec, - excluded_apps: Vec, - ) -> Self { + pub fn new(context: AndroidContext, config: TunConfig) -> Self { let env = JnixEnv::from( context .jvm @@ -83,56 +73,30 @@ impl AndroidTunProvider { jvm: context.jvm, class: talpid_vpn_service_class, object: context.vpn_service, - last_tun_config: TunConfig::default(), - allow_lan, - blocking: false, - custom_dns_servers, - allowed_lan_networks, - excluded_apps, + config, } } - pub fn set_allow_lan(&mut self, allow_lan: bool) -> Result<(), Error> { - if self.allow_lan != allow_lan { - self.allow_lan = allow_lan; - self.recreate_tun_if_open()?; - } - - Ok(()) + /// Get the current tunnel config. Note that the tunnel must be recreated for any changes to + /// take effect. + pub fn config_mut(&mut self) -> &mut TunConfig { + &mut self.config } - pub fn set_dns_servers(&mut self, servers: Option>) -> Result<(), Error> { - if self.custom_dns_servers != servers { - self.custom_dns_servers = servers; - self.recreate_tun_if_open()?; - } - - Ok(()) + /// Open a tunnel with the current configuration. + pub fn open_tun(&mut self) -> Result { + self.open_tun_inner("openTun") } - /// Update the set of excluded paths (split tunnel apps) for the tunnel provider. - /// This will cause any pre-existing tunnel to be recreated if necessary. See - /// [`AndroidTunProvider::recreate_tun_if_open()`] for details. - pub fn set_exclude_apps(&mut self, excluded_apps: Vec) -> Result<(), Error> { - if self.excluded_apps != excluded_apps { - self.excluded_apps = excluded_apps; - self.recreate_tun_if_open()?; - } - Ok(()) - } - - /// Retrieve a tunnel device with the provided configuration. Custom DNS and LAN routes are - /// appended to the provided config. - pub fn get_tun(&mut self, mut config: TunConfig) -> Result { - self.prepare_tun_config(&mut config, false); - self.get_tun_inner(config) + /// Open a tunnel with the current configuration. + /// Force recreation even if the tunnel config hasn't changed. + pub fn open_tun_forced(&mut self) -> Result { + self.open_tun_inner("openTunForced") } - /// Retrieve a tunnel device with the provided configuration. - fn get_tun_inner(&mut self, config: TunConfig) -> Result { - let tun_fd = self.get_tun_fd(config.clone())?; - - self.last_tun_config = config; + /// Open a tunnel with the current configuration. + fn open_tun_inner(&mut self, get_tun_func_name: &'static str) -> Result { + let tun_fd = self.open_tun_fd(get_tun_func_name)?; let jvm = unsafe { JavaVM::from_raw(self.jvm.get_java_vm_pointer()) } .map_err(Error::CloneJavaVm)?; @@ -145,34 +109,23 @@ impl AndroidTunProvider { }) } - /// Open a tunnel device that routes everything but (potentially) LAN routes via the tunnel - /// device. Excluded apps will also be kept. - /// - /// Will open a new tunnel if there is already an active tunnel. The previous tunnel will be - /// closed. - pub fn create_blocking_tun(&mut self) -> Result<(), Error> { - let mut config = TunConfig::default(); - self.prepare_tun_config(&mut config, true); - let _ = self.get_tun_inner(config)?; - Ok(()) - } + fn open_tun_fd(&self, get_tun_func_name: &'static str) -> Result { + let config = VpnServiceConfig::new(self.config.clone()); + + let env = self.env()?; + let java_config = config.into_java(&env); - /// 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> { let result = self.call_method( - "createTun", - "()V", - JavaType::Primitive(Primitive::Void), - &[], + get_tun_func_name, + "(Lnet/mullvad/talpid/model/TunConfig;)Lnet/mullvad/talpid/model/CreateTunResult;", + JavaType::Object("net/mullvad/talpid/model/CreateTunResult".to_owned()), + &[JValue::Object(java_config.as_obj())], )?; match result { - JValue::Void => Ok(()), + JValue::Object(result) => CreateTunResult::from_java(&env, result).into(), value => Err(Error::InvalidMethodResult( - "createTun", + get_tun_func_name, format!("{:?}", value), )), } @@ -199,103 +152,6 @@ impl AndroidTunProvider { } } - fn get_tun_fd(&mut self, config: TunConfig) -> Result { - let env = self.env()?; - let java_config = config.into_java(&env); - - let result = self.call_method( - "getTun", - "(Lnet/mullvad/talpid/model/TunConfig;)Lnet/mullvad/talpid/model/CreateTunResult;", - JavaType::Object("net/mullvad/talpid/model/CreateTunResult".to_owned()), - &[JValue::Object(java_config.as_obj())], - )?; - - match result { - JValue::Object(result) => CreateTunResult::from_java(&env, result).into(), - value => Err(Error::InvalidMethodResult("getTun", format!("{:?}", value))), - } - } - - fn recreate_tun_if_open(&mut self) -> Result<(), Error> { - let mut actual_config = self.last_tun_config.clone(); - - self.prepare_tun_config(&mut actual_config, self.blocking); - - let env = self.env()?; - let java_config = actual_config.into_java(&env); - - let result = self.call_method( - "recreateTunIfOpen", - "(Lnet/mullvad/talpid/model/TunConfig;)V", - JavaType::Primitive(Primitive::Void), - &[JValue::Object(java_config.as_obj())], - )?; - - match result { - JValue::Void => Ok(()), - value => Err(Error::InvalidMethodResult("getTun", format!("{:?}", value))), - } - } - - fn prepare_tun_config(&mut self, config: &mut TunConfig, blocking: bool) { - self.blocking = blocking; - self.prepare_tun_config_for_allow_lan(config); - self.prepare_tun_config_for_excluded_apps(config); - if !blocking { - self.prepare_tun_config_for_custom_dns(config); - } - } - - fn prepare_tun_config_for_allow_lan(&self, config: &mut TunConfig) { - if self.allow_lan { - let (required_ipv4_routes, required_ipv6_routes) = config - .required_routes - .iter() - .cloned() - .partition::, _>(|route| route.is_ipv4()); - - let (original_lan_ipv4_networks, original_lan_ipv6_networks) = self - .allowed_lan_networks - .iter() - .cloned() - .partition::, _>(|network| network.is_ipv4()); - - let lan_ipv4_networks = original_lan_ipv4_networks - .into_iter() - .flat_map(|network| network.sub_all(required_ipv4_routes.iter().cloned())) - .collect::>(); - - let lan_ipv6_networks = original_lan_ipv6_networks - .into_iter() - .flat_map(|network| network.sub_all(required_ipv6_routes.iter().cloned())) - .collect::>(); - - let routes = config - .routes - .iter() - .flat_map(|&route| { - if route.is_ipv4() { - route.sub_all(lan_ipv4_networks.iter().cloned()) - } else { - route.sub_all(lan_ipv6_networks.iter().cloned()) - } - }) - .collect(); - - config.routes = routes; - } - } - - fn prepare_tun_config_for_custom_dns(&self, config: &mut TunConfig) { - if let Some(custom_dns_servers) = self.custom_dns_servers.clone() { - config.dns_servers = custom_dns_servers; - } - } - - fn prepare_tun_config_for_excluded_apps(&self, config: &mut TunConfig) { - config.excluded_packages.clone_from(&self.excluded_apps); - } - /// Allow a socket to bypass the tunnel. pub fn bypass(&mut self, socket: RawFd) -> Result<(), Error> { let env = JnixEnv::from( @@ -353,6 +209,120 @@ impl AndroidTunProvider { } } +/// Configuration to use for VpnService +#[derive(Clone, Debug, Eq, PartialEq, IntoJava)] +#[jnix(class_name = "net.mullvad.talpid.model.TunConfig")] +struct VpnServiceConfig { + /// IP addresses for the tunnel interface. + pub addresses: Vec, + + /// IP addresses for the DNS servers to use. + pub dns_servers: Vec, + + /// Routes to configure for the tunnel. + pub routes: Vec, + + /// App packages that should be excluded from the tunnel. + pub excluded_packages: Vec, + + /// Maximum Transmission Unit in the tunnel. + #[jnix(map = "|mtu| mtu as i32")] + pub mtu: u16, +} + +impl VpnServiceConfig { + pub fn new(tun_config: TunConfig) -> VpnServiceConfig { + let dns_servers = Self::resolve_dns_servers(&tun_config); + let routes = Self::resolve_routes(&tun_config); + + VpnServiceConfig { + addresses: tun_config.addresses, + dns_servers, + routes, + excluded_packages: tun_config.excluded_packages, + mtu: tun_config.mtu, + } + } + + /// Return a list of custom DNS servers. If not specified, gateway addresses are used for DNS. + /// Note that `Some(vec![])` is different from `None`. `Some(vec![])` disables DNS. + fn resolve_dns_servers(config: &TunConfig) -> Vec { + config + .dns_servers + .clone() + .unwrap_or_else(|| config.gateways()) + } + + /// Potentially subtract LAN nets from the VPN service routes, excepting gateways. + /// This prevents LAN traffic from going in the tunnel. + fn resolve_routes(config: &TunConfig) -> Vec { + if !config.allow_lan { + return config + .routes + .iter() + .cloned() + .map(InetNetwork::from) + .collect(); + } + + let required_ipv4_routes = vec![IpNetwork::from(IpAddr::from(config.ipv4_gateway))]; + let required_ipv6_routes = config + .ipv6_gateway + .map(|addr| IpNetwork::from(IpAddr::from(addr))) + .into_iter() + .collect::>(); + + let (original_lan_ipv4_networks, original_lan_ipv6_networks) = Self::allowed_lan_networks() + .cloned() + .partition::, _>(|network| network.is_ipv4()); + + let lan_ipv4_networks = original_lan_ipv4_networks + .into_iter() + .flat_map(|network| network.sub_all(required_ipv4_routes.clone())) + .collect::>(); + + let lan_ipv6_networks = original_lan_ipv6_networks + .into_iter() + .flat_map(|network| network.sub_all(required_ipv6_routes.clone())) + .collect::>(); + + config + .routes + .iter() + .flat_map(|&route| { + if route.is_ipv4() { + route.sub_all(lan_ipv4_networks.clone()) + } else { + route.sub_all(lan_ipv6_networks.clone()) + } + }) + .map(InetNetwork::from) + .collect() + } + + fn allowed_lan_networks() -> impl Iterator { + ALLOWED_LAN_NETS + .iter() + .chain(ALLOWED_LAN_MULTICAST_NETS.iter()) + } +} + +#[derive(Clone, Debug, Eq, PartialEq, IntoJava)] +#[jnix(package = "net.mullvad.talpid.model")] +struct InetNetwork { + address: IpAddr, + prefix: i16, +} + +impl From for InetNetwork { + fn from(ip_network: IpNetwork) -> Self { + InetNetwork { + address: ip_network.ip(), + prefix: ip_network.prefix() as i16, + } + } +} + /// Handle to a tunnel device on Android. pub struct VpnServiceTun { tunnel: RawFd, @@ -400,27 +370,6 @@ impl AsRawFd for VpnServiceTun { } } -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![], - excluded_packages: vec![], - mtu: 1380, - } - } -} - #[derive(FromJava)] #[jnix(package = "net.mullvad.talpid.model")] enum CreateTunResult { diff --git a/talpid-tunnel/src/tun_provider/mod.rs b/talpid-tunnel/src/tun_provider/mod.rs index 3214dd1fde0c..20497ab1bb41 100644 --- a/talpid-tunnel/src/tun_provider/mod.rs +++ b/talpid-tunnel/src/tun_provider/mod.rs @@ -1,8 +1,7 @@ use cfg_if::cfg_if; use ipnetwork::IpNetwork; -#[cfg(target_os = "android")] -use jnix::IntoJava; -use std::net::IpAddr; +use once_cell::sync::Lazy; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; cfg_if! { if #[cfg(target_os = "android")] { @@ -32,50 +31,67 @@ cfg_if! { /// Configuration for creating a tunnel device. #[derive(Clone, Debug, Eq, PartialEq)] -#[cfg_attr(target_os = "android", derive(IntoJava))] -#[cfg_attr(target_os = "android", jnix(package = "net.mullvad.talpid.model"))] pub struct TunConfig { /// IP addresses for the tunnel interface. pub addresses: Vec, - /// IP addresses for the DNS servers to use. - pub dns_servers: Vec, + /// MTU of the tunnel interface. + pub mtu: u16, + + /// IPv4 address of the VPN server, and the default IPv4 DNS resolver. + pub ipv4_gateway: Ipv4Addr, + + /// IPv6 address of the VPN server, and the default IPv6 DNS resolver. + pub ipv6_gateway: Option, /// Routes to configure for the tunnel. - #[cfg_attr( - target_os = "android", - jnix(map = "|networks| networks.into_iter().map(InetNetwork::from).collect::>()") - )] pub routes: Vec, - /// Routes that are required to be configured for the tunnel. - #[cfg(target_os = "android")] - #[jnix(skip)] - pub required_routes: Vec, + /// Exclude private IPs from the tunnel + pub allow_lan: bool, - /// App packages that should be excluded from the tunnel. - #[cfg(target_os = "android")] - pub excluded_packages: Vec, + /// DNS servers to use for the tunnel config. + /// Unless specified, the gateways will be used for DNS + pub dns_servers: Option>, - /// Maximum Transmission Unit in the tunnel. - #[cfg_attr(target_os = "android", jnix(map = "|mtu| mtu as i32"))] - pub mtu: u16, + /// Applications to exclude from the tunnel. + pub excluded_packages: Vec, } -#[cfg(target_os = "android")] -#[derive(IntoJava)] -#[jnix(package = "net.mullvad.talpid.model")] -struct InetNetwork { - address: IpAddr, - prefix: i16, +impl TunConfig { + /// Return a copy of all gateway addresses + pub fn gateways(&self) -> Vec { + let mut servers = vec![self.ipv4_gateway.into()]; + if let Some(gateway) = self.ipv6_gateway { + servers.push(gateway.into()); + } + servers + } } -#[cfg(target_os = "android")] -impl From for InetNetwork { - fn from(ip_network: IpNetwork) -> Self { - InetNetwork { - address: ip_network.ip(), - prefix: ip_network.prefix() as i16, - } +/// Return a tunnel configuration that routes all traffic inside the tunnel. +/// Most values except the routes are nonsensical. This is mostly used as a reasonable default on +/// Android to route all traffic inside the tunnel. +pub fn blocking_config() -> TunConfig { + TunConfig { + addresses: vec![IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))], + mtu: 1380, + ipv4_gateway: Ipv4Addr::new(10, 64, 0, 1), + ipv6_gateway: None, + routes: DEFAULT_ROUTES.clone(), + allow_lan: false, + dns_servers: None, + excluded_packages: vec![], } } + +static DEFAULT_ROUTES: Lazy> = + Lazy::new(|| vec![*IPV4_DEFAULT_ROUTE, *IPV6_DEFAULT_ROUTE]); +static IPV4_DEFAULT_ROUTE: Lazy = Lazy::new(|| { + IpNetwork::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0) + .expect("Invalid IP network prefix for IPv4 address") +}); +static IPV6_DEFAULT_ROUTE: Lazy = Lazy::new(|| { + IpNetwork::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0) + .expect("Invalid IP network prefix for IPv6 address") +}); diff --git a/talpid-tunnel/src/tun_provider/stub.rs b/talpid-tunnel/src/tun_provider/stub.rs index 4227c2f8fbb6..b5f779036e12 100644 --- a/talpid-tunnel/src/tun_provider/stub.rs +++ b/talpid-tunnel/src/tun_provider/stub.rs @@ -7,17 +7,11 @@ pub enum Error {} pub struct StubTunProvider; impl StubTunProvider { - pub fn new() -> Self { + pub fn new(_: TunConfig) -> Self { StubTunProvider } - pub fn get_tun(&mut self, _: TunConfig) -> Result<(), Error> { + pub fn open_tun(&mut self) -> Result<(), Error> { unimplemented!(); } } - -impl Default for StubTunProvider { - fn default() -> Self { - Self::new() - } -} diff --git a/talpid-tunnel/src/tun_provider/unix.rs b/talpid-tunnel/src/tun_provider/unix.rs index 457f124f64b2..640af527b31c 100644 --- a/talpid-tunnel/src/tun_provider/unix.rs +++ b/talpid-tunnel/src/tun_provider/unix.rs @@ -25,23 +25,26 @@ pub enum Error { } /// Factory of tunnel devices on Unix systems. -pub struct UnixTunProvider; - -impl Default for UnixTunProvider { - fn default() -> Self { - Self::new() - } +pub struct UnixTunProvider { + config: TunConfig, } impl UnixTunProvider { - pub fn new() -> Self { - UnixTunProvider + pub const fn new(config: TunConfig) -> Self { + UnixTunProvider { config } + } + + /// Get the current tunnel config. Note that the tunnel must be recreated for any changes to + /// take effect. + pub fn config_mut(&mut self) -> &mut TunConfig { + &mut self.config } - pub fn get_tun(&mut self, config: TunConfig) -> Result { + /// Open a tunnel using the current tunnel config. + pub fn open_tun(&mut self) -> Result { let mut tunnel_device = TunnelDevice::new().map_err(Error::CreateTunnelDevice)?; - for ip in config.addresses.iter() { + for ip in self.config.addresses.iter() { tunnel_device .set_ip(*ip) .map_err(|cause| Error::SetIpAddr(*ip, cause))?; diff --git a/talpid-types/Cargo.toml b/talpid-types/Cargo.toml index d6e9ddc69f7c..ffca93f1a848 100644 --- a/talpid-types/Cargo.toml +++ b/talpid-types/Cargo.toml @@ -12,6 +12,7 @@ workspace = true [dependencies] serde = { workspace = true, features = ["derive"] } +once_cell = { workspace = true } ipnetwork = { workspace = true } base64 = "0.22.0" x25519-dalek = { version = "2.0.1", features = ["static_secrets", "zeroize", "getrandom"] } diff --git a/talpid-types/src/net/mod.rs b/talpid-types/src/net/mod.rs index d6113ab41f64..485f1d779fbd 100644 --- a/talpid-types/src/net/mod.rs +++ b/talpid-types/src/net/mod.rs @@ -1,10 +1,12 @@ +use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network}; use obfuscation::ObfuscatorConfig; +use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; #[cfg(windows)] use std::path::PathBuf; use std::{ fmt, - net::{IpAddr, SocketAddr}, + net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, str::FromStr, }; @@ -15,6 +17,40 @@ pub mod openvpn; pub mod proxy; pub mod wireguard; +/// When "allow local network" is enabled the app will allow traffic to and from these networks. +pub static ALLOWED_LAN_NETS: Lazy<[IpNetwork; 6]> = Lazy::new(|| { + [ + IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(10, 0, 0, 0), 8).unwrap()), + IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(172, 16, 0, 0), 12).unwrap()), + IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(192, 168, 0, 0), 16).unwrap()), + IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(169, 254, 0, 0), 16).unwrap()), + IpNetwork::V6(Ipv6Network::new(Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0), 10).unwrap()), + IpNetwork::V6(Ipv6Network::new(Ipv6Addr::new(0xfc00, 0, 0, 0, 0, 0, 0, 0), 7).unwrap()), + ] +}); + +/// When "allow local network" is enabled the app will allow traffic to these networks. +pub static ALLOWED_LAN_MULTICAST_NETS: Lazy<[IpNetwork; 8]> = Lazy::new(|| { + [ + // Local network broadcast. Not routable + IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(255, 255, 255, 255), 32).unwrap()), + // Local subnetwork multicast. Not routable + IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(224, 0, 0, 0), 24).unwrap()), + // Admin-local IPv4 multicast. + IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(239, 0, 0, 0), 8).unwrap()), + // Interface-local IPv6 multicast. + IpNetwork::V6(Ipv6Network::new(Ipv6Addr::new(0xff01, 0, 0, 0, 0, 0, 0, 0), 16).unwrap()), + // Link-local IPv6 multicast. IPv6 equivalent of 224.0.0.0/24 + IpNetwork::V6(Ipv6Network::new(Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0, 0), 16).unwrap()), + // Realm-local IPv6 multicast. + IpNetwork::V6(Ipv6Network::new(Ipv6Addr::new(0xff03, 0, 0, 0, 0, 0, 0, 0), 16).unwrap()), + // Admin-local IPv6 multicast. + IpNetwork::V6(Ipv6Network::new(Ipv6Addr::new(0xff04, 0, 0, 0, 0, 0, 0, 0), 16).unwrap()), + // Site-local IPv6 multicast. + IpNetwork::V6(Ipv6Network::new(Ipv6Addr::new(0xff05, 0, 0, 0, 0, 0, 0, 0), 16).unwrap()), + ] +}); + /// TunnelParameters are used to encapsulate all the data needed to start a tunnel. This is enum /// should be generated by implementations of the trait /// `talpid-core::tunnel_state_machine::TunnelParametersGenerator` diff --git a/talpid-wireguard/src/wireguard_go/mod.rs b/talpid-wireguard/src/wireguard_go/mod.rs index 851734bc9d54..8fc6814b1350 100644 --- a/talpid-wireguard/src/wireguard_go/mod.rs +++ b/talpid-wireguard/src/wireguard_go/mod.rs @@ -5,7 +5,6 @@ use once_cell::sync::OnceCell; use std::{ffi::CString, fs, path::PathBuf}; use std::{ future::Future, - net::IpAddr, os::unix::io::{AsRawFd, RawFd}, path::Path, pin::Pin, @@ -13,7 +12,7 @@ use std::{ }; #[cfg(target_os = "android")] use talpid_tunnel::tun_provider::Error as TunProviderError; -use talpid_tunnel::tun_provider::{Tun, TunConfig, TunProvider}; +use talpid_tunnel::tun_provider::{Tun, TunProvider}; use talpid_types::BoxedError; use super::{ @@ -108,35 +107,6 @@ impl WgGoTunnel { }) } - fn create_tunnel_config( - config: &Config, - routes: impl Iterator, - ) -> TunConfig { - let mut dns_servers = vec![IpAddr::V4(config.ipv4_gateway)]; - dns_servers.extend(config.ipv6_gateway.map(IpAddr::V6)); - - TunConfig { - addresses: config.tunnel.addresses.clone(), - dns_servers, - routes: routes.collect(), - #[cfg(target_os = "android")] - required_routes: Self::create_required_routes(config), - mtu: config.mtu, - } - } - - #[cfg(target_os = "android")] - fn create_required_routes(config: &Config) -> Vec { - let mut required_routes = vec![IpNetwork::new(IpAddr::V4(config.ipv4_gateway), 32) - .expect("Invalid IPv4 network prefix")]; - - required_routes.extend(config.ipv6_gateway.map(|address| { - IpNetwork::new(IpAddr::V6(address), 128).expect("Invalid IPv6 network prefix") - })); - - required_routes - } - #[cfg(target_os = "android")] fn bypass_tunnel_sockets( handle: &wireguard_go_rs::Tunnel, @@ -159,14 +129,16 @@ impl WgGoTunnel { let mut last_error = None; let mut tun_provider = tun_provider.lock().unwrap(); - let tunnel_config = Self::create_tunnel_config( - config, - routes, - ); + let tun_config = tun_provider.config_mut(); + tun_config.addresses = config.tunnel.addresses.clone(); + tun_config.ipv4_gateway = config.ipv4_gateway; + tun_config.ipv6_gateway = config.ipv6_gateway; + tun_config.routes = routes.collect(); + tun_config.mtu = config.mtu; for _ in 1..=MAX_PREPARE_TUN_ATTEMPTS { let tunnel_device = tun_provider - .get_tun(tunnel_config.clone()) + .open_tun() .map_err(TunnelError::SetupTunnelDevice)?; match nix::unistd::dup(tunnel_device.as_raw_fd()) { diff --git a/test/Cargo.lock b/test/Cargo.lock index b2c01ff0696b..38d1930a5650 100644 --- a/test/Cargo.lock +++ b/test/Cargo.lock @@ -3254,6 +3254,7 @@ dependencies = [ "ipnetwork", "jnix", "log", + "once_cell", "serde", "thiserror", "x25519-dalek", diff --git a/windows/winfw/src/winfw/winfw.cpp b/windows/winfw/src/winfw/winfw.cpp index 352a91c0d154..3d56eb66205b 100644 --- a/windows/winfw/src/winfw/winfw.cpp +++ b/windows/winfw/src/winfw/winfw.cpp @@ -50,7 +50,7 @@ std::optional MakeOptional(T* object) // // Networks for which DNS requests can be made on all network adapters. // -// This should be synchronized with `ALLOWED_LAN_NETS` in talpid-core, +// This should be synchronized with `ALLOWED_LAN_NETS` in talpid-types, // but it also includes loopback addresses. // wfp::IpNetwork g_privateIpRanges[] = {