diff options
21 files changed, 624 insertions, 288 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 8595a44f6f..cee3e30c77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,9 +28,11 @@ Line wrap the file at 100 chars. Th management interface UDS socket. This means that only users in that group can use the CLI and GUI. - Support WireGuard over TCP for custom VPN relays in the CLI. - Make app native on Apple Silicon. +- Add DNS options for ad and tracker blocking to the CLI. ### Changed - Upgrade OpenVPN from 2.5.0 to 2.5.1. +- Replace CLI command `mullvad custom-dns` with the new command `mullvad dns`. #### Linux - Only allow packets with the mark set to `0x6d6f6c65` to communicate with the relay server. diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts index 8eef8848e2..5e28be9380 100644 --- a/gui/src/main/daemon-rpc.ts +++ b/gui/src/main/daemon-rpc.ts @@ -447,8 +447,21 @@ export class DaemonRpc { public async setDnsOptions(dns: IDnsOptions): Promise<void> { const dnsOptions = new grpcTypes.DnsOptions(); - dnsOptions.setCustom(dns.custom); - dnsOptions.setAddressesList(dns.addresses); + + const defaultOptions = new grpcTypes.DefaultDnsOptions(); + defaultOptions.setBlockAds(false); + defaultOptions.setBlockTrackers(false); + dnsOptions.setDefaultOptions(defaultOptions); + + const customOptions = new grpcTypes.CustomDnsOptions(); + customOptions.setAddressesList(dns.addresses); + dnsOptions.setCustomOptions(customOptions); + + if (dns.custom) { + dnsOptions.setState(grpcTypes.DnsOptions.DnsState.CUSTOM); + } else { + dnsOptions.setState(grpcTypes.DnsOptions.DnsState.DEFAULT); + } await this.call<grpcTypes.DnsOptions, Empty>(this.client.setDnsOptions, dnsOptions); } @@ -1029,8 +1042,8 @@ function convertFromTunnelOptions(tunnelOptions: grpcTypes.TunnelOptions.AsObjec enableIpv6: tunnelOptions.generic!.enableIpv6, }, dns: { - custom: tunnelOptions.dnsOptions?.custom ?? false, - addresses: tunnelOptions.dnsOptions?.addressesList ?? [], + custom: tunnelOptions.dnsOptions!.state! === grpcTypes.DnsOptions.DnsState.CUSTOM, + addresses: tunnelOptions.dnsOptions?.customOptions?.addressesList ?? [], }, }; } diff --git a/mullvad-cli/src/cmds/custom_dns.rs b/mullvad-cli/src/cmds/custom_dns.rs deleted file mode 100644 index 6213a8f6ea..0000000000 --- a/mullvad-cli/src/cmds/custom_dns.rs +++ /dev/null @@ -1,156 +0,0 @@ -use crate::{new_rpc_client, Command, Result}; -use clap::value_t_or_exit; -use mullvad_management_interface::types; - -pub struct CustomDns; - -#[mullvad_management_interface::async_trait] -impl Command for CustomDns { - fn name(&self) -> &'static str { - "custom-dns" - } - - fn clap_subcommand(&self) -> clap::App<'static, 'static> { - clap::SubCommand::with_name(self.name()) - .about("Configure custom DNS servers to use when connected") - .setting(clap::AppSettings::SubcommandRequiredElseHelp) - .subcommand( - clap::SubCommand::with_name("servers") - .about("Set custom DNS servers to use") - .setting(clap::AppSettings::SubcommandRequiredElseHelp) - .subcommand( - clap::SubCommand::with_name("set") - .about("Set custom DNS servers to use") - .arg( - clap::Arg::with_name("servers") - .multiple(true) - .help("One or more IP addresses pointing to DNS resolvers.") - .required(true), - ), - ) - .subcommand( - clap::SubCommand::with_name("clear").about("Remove all custom DNS servers"), - ), - ) - .subcommand( - clap::SubCommand::with_name("get").about("Display the current custom DNS settings"), - ) - .subcommand( - clap::SubCommand::with_name("set") - .about("Enable or disable custom DNS") - .arg( - clap::Arg::with_name("policy") - .required(true) - .possible_values(&["on", "off"]), - ), - ) - } - - async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { - match matches.subcommand() { - ("servers", Some(matches)) => match matches.subcommand() { - ("set", Some(matches)) => { - self.set_servers(matches.values_of_lossy("servers")).await - } - ("clear", _) => self.clear_servers().await, - _ => unreachable!("No custom-dns server command given"), - }, - ("set", Some(matches)) => { - let policy = value_t_or_exit!(matches.value_of("policy"), String); - self.set_state(policy == "on").await - } - ("get", _) => self.get().await, - _ => unreachable!("No custom-dns command given"), - } - } -} - -impl CustomDns { - async fn set_state(&self, enabled: bool) -> Result<()> { - let mut rpc = new_rpc_client().await?; - let options = rpc - .get_settings(()) - .await? - .into_inner() - .tunnel_options - .unwrap() - .dns_options - .unwrap(); - rpc.set_dns_options(types::DnsOptions { - custom: enabled, - addresses: options.addresses, - }) - .await?; - println!("Updated custom DNS settings"); - Ok(()) - } - - async fn set_servers(&self, servers: Option<Vec<String>>) -> Result<()> { - let mut rpc = new_rpc_client().await?; - let options = rpc - .get_settings(()) - .await? - .into_inner() - .tunnel_options - .unwrap() - .dns_options - .unwrap(); - rpc.set_dns_options(types::DnsOptions { - custom: options.custom, - addresses: servers.unwrap_or_default(), - }) - .await?; - println!("Updated custom DNS settings"); - Ok(()) - } - - async fn clear_servers(&self) -> Result<()> { - let mut rpc = new_rpc_client().await?; - let options = rpc - .get_settings(()) - .await? - .into_inner() - .tunnel_options - .unwrap() - .dns_options - .unwrap(); - rpc.set_dns_options(types::DnsOptions { - custom: options.custom, - addresses: vec![], - }) - .await?; - println!("Cleared list of custom DNS servers"); - Ok(()) - } - - async fn get(&self) -> Result<()> { - let mut rpc = new_rpc_client().await?; - let options = rpc - .get_settings(()) - .await? - .into_inner() - .tunnel_options - .unwrap() - .dns_options - .unwrap(); - - let state = if options.custom { - "enabled" - } else { - "disabled" - }; - println!("Custom DNS: {}", state); - - match options.addresses.len() { - 0 => println!("No DNS servers are configured"), - _ => { - println!("Servers:"); - for server in &options.addresses { - println!("\t{}", server); - } - } - } - - Ok(()) - } -} diff --git a/mullvad-cli/src/cmds/dns.rs b/mullvad-cli/src/cmds/dns.rs new file mode 100644 index 0000000000..9ffa0ec09c --- /dev/null +++ b/mullvad-cli/src/cmds/dns.rs @@ -0,0 +1,136 @@ +use crate::{new_rpc_client, Command, Result}; +use mullvad_management_interface::types; +use mullvad_types::settings::{DnsOptions, DnsState}; +use std::convert::TryInto; + +pub struct Dns; + +#[mullvad_management_interface::async_trait] +impl Command for Dns { + fn name(&self) -> &'static str { + "dns" + } + + fn clap_subcommand(&self) -> clap::App<'static, 'static> { + clap::SubCommand::with_name(self.name()) + .about("Configure DNS servers to use when connected") + .setting(clap::AppSettings::SubcommandRequiredElseHelp) + .subcommand( + clap::SubCommand::with_name("get").about("Display the current DNS settings"), + ) + .subcommand( + clap::SubCommand::with_name("set") + .about("Set DNS servers to use") + .setting(clap::AppSettings::SubcommandRequiredElseHelp) + .subcommand( + clap::SubCommand::with_name("default") + .about("Use default DNS servers") + .arg( + clap::Arg::with_name("block ads") + .long("block-ads") + .takes_value(false) + .help("Block domain names used for ads"), + ) + .arg( + clap::Arg::with_name("block trackers") + .long("block-trackers") + .takes_value(false) + .help("Block domain names used for tracking"), + ), + ) + .subcommand( + clap::SubCommand::with_name("custom") + .about("Set a list of custom DNS servers") + .arg( + clap::Arg::with_name("servers") + .multiple(true) + .help("One or more IP addresses pointing to DNS resolvers.") + .required(true), + ), + ), + ) + } + + async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + match matches.subcommand() { + ("set", Some(matches)) => match matches.subcommand() { + ("default", Some(matches)) => { + self.set_default( + matches.is_present("block ads"), + matches.is_present("block trackers"), + ) + .await + } + ("custom", Some(matches)) => { + self.set_custom(matches.values_of_lossy("servers")).await + } + _ => unreachable!("No custom-dns server command given"), + }, + ("get", _) => self.get().await, + _ => unreachable!("No custom-dns command given"), + } + } +} + +impl Dns { + async fn set_default(&self, block_ads: bool, block_trackers: bool) -> Result<()> { + let mut rpc = new_rpc_client().await?; + let settings = rpc.get_settings(()).await?.into_inner(); + rpc.set_dns_options(types::DnsOptions { + state: types::dns_options::DnsState::Default as i32, + default_options: Some(types::DefaultDnsOptions { + block_ads, + block_trackers, + }), + ..settings.tunnel_options.unwrap().dns_options.unwrap() + }) + .await?; + println!("Updated DNS settings"); + Ok(()) + } + + async fn set_custom(&self, servers: Option<Vec<String>>) -> Result<()> { + let mut rpc = new_rpc_client().await?; + let settings = rpc.get_settings(()).await?.into_inner(); + rpc.set_dns_options(types::DnsOptions { + state: types::dns_options::DnsState::Custom as i32, + custom_options: Some(types::CustomDnsOptions { + addresses: servers.unwrap_or_default(), + }), + ..settings.tunnel_options.unwrap().dns_options.unwrap() + }) + .await?; + println!("Updated DNS settings"); + Ok(()) + } + + async fn get(&self) -> Result<()> { + let mut rpc = new_rpc_client().await?; + let options: DnsOptions = rpc + .get_settings(()) + .await? + .into_inner() + .tunnel_options + .unwrap() + .dns_options + .unwrap() + .try_into() + .unwrap(); + + match options.state { + DnsState::Default => { + println!("Custom DNS: no"); + println!("Block ads: {}", options.default_options.block_ads); + println!("Block trackers: {}", options.default_options.block_trackers); + } + DnsState::Custom => { + println!("Custom DNS: yes\nServers:"); + for server in &options.custom_options.addresses { + println!("{}", server); + } + } + } + + Ok(()) + } +} diff --git a/mullvad-cli/src/cmds/mod.rs b/mullvad-cli/src/cmds/mod.rs index eb999a2936..bd7ca97372 100644 --- a/mullvad-cli/src/cmds/mod.rs +++ b/mullvad-cli/src/cmds/mod.rs @@ -22,12 +22,12 @@ pub use self::connect::Connect; mod disconnect; pub use self::disconnect::Disconnect; +mod dns; +pub use self::dns::Dns; + mod lan; pub use self::lan::Lan; -mod custom_dns; -pub use self::custom_dns::CustomDns; - mod reconnect; pub use self::reconnect::Reconnect; @@ -61,10 +61,9 @@ pub fn get_commands() -> HashMap<&'static str, Box<dyn Command>> { Box::new(Bridge), Box::new(Connect), Box::new(Disconnect), + Box::new(Dns), Box::new(Reconnect), Box::new(Lan), - #[cfg(not(target_os = "android"))] - Box::new(CustomDns), Box::new(Relay), Box::new(Reset), #[cfg(target_os = "linux")] diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index e5f57373a8..d44ce55153 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -35,7 +35,7 @@ use mullvad_types::{ RelaySettingsUpdate, }, relay_list::{Relay, RelayList}, - settings::{DnsOptions, Settings}, + settings::{DnsOptions, DnsState, Settings}, states::{TargetState, TunnelState}, version::{AppVersion, AppVersionInfo}, wireguard::{KeygenEvent, RotationInterval}, @@ -80,6 +80,12 @@ const FIRST_KEY_PUSH_TIMEOUT: Duration = Duration::from_secs(5); /// Delay between generating a new WireGuard key and reconnecting const WG_RECONNECT_DELAY: Duration = Duration::from_secs(4 * 60); +lazy_static::lazy_static! { + static ref DNS_AD_BLOCKING_SERVERS: [IpAddr; 1] = ["100.64.0.1".parse().unwrap()]; + static ref DNS_TRACKER_BLOCKING_SERVERS: [IpAddr; 1] = ["100.64.0.2".parse().unwrap()]; + static ref DNS_AD_TRACKER_BLOCKING_SERVERS: [IpAddr; 1] = ["100.64.0.3".parse().unwrap()]; +} + pub type ResponseTx<T, E> = oneshot::Sender<Result<T, E>>; #[derive(err_derive::Error, Debug)] @@ -216,7 +222,7 @@ pub enum DaemonCommand { SetBridgeState(ResponseTx<(), settings::Error>, BridgeState), /// Set if IPv6 should be enabled in the tunnel SetEnableIpv6(ResponseTx<(), settings::Error>, bool), - /// Set custom DNS servers to use instead of passing requests to the gateway + /// Set DNS options or servers to use SetDnsOptions(ResponseTx<(), settings::Error>, DnsOptions), /// Set MTU for wireguard tunnels SetWireguardMtu(ResponseTx<(), settings::Error>, Option<u16>), @@ -629,7 +635,7 @@ where let tunnel_command_tx = tunnel_state_machine::spawn( settings.allow_lan, settings.block_when_disconnected, - Self::get_custom_resolvers(&settings.tunnel_options.dns_options), + Self::get_dns_resolvers(&settings.tunnel_options.dns_options), initial_api_endpoint, tunnel_parameters_generator, log_dir, @@ -694,11 +700,28 @@ where Ok(daemon) } - fn get_custom_resolvers(dns_options: &DnsOptions) -> Option<Vec<IpAddr>> { - if dns_options.custom && !dns_options.addresses.is_empty() { - Some(dns_options.addresses.clone()) - } else { - None + fn get_dns_resolvers(options: &DnsOptions) -> Option<Vec<IpAddr>> { + match options.state { + DnsState::Default => { + if options.default_options.block_ads { + if options.default_options.block_trackers { + Some(DNS_AD_TRACKER_BLOCKING_SERVERS.to_vec()) + } else { + Some(DNS_AD_BLOCKING_SERVERS.to_vec()) + } + } else if options.default_options.block_trackers { + Some(DNS_TRACKER_BLOCKING_SERVERS.to_vec()) + } else { + None + } + } + DnsState::Custom => { + if options.custom_options.addresses.is_empty() { + None + } else { + Some(options.custom_options.addresses.clone()) + } + } } } @@ -1854,10 +1877,9 @@ where Self::oneshot_send(tx, Ok(()), "set_dns_options response"); if settings_changed { let settings = self.settings.to_settings(); - let resolvers = - Self::get_custom_resolvers(&settings.tunnel_options.dns_options); + let resolvers = Self::get_dns_resolvers(&settings.tunnel_options.dns_options); self.event_listener.notify_settings(settings); - self.send_tunnel_command(TunnelCommand::CustomDns(resolvers)); + self.send_tunnel_command(TunnelCommand::Dns(resolvers)); } } Err(e) => { diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index a9b07a1214..2e1d8cbc82 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -361,31 +361,13 @@ impl ManagementService for ManagementServiceImpl { #[cfg(not(target_os = "android"))] async fn set_dns_options(&self, request: Request<types::DnsOptions>) -> ServiceResult<()> { - let options = request.into_inner(); - log::debug!( - "set_dns_options({}, {:?})", - options.custom, - options.addresses - ); - - let mut servers_ip = vec![]; - for server in options.addresses.into_iter() { - if let Ok(addr) = server.parse() { - servers_ip.push(addr); - } else { - let err_msg = format!("failed to parse IP address: {}", server); - return Err(Status::invalid_argument(err_msg)); - } - } + let options = DnsOptions::try_from(request.into_inner()).map_err(|error| match error { + types::FromProtobufTypeError::InvalidArgument(error) => Status::invalid_argument(error), + })?; + log::debug!("set_dns_options({:?})", options); let (tx, rx) = oneshot::channel(); - self.send_command_to_daemon(DaemonCommand::SetDnsOptions( - tx, - DnsOptions { - custom: options.custom, - addresses: servers_ip, - }, - ))?; + self.send_command_to_daemon(DaemonCommand::SetDnsOptions(tx, options))?; self.wait_for_result(rx) .await? .map(Response::new) diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index ba36deeea4..41ba8c96a9 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -376,9 +376,23 @@ message TunnelOptions { DnsOptions dns_options = 4; } +message DefaultDnsOptions { + bool block_ads = 1; + bool block_trackers = 2; +} + +message CustomDnsOptions { + repeated string addresses = 1; +} + message DnsOptions { - bool custom = 1; - repeated string addresses = 2; + enum DnsState { + DEFAULT = 0; + CUSTOM = 1; + } + DnsState state = 1; + DefaultDnsOptions default_options = 2; + CustomDnsOptions custom_options = 3; } message PublicKey { diff --git a/mullvad-management-interface/src/types.rs b/mullvad-management-interface/src/types.rs index 753149f35e..aff3a30789 100644 --- a/mullvad-management-interface/src/types.rs +++ b/mullvad-management-interface/src/types.rs @@ -472,6 +472,29 @@ impl From<mullvad_types::relay_constraints::RelaySettings> for RelaySettings { } } +impl From<&mullvad_types::settings::DnsOptions> for DnsOptions { + fn from(options: &mullvad_types::settings::DnsOptions) -> Self { + DnsOptions { + state: match options.state { + mullvad_types::settings::DnsState::Default => dns_options::DnsState::Default as i32, + mullvad_types::settings::DnsState::Custom => dns_options::DnsState::Custom as i32, + }, + default_options: Some(DefaultDnsOptions { + block_ads: options.default_options.block_ads, + block_trackers: options.default_options.block_trackers, + }), + custom_options: Some(CustomDnsOptions { + addresses: options + .custom_options + .addresses + .iter() + .map(|addr| addr.to_string()) + .collect(), + }), + } + } +} + impl From<&mullvad_types::settings::TunnelOptions> for TunnelOptions { fn from(options: &mullvad_types::settings::TunnelOptions) -> Self { Self { @@ -489,15 +512,7 @@ impl From<&mullvad_types::settings::TunnelOptions> for TunnelOptions { enable_ipv6: options.generic.enable_ipv6, }), #[cfg(not(target_os = "android"))] - dns_options: Some(DnsOptions { - custom: options.dns_options.custom, - addresses: options - .dns_options - .addresses - .iter() - .map(|addr| addr.to_string()) - .collect(), - }), + dns_options: Some(DnsOptions::from(&options.dns_options)), #[cfg(target_os = "android")] dns_options: None, } @@ -606,6 +621,7 @@ impl From<TransportProtocol> for talpid_types::net::TransportProtocol { } } +#[derive(Debug)] pub enum FromProtobufTypeError { InvalidArgument(&'static str), } @@ -1008,6 +1024,60 @@ impl TryFrom<BridgeState> for mullvad_types::relay_constraints::BridgeState { } } +impl TryFrom<DnsOptions> for mullvad_types::settings::DnsOptions { + type Error = FromProtobufTypeError; + + fn try_from(options: DnsOptions) -> Result<Self, Self::Error> { + use mullvad_types::settings::{ + CustomDnsOptions as MullvadCustomDnsOptions, + DefaultDnsOptions as MullvadDefaultDnsOptions, DnsOptions as MullvadDnsOptions, + DnsState as MullvadDnsState, + }; + + let state = match dns_options::DnsState::from_i32(options.state) { + Some(dns_options::DnsState::Default) => MullvadDnsState::Default, + Some(dns_options::DnsState::Custom) => MullvadDnsState::Custom, + None => { + return Err(FromProtobufTypeError::InvalidArgument( + "invalid DNS options state", + )) + } + }; + + let default_options = + options + .default_options + .ok_or(FromProtobufTypeError::InvalidArgument( + "missing default DNS options", + ))?; + let custom_options = + options + .custom_options + .ok_or(FromProtobufTypeError::InvalidArgument( + "missing default DNS options", + ))?; + + Ok(MullvadDnsOptions { + state, + default_options: MullvadDefaultDnsOptions { + block_ads: default_options.block_ads, + block_trackers: default_options.block_trackers, + }, + custom_options: MullvadCustomDnsOptions { + addresses: custom_options + .addresses + .into_iter() + .map(|addr| { + addr.parse().map_err(|_| { + FromProtobufTypeError::InvalidArgument("invalid IP address") + }) + }) + .collect::<Result<Vec<_>, _>>()?, + }, + }) + } +} + fn convert_providers_constraint( providers: &Constraint<mullvad_types::relay_constraints::Providers>, ) -> Vec<String> { diff --git a/mullvad-types/src/settings/migrations/mod.rs b/mullvad-types/src/settings/migrations/mod.rs index 0d40e1e564..433b7ba5bc 100644 --- a/mullvad-types/src/settings/migrations/mod.rs +++ b/mullvad-types/src/settings/migrations/mod.rs @@ -2,6 +2,7 @@ use super::{Error, Result}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; mod v1; mod v2; +mod v3; #[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] @@ -9,6 +10,7 @@ mod v2; pub enum SettingsVersion { V2 = 2, V3 = 3, + V4 = 4, } pub const CURRENT_SETTINGS_VERSION: SettingsVersion = SettingsVersion::V3; @@ -21,6 +23,7 @@ impl<'de> Deserialize<'de> for SettingsVersion { match <u32>::deserialize(deserializer)? { v if v == SettingsVersion::V2 as u32 => Ok(SettingsVersion::V2), v if v == SettingsVersion::V3 as u32 => Ok(SettingsVersion::V3), + v if v == SettingsVersion::V4 as u32 => Ok(SettingsVersion::V4), v => Err(serde::de::Error::custom(format!( "{} is not a valid SettingsVersion", v @@ -52,8 +55,11 @@ pub fn try_migrate_settings(mut settings_file: &[u8]) -> Result<crate::settings: return Err(Error::NoMatchingVersion); } - let migrations: Vec<Box<dyn SettingsMigration>> = - vec![Box::new(v1::Migration), Box::new(v2::Migration)]; + let migrations: Vec<Box<dyn SettingsMigration>> = vec![ + Box::new(v1::Migration), + Box::new(v2::Migration), + Box::new(v3::Migration), + ]; for migration in &migrations { if !migration.version_matches(&mut settings) { @@ -79,7 +85,7 @@ mod test { #[test] #[should_panic] fn test_deserialization_failure_version_too_big() { - let _version: SettingsVersion = serde_json::from_str("4").expect("Version too big"); + let _version: SettingsVersion = serde_json::from_str("100").expect("Version too big"); } #[test] diff --git a/mullvad-types/src/settings/migrations/v1.rs b/mullvad-types/src/settings/migrations/v1.rs index 15ce57f0f5..870b9b9d09 100644 --- a/mullvad-types/src/settings/migrations/v1.rs +++ b/mullvad-types/src/settings/migrations/v1.rs @@ -128,7 +128,7 @@ mod test { "enable_ipv6": false } }, - "settings_version": 3 + "settings_version": 4 } "#; diff --git a/mullvad-types/src/settings/migrations/v2.rs b/mullvad-types/src/settings/migrations/v2.rs index 393da1827c..6e74f60ccb 100644 --- a/mullvad-types/src/settings/migrations/v2.rs +++ b/mullvad-types/src/settings/migrations/v2.rs @@ -160,7 +160,7 @@ mod test { "enable_ipv6": false } }, - "settings_version": 3 + "settings_version": 4 } "#; diff --git a/mullvad-types/src/settings/migrations/v3.rs b/mullvad-types/src/settings/migrations/v3.rs new file mode 100644 index 0000000000..fc56745f06 --- /dev/null +++ b/mullvad-types/src/settings/migrations/v3.rs @@ -0,0 +1,189 @@ +use super::{Error, Result, SettingsVersion}; +use crate::settings::{CustomDnsOptions, DefaultDnsOptions, DnsOptions, DnsState}; + + +pub(super) struct Migration; + +impl super::SettingsMigration for Migration { + fn version_matches(&self, settings: &mut serde_json::Value) -> bool { + settings + .get("settings_version") + .map(|version| version == SettingsVersion::V3 as u64) + .unwrap_or(false) + } + + fn migrate(&self, settings: &mut serde_json::Value) -> Result<()> { + log::info!("Migrating settings format to V4"); + + let dns_options = || -> Option<&serde_json::Value> { + settings.get("tunnel_options")?.get("dns_options") + }(); + + if let Some(options) = dns_options { + let new_state = if options + .get("custom") + .map(|custom| custom.as_bool().unwrap_or(false)) + .unwrap_or(false) + { + DnsState::Custom + } else { + DnsState::Default + }; + let addresses = if let Some(addrs) = options.get("addresses") { + serde_json::from_value(addrs.clone()).map_err(Error::ParseError)? + } else { + vec![] + }; + + settings["tunnel_options"]["dns_options"] = serde_json::json!(DnsOptions { + state: new_state, + default_options: DefaultDnsOptions::default(), + custom_options: CustomDnsOptions { addresses }, + }); + } + + settings["settings_version"] = serde_json::json!(SettingsVersion::V4); + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::super::try_migrate_settings; + use serde_json; + + pub const V3_SETTINGS: &str = r#" +{ + "account_token": "1234", + "relay_settings": { + "normal": { + "location": { + "only": { + "country": "se" + } + }, + "tunnel_protocol": "any", + "wireguard_constraints": { + "port": "any" + }, + "openvpn_constraints": { + "port": { + "only": 53 + }, + "protocol": { + "only": "udp" + } + } + } + }, + "bridge_settings": { + "normal": { + "location": "any" + } + }, + "bridge_state": "auto", + "allow_lan": true, + "block_when_disconnected": false, + "auto_connect": false, + "tunnel_options": { + "openvpn": { + "mssfix": null + }, + "wireguard": { + "mtu": null, + "rotation_interval": { + "secs": 86400, + "nanos": 0 + } + }, + "generic": { + "enable_ipv6": false + }, + "dns_options": { + "custom": false, + "addresses": [ + "1.1.1.1", + "1.2.3.4" + ] + } + }, + "settings_version": 3 +} +"#; + + pub const NEW_SETTINGS: &str = r#" +{ + "account_token": "1234", + "relay_settings": { + "normal": { + "location": { + "only": { + "country": "se" + } + }, + "tunnel_protocol": "any", + "wireguard_constraints": { + "port": "any" + }, + "openvpn_constraints": { + "port": { + "only": 53 + }, + "protocol": { + "only": "udp" + } + } + } + }, + "bridge_settings": { + "normal": { + "location": "any" + } + }, + "bridge_state": "auto", + "allow_lan": true, + "block_when_disconnected": false, + "auto_connect": false, + "tunnel_options": { + "openvpn": { + "mssfix": null + }, + "wireguard": { + "mtu": null, + "rotation_interval": { + "secs": 86400, + "nanos": 0 + } + }, + "generic": { + "enable_ipv6": false + }, + "dns_options": { + "state": "default", + "default_options": { + "block_ads": false, + "block_trackers": false + }, + "custom_options": { + "addresses": [ + "1.1.1.1", + "1.2.3.4" + ] + } + } + }, + "settings_version": 4 +} +"#; + + + #[test] + fn test_v3_migration() { + let migrated_settings = + try_migrate_settings(V3_SETTINGS.as_bytes()).expect("Migration failed"); + let new_settings = serde_json::from_str(NEW_SETTINGS).unwrap(); + + assert_eq!(&migrated_settings, &new_settings); + } +} diff --git a/mullvad-types/src/settings/mod.rs b/mullvad-types/src/settings/mod.rs index 1cd900edfc..0430f530ea 100644 --- a/mullvad-types/src/settings/mod.rs +++ b/mullvad-types/src/settings/mod.rs @@ -6,7 +6,7 @@ use crate::{ wireguard, }; #[cfg(target_os = "android")] -use jnix::{FromJava, IntoJava}; +use jnix::{jni::objects::JObject, FromJava, IntoJava, JnixEnv}; use log::{debug, info}; use serde::{Deserialize, Serialize}; use serde_json; @@ -174,19 +174,87 @@ pub struct TunnelOptions { /// Contains generic tunnel options that may apply to more than a single tunnel type. #[cfg_attr(target_os = "android", jnix(skip))] pub generic: GenericTunnelOptions, - /// Custom DNS options. + /// DNS options. pub dns_options: DnsOptions, } -/// Custom DNS config +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[serde(rename_all = "snake_case")] +pub enum DnsState { + Default, + Custom, +} + +impl Default for DnsState { + fn default() -> Self { + Self::Default + } +} + +/// DNS config #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] -#[serde(default)] -#[cfg_attr(target_os = "android", derive(FromJava, IntoJava))] +#[cfg_attr(target_os = "android", derive(IntoJava))] #[cfg_attr(target_os = "android", jnix(package = "net.mullvad.mullvadvpn.model"))] pub struct DnsOptions { - /// Whether to use the addresses in `custom_dns`. + #[cfg_attr(target_os = "android", jnix(map = "|state| state == DnsState::Custom"))] + pub state: DnsState, + #[cfg_attr(target_os = "android", jnix(skip))] + pub default_options: DefaultDnsOptions, + #[cfg_attr(target_os = "android", jnix(map = "|opts| opts.addresses"))] + pub custom_options: CustomDnsOptions, +} + +#[cfg(target_os = "android")] +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[cfg_attr(target_os = "android", derive(FromJava))] +#[cfg_attr( + target_os = "android", + jnix(class_name = "net.mullvad.mullvadvpn.model.DnsOptions") +)] +pub struct AndroidDnsOptions { pub custom: bool, - /// Custom DNS servers to use. + pub addresses: Vec<IpAddr>, +} + +#[cfg(target_os = "android")] +impl From<AndroidDnsOptions> for DnsOptions { + fn from(options: AndroidDnsOptions) -> Self { + Self { + state: if options.custom { + DnsState::Custom + } else { + DnsState::Default + }, + default_options: DefaultDnsOptions::default(), + custom_options: CustomDnsOptions { + addresses: options.addresses, + }, + } + } +} + +#[cfg(target_os = "android")] +impl<'env, 'sub_env> FromJava<'env, JObject<'sub_env>> for DnsOptions +where + 'env: 'sub_env, +{ + const JNI_SIGNATURE: &'static str = "Lnet/mullvad/mullvadvpn/model/DnsOptions"; + + fn from_java(env: &JnixEnv<'env>, object: JObject<'sub_env>) -> Self { + AndroidDnsOptions::from_java(env, object).into() + } +} + +/// Default DNS config +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +pub struct DefaultDnsOptions { + pub block_ads: bool, + pub block_trackers: bool, +} + +/// Custom DNS config +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +pub struct CustomDnsOptions { pub addresses: Vec<IpAddr>, } diff --git a/talpid-core/src/tunnel/tun_provider/android/mod.rs b/talpid-core/src/tunnel/tun_provider/android/mod.rs index 575f05f4c2..3febda73d4 100644 --- a/talpid-core/src/tunnel/tun_provider/android/mod.rs +++ b/talpid-core/src/tunnel/tun_provider/android/mod.rs @@ -110,7 +110,7 @@ impl AndroidTunProvider { self.allowed_endpoint = endpoint; } - pub fn set_custom_dns_servers(&mut self, servers: Option<Vec<IpAddr>>) -> Result<(), Error> { + 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()?; diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs index dd2102127e..ea8ce8e992 100644 --- a/talpid-core/src/tunnel_state_machine/connected_state.rs +++ b/talpid-core/src/tunnel_state_machine/connected_state.rs @@ -80,7 +80,7 @@ impl ConnectedState { #[allow(unused_variables)] fn get_dns_servers(&self, shared_values: &SharedTunnelStateValues) -> Vec<IpAddr> { #[cfg(not(target_os = "android"))] - if let Some(ref servers) = shared_values.custom_dns { + if let Some(ref servers) = shared_values.dns_servers { servers.clone() } else { let mut dns_ips = vec![]; @@ -197,41 +197,34 @@ impl ConnectedState { } SameState(self.into()) } - Some(TunnelCommand::CustomDns(servers)) => { - match shared_values.set_custom_dns(servers) { - Ok(true) => { - if let Err(error) = self.set_firewall_policy(shared_values) { - return self.disconnect( - shared_values, - AfterDisconnect::Block(ErrorStateCause::SetFirewallPolicyError( - error, - )), - ); - } + Some(TunnelCommand::Dns(servers)) => match shared_values.set_dns_servers(servers) { + Ok(true) => { + if let Err(error) = self.set_firewall_policy(shared_values) { + return self.disconnect( + shared_values, + AfterDisconnect::Block(ErrorStateCause::SetFirewallPolicyError(error)), + ); + } - 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.into()), - Err(error) => { - log::error!( - "{}", - error.display_chain_with_msg("Failed to set DNS") - ); - self.disconnect( - shared_values, - AfterDisconnect::Block(ErrorStateCause::SetDnsError), - ) - } + 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.into()), + Err(error) => { + log::error!("{}", error.display_chain_with_msg("Failed to set DNS")); + self.disconnect( + shared_values, + AfterDisconnect::Block(ErrorStateCause::SetDnsError), + ) } } - Ok(false) => SameState(self.into()), - Err(error_cause) => { - self.disconnect(shared_values, AfterDisconnect::Block(error_cause)) - } } - } + Ok(false) => SameState(self.into()), + Err(error_cause) => { + self.disconnect(shared_values, AfterDisconnect::Block(error_cause)) + } + }, Some(TunnelCommand::BlockWhenDisconnected(block_when_disconnected)) => { shared_values.block_when_disconnected = block_when_disconnected; SameState(self.into()) diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs index 29c3bb0a90..553cf9377d 100644 --- a/talpid-core/src/tunnel_state_machine/connecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs @@ -267,14 +267,12 @@ impl ConnectingState { } SameState(self.into()) } - Some(TunnelCommand::CustomDns(servers)) => { - match shared_values.set_custom_dns(servers) { - #[cfg(target_os = "android")] - Ok(true) => self.disconnect(shared_values, AfterDisconnect::Reconnect(0)), - Ok(_) => SameState(self.into()), - Err(cause) => self.disconnect(shared_values, AfterDisconnect::Block(cause)), - } - } + Some(TunnelCommand::Dns(servers)) => match shared_values.set_dns_servers(servers) { + #[cfg(target_os = "android")] + Ok(true) => self.disconnect(shared_values, AfterDisconnect::Reconnect(0)), + Ok(_) => SameState(self.into()), + Err(cause) => self.disconnect(shared_values, AfterDisconnect::Block(cause)), + }, Some(TunnelCommand::BlockWhenDisconnected(block_when_disconnected)) => { shared_values.block_when_disconnected = block_when_disconnected; SameState(self.into()) diff --git a/talpid-core/src/tunnel_state_machine/disconnected_state.rs b/talpid-core/src/tunnel_state_machine/disconnected_state.rs index 7a02a9e550..8d2c9bc0fa 100644 --- a/talpid-core/src/tunnel_state_machine/disconnected_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnected_state.rs @@ -87,10 +87,10 @@ impl TunnelState for DisconnectedState { } SameState(self.into()) } - Some(TunnelCommand::CustomDns(servers)) => { + Some(TunnelCommand::Dns(servers)) => { // Same situation as allow LAN above. shared_values - .set_custom_dns(servers) + .set_dns_servers(servers) .expect("Failed to reconnect after changing custom DNS servers"); SameState(self.into()) diff --git a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs index 7ddbdcf5da..7d308d5971 100644 --- a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs @@ -39,8 +39,8 @@ impl DisconnectingState { } AfterDisconnect::Nothing } - Some(TunnelCommand::CustomDns(servers)) => { - let _ = shared_values.set_custom_dns(servers); + Some(TunnelCommand::Dns(servers)) => { + let _ = shared_values.set_dns_servers(servers); AfterDisconnect::Nothing } Some(TunnelCommand::BlockWhenDisconnected(block_when_disconnected)) => { @@ -72,8 +72,8 @@ impl DisconnectingState { } AfterDisconnect::Block(reason) } - Some(TunnelCommand::CustomDns(servers)) => { - let _ = shared_values.set_custom_dns(servers); + Some(TunnelCommand::Dns(servers)) => { + let _ = shared_values.set_dns_servers(servers); AfterDisconnect::Block(reason) } Some(TunnelCommand::BlockWhenDisconnected(block_when_disconnected)) => { @@ -110,8 +110,8 @@ impl DisconnectingState { } AfterDisconnect::Reconnect(retry_attempt) } - Some(TunnelCommand::CustomDns(servers)) => { - let _ = shared_values.set_custom_dns(servers); + Some(TunnelCommand::Dns(servers)) => { + let _ = shared_values.set_dns_servers(servers); AfterDisconnect::Reconnect(retry_attempt) } Some(TunnelCommand::BlockWhenDisconnected(block_when_disconnected)) => { diff --git a/talpid-core/src/tunnel_state_machine/error_state.rs b/talpid-core/src/tunnel_state_machine/error_state.rs index 6674cca13e..5e647c8201 100644 --- a/talpid-core/src/tunnel_state_machine/error_state.rs +++ b/talpid-core/src/tunnel_state_machine/error_state.rs @@ -123,8 +123,8 @@ impl TunnelState for ErrorState { } SameState(self.into()) } - Some(TunnelCommand::CustomDns(servers)) => { - if let Err(error_state_cause) = shared_values.set_custom_dns(servers) { + Some(TunnelCommand::Dns(servers)) => { + if let Err(error_state_cause) = shared_values.set_dns_servers(servers) { NewState(Self::enter(shared_values, error_state_cause)) } else { SameState(self.into()) diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs index 509a1a0e43..1f26f2fa04 100644 --- a/talpid-core/src/tunnel_state_machine/mod.rs +++ b/talpid-core/src/tunnel_state_machine/mod.rs @@ -76,7 +76,7 @@ pub enum Error { pub async fn spawn( allow_lan: bool, block_when_disconnected: bool, - custom_dns: Option<Vec<IpAddr>>, + dns_servers: Option<Vec<IpAddr>>, allowed_endpoint: Endpoint, tunnel_parameters_generator: impl TunnelParametersGenerator, log_dir: Option<PathBuf>, @@ -106,7 +106,7 @@ pub async fn spawn( #[cfg(target_os = "android")] allowed_endpoint.address.ip(), #[cfg(target_os = "android")] - custom_dns.clone(), + dns_servers.clone(), ); let runtime = tokio::runtime::Handle::current(); @@ -118,7 +118,7 @@ pub async fn spawn( allow_lan, block_when_disconnected, is_offline, - custom_dns, + dns_servers, allowed_endpoint, tunnel_parameters_generator, tun_provider, @@ -161,8 +161,8 @@ pub enum TunnelCommand { /// Endpoint that should never be blocked. /// If an error occurs, the sender is dropped. AllowEndpoint(Endpoint, oneshot::Sender<()>), - /// Set custom DNS servers to use. - CustomDns(Option<Vec<IpAddr>>), + /// Set DNS servers to use. + Dns(Option<Vec<IpAddr>>), /// Enable or disable the block_when_disconnected feature. BlockWhenDisconnected(bool), /// Notify the state machine of the connectivity of the device. @@ -204,7 +204,7 @@ impl TunnelStateMachine { allow_lan: bool, block_when_disconnected: bool, is_offline: bool, - custom_dns: Option<Vec<IpAddr>>, + dns_servers: Option<Vec<IpAddr>>, allowed_endpoint: Endpoint, tunnel_parameters_generator: impl TunnelParametersGenerator, tun_provider: TunProvider, @@ -232,7 +232,7 @@ impl TunnelStateMachine { allow_lan, block_when_disconnected, is_offline, - custom_dns, + dns_servers, allowed_endpoint, tunnel_parameters_generator: Box::new(tunnel_parameters_generator), tun_provider, @@ -304,8 +304,8 @@ struct SharedTunnelStateValues { block_when_disconnected: bool, /// True when the computer is known to be offline. is_offline: bool, - /// Custom DNS servers to use. - custom_dns: Option<Vec<IpAddr>>, + /// DNS servers to use (overriding default). + dns_servers: Option<Vec<IpAddr>>, /// Endpoint that should not be blocked by the firewall. allowed_endpoint: Endpoint, /// The generator of new `TunnelParameter`s @@ -359,20 +359,20 @@ impl SharedTunnelStateValues { } } - pub fn set_custom_dns( + pub fn set_dns_servers( &mut self, - custom_dns: Option<Vec<IpAddr>>, + dns_servers: Option<Vec<IpAddr>>, ) -> Result<bool, ErrorStateCause> { - if self.custom_dns != custom_dns { - self.custom_dns = custom_dns.clone(); + if self.dns_servers != dns_servers { + self.dns_servers = dns_servers.clone(); #[cfg(target_os = "android")] { - if let Err(error) = self.tun_provider.set_custom_dns_servers(custom_dns) { + if let Err(error) = self.tun_provider.set_dns_servers(dns_servers) { log::error!( "{}", error.display_chain_with_msg( - "Failed to restart tunnel after changing custom DNS servers", + "Failed to restart tunnel after changing DNS servers", ) ); return Err(ErrorStateCause::StartTunnelError); |
