summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2024-08-09 15:39:32 +0200
committerDavid Lönnhager <david.l@mullvad.net>2024-08-13 13:39:54 +0200
commitbb9597f7e4d1ac0c690526851bbc3e745cd8e815 (patch)
treeb23c61a4dbabf4967060c45bd1a95ed934fc9161
parent7928cf5c31b93f333c628285b6e15a11ae7bf4e8 (diff)
downloadmullvadvpn-bb9597f7e4d1ac0c690526851bbc3e745cd8e815.tar.xz
mullvadvpn-bb9597f7e4d1ac0c690526851bbc3e745cd8e815.zip
Refactor tunnel provider and TalpidVpnService
This also fixes the issue of the VPN service being restarted unnecessarily
-rw-r--r--Cargo.lock2
-rw-r--r--android/lib/talpid/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt40
-rw-r--r--mullvad-jni/src/talpid_vpn_service.rs23
-rw-r--r--talpid-core/src/firewall/linux.rs11
-rw-r--r--talpid-core/src/firewall/macos.rs8
-rw-r--r--talpid-core/src/firewall/mod.rs41
-rw-r--r--talpid-core/src/tunnel_state_machine/connected_state.rs99
-rw-r--r--talpid-core/src/tunnel_state_machine/connecting_state.rs67
-rw-r--r--talpid-core/src/tunnel_state_machine/disconnected_state.rs15
-rw-r--r--talpid-core/src/tunnel_state_machine/disconnecting_state.rs9
-rw-r--r--talpid-core/src/tunnel_state_machine/error_state.rs75
-rw-r--r--talpid-core/src/tunnel_state_machine/mod.rs120
-rw-r--r--talpid-tunnel/Cargo.toml1
-rw-r--r--talpid-tunnel/src/lib.rs11
-rw-r--r--talpid-tunnel/src/tun_provider/android/mod.rs339
-rw-r--r--talpid-tunnel/src/tun_provider/mod.rs84
-rw-r--r--talpid-tunnel/src/tun_provider/stub.rs10
-rw-r--r--talpid-tunnel/src/tun_provider/unix.rs23
-rw-r--r--talpid-types/Cargo.toml1
-rw-r--r--talpid-types/src/net/mod.rs38
-rw-r--r--talpid-wireguard/src/wireguard_go/mod.rs44
-rw-r--r--test/Cargo.lock1
-rw-r--r--windows/winfw/src/winfw/winfw.cpp2
23 files changed, 506 insertions, 558 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 6b387409b4..d955c2ca58 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 2a854f7e5c..1c4b51a520 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 a192444e7b..ea6928538a 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)]
@@ -35,17 +31,6 @@ enum Error {
#[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(
_: JNIEnv<'_>,
_this: JObject<'_>,
diff --git a/talpid-core/src/firewall/linux.rs b/talpid-core/src/firewall/linux.rs
index 7c3cb26ec7..accac083c3 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 c86a375b8b..51ae7ef708 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<Vec<pfctl::FilterRule>> {
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 1e6f2ae247..1ef8fd7af7 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<Ipv6Network> =
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<Ipv6Network> =
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 8208223224..ff3a4fec0a 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<IpAddr> {
- 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 9e6a21190c..d9a58a0ea7 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<Self>,
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 e570d3af64..4ee19a5e16 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 099208201e..ddcb3cebd2 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 b4f642d8e2..99f8dc17c4 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 f1f0340cd9..361280e3ae 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<String>,
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<Vec<IpAddr>>,
- ) -> Result<bool, ErrorStateCause> {
+ pub fn set_dns_servers(&mut self, dns_servers: Option<Vec<IpAddr>>) -> 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<String>) -> Result<bool, split_tunnel::Error> {
- 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<String>) -> 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 21312912f4..d452b2c20f 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 8ce3dd2d0d..53a746d102 100644
--- a/talpid-tunnel/src/lib.rs
+++ b/talpid-tunnel/src/lib.rs
@@ -61,6 +61,17 @@ pub struct TunnelMetadata {
pub ipv6_gateway: Option<Ipv6Addr>,
}
+impl TunnelMetadata {
+ /// Return a copy of all gateway addresses
+ pub fn gateways(&self) -> Vec<IpAddr> {
+ 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 cbea6c2731..21eb1afb1e 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<JavaVM>,
class: GlobalRef,
object: GlobalRef,
- last_tun_config: TunConfig,
- allow_lan: bool,
- blocking: bool,
- custom_dns_servers: Option<Vec<IpAddr>>,
- allowed_lan_networks: Vec<IpNetwork>,
- excluded_apps: Vec<String>,
+ 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<Vec<IpAddr>>,
- allowed_lan_networks: Vec<IpNetwork>,
- excluded_apps: Vec<String>,
- ) -> 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(())
- }
-
- pub fn set_dns_servers(&mut self, servers: Option<Vec<IpAddr>>) -> Result<(), Error> {
- if self.custom_dns_servers != servers {
- self.custom_dns_servers = servers;
- 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
}
- /// 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<String>) -> Result<(), Error> {
- if self.excluded_apps != excluded_apps {
- self.excluded_apps = excluded_apps;
- self.recreate_tun_if_open()?;
- }
- Ok(())
+ /// Open a tunnel with the current configuration.
+ pub fn open_tun(&mut self) -> Result<VpnServiceTun, Error> {
+ self.open_tun_inner("openTun")
}
- /// 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<VpnServiceTun, Error> {
- 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<VpnServiceTun, Error> {
+ self.open_tun_inner("openTunForced")
}
- /// Retrieve a tunnel device with the provided configuration.
- fn get_tun_inner(&mut self, config: TunConfig) -> Result<VpnServiceTun, Error> {
- 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<VpnServiceTun, Error> {
+ 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<RawFd, Error> {
+ 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<RawFd, Error> {
- 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::<Vec<_>, _>(|route| route.is_ipv4());
-
- let (original_lan_ipv4_networks, original_lan_ipv6_networks) = self
- .allowed_lan_networks
- .iter()
- .cloned()
- .partition::<Vec<_>, _>(|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::<Vec<_>>();
-
- let lan_ipv6_networks = original_lan_ipv6_networks
- .into_iter()
- .flat_map(|network| network.sub_all(required_ipv6_routes.iter().cloned()))
- .collect::<Vec<_>>();
-
- 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<IpAddr>,
+
+ /// IP addresses for the DNS servers to use.
+ pub dns_servers: Vec<IpAddr>,
+
+ /// Routes to configure for the tunnel.
+ pub routes: Vec<InetNetwork>,
+
+ /// App packages that should be excluded from the tunnel.
+ pub excluded_packages: Vec<String>,
+
+ /// 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<IpAddr> {
+ 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<InetNetwork> {
+ 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::<Vec<IpNetwork>>();
+
+ let (original_lan_ipv4_networks, original_lan_ipv6_networks) = Self::allowed_lan_networks()
+ .cloned()
+ .partition::<Vec<_>, _>(|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::<Vec<_>>();
+
+ let lan_ipv6_networks = original_lan_ipv6_networks
+ .into_iter()
+ .flat_map(|network| network.sub_all(required_ipv6_routes.clone()))
+ .collect::<Vec<_>>();
+
+ 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<Item = &'static IpNetwork> {
+ 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<IpNetwork> 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 3214dd1fde..20497ab1bb 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<IpAddr>,
- /// IP addresses for the DNS servers to use.
- pub dns_servers: Vec<IpAddr>,
+ /// 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<Ipv6Addr>,
/// Routes to configure for the tunnel.
- #[cfg_attr(
- target_os = "android",
- jnix(map = "|networks| networks.into_iter().map(InetNetwork::from).collect::<Vec<_>>()")
- )]
pub routes: Vec<IpNetwork>,
- /// Routes that are required to be configured for the tunnel.
- #[cfg(target_os = "android")]
- #[jnix(skip)]
- pub required_routes: Vec<IpNetwork>,
+ /// 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<String>,
+ /// DNS servers to use for the tunnel config.
+ /// Unless specified, the gateways will be used for DNS
+ pub dns_servers: Option<Vec<IpAddr>>,
- /// 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<String>,
}
-#[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<IpAddr> {
+ 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<IpNetwork> 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<Vec<IpNetwork>> =
+ Lazy::new(|| vec![*IPV4_DEFAULT_ROUTE, *IPV6_DEFAULT_ROUTE]);
+static IPV4_DEFAULT_ROUTE: Lazy<IpNetwork> = Lazy::new(|| {
+ IpNetwork::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)
+ .expect("Invalid IP network prefix for IPv4 address")
+});
+static IPV6_DEFAULT_ROUTE: Lazy<IpNetwork> = 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 4227c2f8fb..b5f779036e 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 457f124f64..640af527b3 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<UnixTun, Error> {
+ /// Open a tunnel using the current tunnel config.
+ pub fn open_tun(&mut self) -> Result<UnixTun, Error> {
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 d6e9ddc69f..ffca93f1a8 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 d6113ab41f..485f1d779f 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 851734bc9d..8fc6814b13 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<Item = IpNetwork>,
- ) -> 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<IpNetwork> {
- 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 b2c01ff069..38d1930a56 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 352a91c0d1..3d56eb6620 100644
--- a/windows/winfw/src/winfw/winfw.cpp
+++ b/windows/winfw/src/winfw/winfw.cpp
@@ -50,7 +50,7 @@ std::optional<T> 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[] = {