diff options
| author | David Lönnhager <david.l@mullvad.net> | 2020-10-22 09:52:19 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2020-10-22 09:52:19 +0200 |
| commit | d88d5f32768fe14c7aa6276e61104e66dd05d1fe (patch) | |
| tree | 8747ab07d076a5927c9144bb8123a63be0e0315b | |
| parent | 82d224e656001e23f674db58b9408dca0ff4ed98 (diff) | |
| parent | eb7a77321c818e64325c6d328cb45d026bf56b21 (diff) | |
| download | mullvadvpn-d88d5f32768fe14c7aa6276e61104e66dd05d1fe.tar.xz mullvadvpn-d88d5f32768fe14c7aa6276e61104e66dd05d1fe.zip | |
Merge branch 'win-custom-dns'
23 files changed, 418 insertions, 33 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 7be5d010ed..a14e60aaba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,9 @@ Line wrap the file at 100 chars. Th - Add `--wait` flag to `connect`, `disconnect` and `reconnect` CLI subcommands to make the CLI wait for the target state to be reached before exiting. +#### Windows +- Add support for custom DNS resolvers (CLI only). + ### Changed - Use the API to fetch API IP addresses instead of DNS. - Remove WireGuard keys during uninstallation after the firewall is unlocked. diff --git a/docs/security.md b/docs/security.md index 40fb188465..20307390ec 100644 --- a/docs/security.md +++ b/docs/security.md @@ -156,8 +156,11 @@ stays active until the user requests a disconnect, quit, server change, change o that affects the tunnel or until the tunnel goes down unexpectedly. In this state, all traffic in both directions over the tunnel interface is allowed. Minus DNS -requests (TCP and UDP destination port 53) not to a gateway IP on the tunnel interface. -Meaning we can *only* request DNS inside the tunnel and *only* from the relay server itself. +requests (TCP and UDP destination port 53) not to a gateway IP on the tunnel interface or +one of the defined custom DNS servers. +We can *only* request DNS inside the tunnel and *only* from the relay server itself, +unless one or more custom DNS servers are provided. If custom servers are specified, DNS requests +can only be made to them. This state allows traffic on all interfaces to and from the IP+port+protocol combination that the tunnel runs over. See the [connecting] state for details on this rule. @@ -237,11 +240,10 @@ Since an invalid or missing DNS response prevents the user from going where they it is important that it works and gives correct replies, from an anti-censorship point of view. Poisoned DNS replies is a very common way of censoring the network in many places. -With the above as background, the app makes sure that every DNS request from the device goes -inside the VPN tunnel and to exactly one place, the VPN relay server the device is currently -connected to. That ensures the request reaches the Mullvad infrastructure and does so safely -(encrypted). From there the Mullvad servers are responsible for delivering a correct and -uncensored reply. +By default, the app makes sure that every DNS request from the device goes inside the VPN tunnel +and only to the VPN relay server that the device is currently connected to. If custom DNS servers +are provided, requests are always made inside the tunnel unless the address belongs to a private +address range (such as 192.168.0.0/16) or a loopback address. The above holds during the [connected] state. In the [disconnected] state the app does nothing with DNS, meaning the default one is used, probably from the ISP. diff --git a/mullvad-cli/src/cmds/custom_dns.rs b/mullvad-cli/src/cmds/custom_dns.rs new file mode 100644 index 0000000000..1ae196f47c --- /dev/null +++ b/mullvad-cli/src/cmds/custom_dns.rs @@ -0,0 +1,74 @@ +use crate::{new_rpc_client, Command, Result}; +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("set") + .about("Change custom DNS setting") + .arg( + clap::Arg::with_name("servers") + .multiple(true) + .required(false), + ), + ) + .subcommand( + clap::SubCommand::with_name("get").about("Display the current custom DNS setting"), + ) + } + + async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + if let Some(set_matches) = matches.subcommand_matches("set") { + self.set(set_matches.values_of_lossy("servers")).await + } else if let Some(_matches) = matches.subcommand_matches("get") { + self.get().await + } else { + unreachable!("No custom-dns command given"); + } + } +} + +impl CustomDns { + async fn set(&self, servers: Option<Vec<String>>) -> Result<()> { + let mut rpc = new_rpc_client().await?; + rpc.set_custom_dns(types::CustomDns { + addresses: servers.unwrap_or_default(), + }) + .await?; + println!("Updated custom DNS settings"); + Ok(()) + } + + async fn get(&self) -> Result<()> { + let mut rpc = new_rpc_client().await?; + let custom_dns = rpc + .get_settings(()) + .await? + .into_inner() + .tunnel_options + .unwrap() + .generic + .unwrap() + .custom_dns; + match custom_dns { + None => println!("No DNS servers are configured"), + Some(types::CustomDns { addresses }) => { + println!("Custom DNS servers:"); + for server in &addresses { + println!("\t{}", server); + } + } + } + Ok(()) + } +} diff --git a/mullvad-cli/src/cmds/mod.rs b/mullvad-cli/src/cmds/mod.rs index 9cdefd19c5..d542eb7844 100644 --- a/mullvad-cli/src/cmds/mod.rs +++ b/mullvad-cli/src/cmds/mod.rs @@ -25,6 +25,9 @@ pub use self::disconnect::Disconnect; mod lan; pub use self::lan::Lan; +mod custom_dns; +pub use self::custom_dns::CustomDns; + mod reconnect; pub use self::reconnect::Reconnect; @@ -60,6 +63,8 @@ pub fn get_commands() -> HashMap<&'static str, Box<dyn Command>> { Box::new(Disconnect), Box::new(Reconnect), Box::new(Lan), + #[cfg(windows)] + 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 09f522bd81..7f5b85f7e0 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -41,6 +41,8 @@ use mullvad_types::{ wireguard::KeygenEvent, }; use settings::SettingsPersister; +#[cfg(windows)] +use std::net::IpAddr; #[cfg(not(target_os = "android"))] use std::path::Path; use std::{ @@ -192,6 +194,9 @@ pub enum DaemonCommand { SetBridgeState(oneshot::Sender<Result<(), settings::Error>>, BridgeState), /// Set if IPv6 should be enabled in the tunnel SetEnableIpv6(oneshot::Sender<()>, bool), + /// Set custom DNS servers to use instead of passing requests to the gateway + #[cfg(windows)] + SetCustomDns(oneshot::Sender<()>, Option<Vec<IpAddr>>), /// Set MTU for wireguard tunnels SetWireguardMtu(oneshot::Sender<()>, Option<u16>), /// Set automatic key rotation interval for wireguard tunnels @@ -572,10 +577,11 @@ where TargetState::Unsecured }; - let tunnel_command_tx = tunnel_state_machine::spawn( settings.allow_lan, settings.block_when_disconnected, + #[cfg(windows)] + settings.tunnel_options.generic.custom_dns.clone(), tunnel_parameters_generator, log_dir, resource_dir, @@ -1039,6 +1045,8 @@ where } SetBridgeState(tx, bridge_state) => self.on_set_bridge_state(tx, bridge_state), SetEnableIpv6(tx, enable_ipv6) => self.on_set_enable_ipv6(tx, enable_ipv6), + #[cfg(windows)] + SetCustomDns(tx, dns_servers) => self.on_set_custom_dns(tx, dns_servers), SetWireguardMtu(tx, mtu) => self.on_set_wireguard_mtu(tx, mtu), SetWireguardRotationInterval(tx, interval) => { self.on_set_wireguard_rotation_interval(tx, interval).await @@ -1677,6 +1685,22 @@ where } } + #[cfg(windows)] + fn on_set_custom_dns(&mut self, tx: oneshot::Sender<()>, servers: Option<Vec<IpAddr>>) { + let save_result = self.settings.set_custom_dns(servers.clone()); + match save_result { + Ok(settings_changed) => { + Self::oneshot_send(tx, (), "set_custom_dns response"); + if settings_changed { + self.event_listener + .notify_settings(self.settings.to_settings()); + self.send_tunnel_command(TunnelCommand::CustomDns(servers)); + } + } + Err(e) => error!("{}", e.display_chain_with_msg("Unable to save settings")), + } + } + fn on_set_wireguard_mtu(&mut self, tx: oneshot::Sender<()>, mtu: Option<u16>) { let save_result = self.settings.set_wireguard_mtu(mtu); match save_result { diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index e3533b28e2..a5cfebedc3 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -408,6 +408,38 @@ impl ManagementService for ManagementServiceImpl { .map_err(|_| Status::internal("internal error")) } + #[cfg(windows)] + async fn set_custom_dns(&self, request: Request<types::CustomDns>) -> ServiceResult<()> { + let servers = request.into_inner(); + log::debug!("set_custom_dns({:?})", servers.addresses); + + let mut servers_ip = vec![]; + for server in servers.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 servers_ip = if !servers_ip.is_empty() { + Some(servers_ip) + } else { + None + }; + + let (tx, rx) = oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::SetCustomDns(tx, servers_ip))?; + rx.await + .map(Response::new) + .map_err(|_| Status::internal("internal error")) + } + #[cfg(not(windows))] + async fn set_custom_dns(&self, _: Request<types::CustomDns>) -> ServiceResult<()> { + Ok(Response::new(())) + } + // Account management // @@ -1140,6 +1172,16 @@ fn convert_tunnel_options(options: &TunnelOptions) -> types::TunnelOptions { }), generic: Some(types::tunnel_options::GenericOptions { enable_ipv6: options.generic.enable_ipv6, + #[cfg(windows)] + custom_dns: options + .generic + .custom_dns + .as_ref() + .map(|addresses| types::CustomDns { + addresses: addresses.iter().map(|addr| addr.to_string()).collect(), + }), + #[cfg(not(windows))] + custom_dns: None, }), } } diff --git a/mullvad-daemon/src/settings.rs b/mullvad-daemon/src/settings.rs index c343d7c336..1f6f6a447c 100644 --- a/mullvad-daemon/src/settings.rs +++ b/mullvad-daemon/src/settings.rs @@ -3,6 +3,8 @@ use mullvad_types::{ relay_constraints::{BridgeSettings, BridgeState, RelaySettingsUpdate}, settings::Settings, }; +#[cfg(windows)] +use std::net::IpAddr; use std::{ fs::{self, File}, io, @@ -210,6 +212,15 @@ impl SettingsPersister { self.update(should_save) } + #[cfg(windows)] + pub fn set_custom_dns(&mut self, servers: Option<Vec<IpAddr>>) -> Result<bool, Error> { + let should_save = Self::update_field( + &mut self.settings.tunnel_options.generic.custom_dns, + servers, + ); + self.update(should_save) + } + pub fn set_wireguard_mtu(&mut self, mtu: Option<u16>) -> Result<bool, Error> { let should_save = Self::update_field(&mut self.settings.tunnel_options.wireguard.mtu, mtu); self.update(should_save) diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 995bd0d346..b66bc7439f 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -39,6 +39,7 @@ service ManagementService { rpc SetOpenvpnMssfix(google.protobuf.UInt32Value) returns (google.protobuf.Empty) {} rpc SetWireguardMtu(google.protobuf.UInt32Value) returns (google.protobuf.Empty) {} rpc SetEnableIpv6(google.protobuf.BoolValue) returns (google.protobuf.Empty) {} + rpc SetCustomDns(CustomDns) returns (google.protobuf.Empty) {} // Account management rpc CreateNewAccount(google.protobuf.Empty) returns (google.protobuf.StringValue) {} @@ -359,6 +360,7 @@ message TunnelOptions { } message GenericOptions { bool enable_ipv6 = 1; + CustomDns custom_dns = 2; } OpenvpnOptions openvpn = 1; @@ -366,6 +368,10 @@ message TunnelOptions { GenericOptions generic = 3; } +message CustomDns { + repeated string addresses = 1; +} + message PublicKey { bytes key = 1; google.protobuf.Timestamp created = 2; diff --git a/mullvad-types/src/settings/mod.rs b/mullvad-types/src/settings/mod.rs index d32cf5fead..2b8ce221b8 100644 --- a/mullvad-types/src/settings/mod.rs +++ b/mullvad-types/src/settings/mod.rs @@ -177,6 +177,8 @@ impl Default for TunnelOptions { generic: GenericTunnelOptions { // Enable IPv6 be default on Android enable_ipv6: cfg!(target_os = "android"), + #[cfg(windows)] + custom_dns: None, }, } } diff --git a/talpid-core/src/firewall/mod.rs b/talpid-core/src/firewall/mod.rs index 658fba72ae..b427e459d5 100644 --- a/talpid-core/src/firewall/mod.rs +++ b/talpid-core/src/firewall/mod.rs @@ -111,6 +111,9 @@ pub enum FirewallPolicy { tunnel: crate::tunnel::TunnelMetadata, /// Flag setting if communication with LAN networks should be possible. allow_lan: bool, + /// Servers that are allowed to respond to DNS requests. + #[cfg(windows)] + dns_servers: Vec<IpAddr>, /// A process that is allowed to send packets to the relay. #[cfg(windows)] relay_client: PathBuf, diff --git a/talpid-core/src/firewall/windows.rs b/talpid-core/src/firewall/windows.rs index 52062c3494..90f1d73c78 100644 --- a/talpid-core/src/firewall/windows.rs +++ b/talpid-core/src/firewall/windows.rs @@ -1,6 +1,6 @@ use crate::logging::windows::log_sink; -use std::{net::IpAddr, path::Path, ptr}; +use std::{ffi::OsString, iter, net::IpAddr, path::Path, ptr}; use self::winfw::*; use super::{FirewallArguments, FirewallPolicy, FirewallT}; @@ -99,10 +99,11 @@ impl FirewallT for Firewall { peer_endpoint, tunnel, allow_lan, + dns_servers, relay_client, } => { let cfg = &WinFwSettings::new(allow_lan); - self.set_connected_state(&peer_endpoint, &cfg, &tunnel, &relay_client) + self.set_connected_state(&peer_endpoint, &cfg, &tunnel, &dns_servers, &relay_client) } FirewallPolicy::Blocked { allow_lan } => { let cfg = &WinFwSettings::new(allow_lan); @@ -192,6 +193,7 @@ impl Firewall { endpoint: &Endpoint, winfw_settings: &WinFwSettings, tunnel_metadata: &crate::tunnel::TunnelMetadata, + dns_servers: &[IpAddr], relay_client: &Path, ) -> Result<(), Error> { trace!("Applying 'connected' firewall policy"); @@ -228,6 +230,18 @@ impl Firewall { let mut relay_client: Vec<u16> = relay_client.as_os_str().encode_wide().collect(); relay_client.push(0u16); + let dns_servers: Vec<Vec<u16>> = dns_servers + .iter() + .map(|ip| { + OsString::from(ip.to_string()) + .as_os_str() + .encode_wide() + .chain(iter::once(0u16)) + .collect() + }) + .collect(); + let dns_servers: Vec<*const u16> = dns_servers.iter().map(|ip| ip.as_ptr()).collect(); + unsafe { WinFw_ApplyPolicyConnected( winfw_settings, @@ -236,6 +250,8 @@ impl Firewall { tunnel_alias.as_ptr(), v4_gateway.as_ptr(), v6_gateway_ptr, + dns_servers.as_ptr(), + dns_servers.len(), ) .into_result() .map_err(Error::ApplyingConnectedPolicy) @@ -392,6 +408,8 @@ mod winfw { tunnelIfaceAlias: *const libc::wchar_t, v4Gateway: *const libc::wchar_t, v6Gateway: *const libc::wchar_t, + dnsServers: *const *const libc::wchar_t, + numDnsServers: usize, ) -> WinFwPolicyStatus; #[link_name = "WinFw_ApplyPolicyBlocked"] diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs index a36093d51c..a7fd495c50 100644 --- a/talpid-core/src/tunnel_state_machine/connected_state.rs +++ b/talpid-core/src/tunnel_state_machine/connected_state.rs @@ -8,6 +8,7 @@ use crate::{ tunnel::{CloseHandle, TunnelEvent, TunnelMetadata}, }; use futures::{channel::mpsc, stream::Fuse, StreamExt}; +use std::net::IpAddr; use talpid_types::{ net::TunnelParameters, tunnel::{ErrorStateCause, FirewallPolicyError}, @@ -75,12 +76,38 @@ impl ConnectedState { }) } + #[allow(unused_variables)] + fn get_dns_servers(&self, shared_values: &SharedTunnelStateValues) -> Vec<IpAddr> { + #[cfg(windows)] + if let Some(ref servers) = shared_values.custom_dns { + servers.clone() + } else { + let mut dns_ips = vec![]; + dns_ips.push(self.metadata.ipv4_gateway.into()); + if let Some(ipv6_gateway) = self.metadata.ipv6_gateway { + dns_ips.push(ipv6_gateway.into()); + }; + dns_ips + } + #[cfg(not(windows))] + { + let mut dns_ips = vec![]; + dns_ips.push(self.metadata.ipv4_gateway.into()); + if let Some(ipv6_gateway) = self.metadata.ipv6_gateway { + dns_ips.push(ipv6_gateway.into()); + }; + dns_ips + } + } + fn get_firewall_policy(&self, shared_values: &SharedTunnelStateValues) -> FirewallPolicy { FirewallPolicy::Connected { peer_endpoint: self.tunnel_parameters.get_next_hop_endpoint(), tunnel: self.metadata.clone(), allow_lan: shared_values.allow_lan, #[cfg(windows)] + dns_servers: self.get_dns_servers(shared_values), + #[cfg(windows)] relay_client: TunnelMonitor::get_relay_client( &shared_values.resource_dir, &self.tunnel_parameters, @@ -91,11 +118,7 @@ impl ConnectedState { } fn set_dns(&self, shared_values: &mut SharedTunnelStateValues) -> Result<(), BoxedError> { - 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()); - }; - + let dns_ips = self.get_dns_servers(shared_values); shared_values .dns_monitor .set(&self.metadata.interface, &dns_ips) @@ -159,6 +182,32 @@ impl ConnectedState { } } } + #[cfg(windows)] + Some(TunnelCommand::CustomDns(servers)) => { + if shared_values.custom_dns != servers { + shared_values.custom_dns = servers; + + 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) { + 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), + ) + } + } + } else { + SameState(self.into()) + } + } 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 bfccac6572..6f081697e5 100644 --- a/talpid-core/src/tunnel_state_machine/connecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs @@ -227,6 +227,11 @@ impl ConnectingState { } } } + #[cfg(windows)] + Some(TunnelCommand::CustomDns(servers)) => { + shared_values.custom_dns = servers; + SameState(self.into()) + } 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 faeac0a45f..4781f19091 100644 --- a/talpid-core/src/tunnel_state_machine/disconnected_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnected_state.rs @@ -82,6 +82,11 @@ impl TunnelState for DisconnectedState { } SameState(self.into()) } + #[cfg(windows)] + Some(TunnelCommand::CustomDns(servers)) => { + shared_values.custom_dns = servers; + SameState(self.into()) + } Some(TunnelCommand::BlockWhenDisconnected(block_when_disconnected)) => { if shared_values.block_when_disconnected != block_when_disconnected { shared_values.block_when_disconnected = block_when_disconnected; diff --git a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs index 33a09ca31a..356df9be53 100644 --- a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs @@ -32,6 +32,11 @@ impl DisconnectingState { let _ = shared_values.set_allow_lan(allow_lan); AfterDisconnect::Nothing } + #[cfg(windows)] + Some(TunnelCommand::CustomDns(servers)) => { + shared_values.custom_dns = servers; + AfterDisconnect::Nothing + } Some(TunnelCommand::BlockWhenDisconnected(block_when_disconnected)) => { shared_values.block_when_disconnected = block_when_disconnected; AfterDisconnect::Nothing @@ -49,6 +54,11 @@ impl DisconnectingState { let _ = shared_values.set_allow_lan(allow_lan); AfterDisconnect::Block(reason) } + #[cfg(windows)] + Some(TunnelCommand::CustomDns(servers)) => { + shared_values.custom_dns = servers; + AfterDisconnect::Block(reason) + } Some(TunnelCommand::BlockWhenDisconnected(block_when_disconnected)) => { shared_values.block_when_disconnected = block_when_disconnected; AfterDisconnect::Block(reason) @@ -71,6 +81,11 @@ impl DisconnectingState { let _ = shared_values.set_allow_lan(allow_lan); AfterDisconnect::Reconnect(retry_attempt) } + #[cfg(windows)] + Some(TunnelCommand::CustomDns(servers)) => { + shared_values.custom_dns = servers; + AfterDisconnect::Reconnect(retry_attempt) + } Some(TunnelCommand::BlockWhenDisconnected(block_when_disconnected)) => { shared_values.block_when_disconnected = block_when_disconnected; AfterDisconnect::Reconnect(retry_attempt) diff --git a/talpid-core/src/tunnel_state_machine/error_state.rs b/talpid-core/src/tunnel_state_machine/error_state.rs index a9861e788e..aa53e0b0b5 100644 --- a/talpid-core/src/tunnel_state_machine/error_state.rs +++ b/talpid-core/src/tunnel_state_machine/error_state.rs @@ -102,6 +102,11 @@ impl TunnelState for ErrorState { SameState(self.into()) } } + #[cfg(windows)] + Some(TunnelCommand::CustomDns(servers)) => { + shared_values.custom_dns = servers; + SameState(self.into()) + } 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/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs index 37a27ae58f..90bf9a5d29 100644 --- a/talpid-core/src/tunnel_state_machine/mod.rs +++ b/talpid-core/src/tunnel_state_machine/mod.rs @@ -24,6 +24,8 @@ use futures::{ channel::{mpsc, oneshot}, stream, StreamExt, }; +#[cfg(windows)] +use std::net::IpAddr; use std::{ collections::HashSet, io, @@ -74,6 +76,7 @@ pub enum Error { pub async fn spawn( allow_lan: bool, block_when_disconnected: bool, + #[cfg(windows)] custom_dns: Option<Vec<IpAddr>>, tunnel_parameters_generator: impl TunnelParametersGenerator, log_dir: Option<PathBuf>, resource_dir: PathBuf, @@ -109,6 +112,8 @@ pub async fn spawn( allow_lan, block_when_disconnected, is_offline, + #[cfg(windows)] + custom_dns, tunnel_parameters_generator, tun_provider, log_dir, @@ -147,6 +152,9 @@ pub async fn spawn( pub enum TunnelCommand { /// Enable or disable LAN access in the firewall. AllowLan(bool), + /// Set custom DNS servers to use. + #[cfg(windows)] + CustomDns(Option<Vec<IpAddr>>), /// Enable or disable the block_when_disconnected feature. BlockWhenDisconnected(bool), /// Notify the state machine of the connectivity of the device. @@ -184,6 +192,7 @@ impl TunnelStateMachine { allow_lan: bool, block_when_disconnected: bool, is_offline: bool, + #[cfg(windows)] custom_dns: Option<Vec<IpAddr>>, tunnel_parameters_generator: impl TunnelParametersGenerator, tun_provider: TunProvider, log_dir: Option<PathBuf>, @@ -208,6 +217,8 @@ impl TunnelStateMachine { allow_lan, block_when_disconnected, is_offline, + #[cfg(windows)] + custom_dns, tunnel_parameters_generator: Box::new(tunnel_parameters_generator), tun_provider, log_dir, @@ -277,6 +288,9 @@ struct SharedTunnelStateValues { block_when_disconnected: bool, /// True when the computer is known to be offline. is_offline: bool, + /// Custom DNS servers to use. + #[cfg(windows)] + custom_dns: Option<Vec<IpAddr>>, /// The generator of new `TunnelParameter`s tunnel_parameters_generator: Box<dyn TunnelParametersGenerator>, /// The provider of tunnel devices. diff --git a/talpid-types/src/net/mod.rs b/talpid-types/src/net/mod.rs index 15bf33a5bb..36528e9744 100644 --- a/talpid-types/src/net/mod.rs +++ b/talpid-types/src/net/mod.rs @@ -203,6 +203,9 @@ pub struct GenericTunnelOptions { /// Enable configuration of IPv6 on the tunnel interface, allowing IPv6 communication to be /// forwarded through the tunnel. pub enable_ipv6: bool, + /// Custom DNS servers to use. + #[cfg(windows)] + pub custom_dns: Option<Vec<IpAddr>>, } /// Returns a vector of IP networks representing all of the internet, 0.0.0.0/0. diff --git a/windows/libwfp b/windows/libwfp -Subproject c56a9b2d02c6b09539192bd017169a811337058 +Subproject c73a1773046ce5c044d44ef362a3c4274c17d4f diff --git a/windows/winfw/src/winfw/fwcontext.cpp b/windows/winfw/src/winfw/fwcontext.cpp index 65b5762500..b8959cfc01 100644 --- a/windows/winfw/src/winfw/fwcontext.cpp +++ b/windows/winfw/src/winfw/fwcontext.cpp @@ -17,6 +17,7 @@ #include "rules/baseline/permitdns.h" #include "rules/dns/blockall.h" #include "rules/dns/permittunnel.h" +#include "rules/dns/permitnontunnel.h" #include "rules/multi/permitvpnrelay.h" #include <libwfp/transaction.h> #include <libwfp/filterengine.h> @@ -210,7 +211,8 @@ bool FwContext::applyPolicyConnected const WinFwRelay &relay, const std::wstring &relayClient, const std::wstring &tunnelInterfaceAlias, - const std::vector<wfp::IpAddress> &tunnelDnsServers + const std::vector<wfp::IpAddress> &tunnelDnsServers, + const std::vector<wfp::IpAddress> &nonTunnelDnsServers ) { Ruleset ruleset; @@ -219,9 +221,18 @@ bool FwContext::applyPolicyConnected AppendSettingsRules(ruleset, settings); AppendRelayRules(ruleset, relay, relayClient); - ruleset.emplace_back(std::make_unique<dns::PermitTunnel>( - tunnelInterfaceAlias, tunnelDnsServers - )); + if (!tunnelDnsServers.empty()) + { + ruleset.emplace_back(std::make_unique<dns::PermitTunnel>( + tunnelInterfaceAlias, tunnelDnsServers + )); + } + if (!nonTunnelDnsServers.empty()) + { + ruleset.emplace_back(std::make_unique<dns::PermitNonTunnel>( + tunnelInterfaceAlias, nonTunnelDnsServers + )); + } ruleset.emplace_back(std::make_unique<baseline::PermitVpnTunnel>( tunnelInterfaceAlias diff --git a/windows/winfw/src/winfw/fwcontext.h b/windows/winfw/src/winfw/fwcontext.h index e342f52fe5..100672073a 100644 --- a/windows/winfw/src/winfw/fwcontext.h +++ b/windows/winfw/src/winfw/fwcontext.h @@ -43,7 +43,8 @@ public: const WinFwRelay &relay, const std::wstring &relayClient, const std::wstring &tunnelInterfaceAlias, - const std::vector<wfp::IpAddress> &tunnelDnsServers + const std::vector<wfp::IpAddress> &tunnelDnsServers, + const std::vector<wfp::IpAddress> &nonTunnelDnsServers ); bool applyPolicyBlocked(const WinFwSettings &settings); diff --git a/windows/winfw/src/winfw/winfw.cpp b/windows/winfw/src/winfw/winfw.cpp index 4998feef7f..080713b742 100644 --- a/windows/winfw/src/winfw/winfw.cpp +++ b/windows/winfw/src/winfw/winfw.cpp @@ -4,8 +4,10 @@ #include "objectpurger.h" #include "mullvadobjects.h" #include "rules/persistent/blockall.h" +#include "libwfp/ipnetwork.h" #include <windows.h> #include <libcommon/error.h> +#include <libcommon/string.h> #include <optional> namespace @@ -63,6 +65,23 @@ HandlePolicyException(const common::error::WindowsException &err) return WINFW_POLICY_STATUS_GENERAL_FAILURE; } +// +// Networks for which DNS requests can be made on all network adapters. +// +// This should be synchronized with `ALLOWED_LAN_NETS` in talpid-core, +// but it also includes loopback addresses. +// +wfp::IpNetwork g_privateIpRanges[] = { + wfp::IpNetwork(wfp::IpAddress::Literal{127, 0, 0, 0}, 8), + wfp::IpNetwork(wfp::IpAddress::Literal{10, 0, 0, 0}, 8), + wfp::IpNetwork(wfp::IpAddress::Literal{176, 16, 0, 0}, 12), + wfp::IpNetwork(wfp::IpAddress::Literal{192, 168, 0, 0}, 16), + wfp::IpNetwork(wfp::IpAddress::Literal{169, 254, 0, 0}, 16), + wfp::IpNetwork(wfp::IpAddress::Literal6{0, 0, 0, 0, 0, 0, 0, 1}, 128), + wfp::IpNetwork(wfp::IpAddress::Literal6{0xfe80, 0, 0, 0, 0, 0, 0, 0}, 10), + wfp::IpNetwork(wfp::IpAddress::Literal6{0xfc80, 0, 0, 0, 0, 0, 0, 0}, 7) +}; + } // anonymous namespace WINFW_LINKAGE @@ -289,8 +308,10 @@ WinFw_ApplyPolicyConnected( const WinFwRelay *relay, const wchar_t *relayClient, const wchar_t *tunnelInterfaceAlias, - const wchar_t *v4DnsHost, - const wchar_t *v6DnsHost + const wchar_t *v4Gateway, + const wchar_t *v6Gateway, + const wchar_t **dnsServers, + size_t numDnsServers ) { if (nullptr == g_fwContext) @@ -320,16 +341,78 @@ WinFw_ApplyPolicyConnected( THROW_ERROR("Invalid argument: tunnelInterfaceAlias"); } - if (nullptr == v4DnsHost) + if (nullptr == v4Gateway) + { + THROW_ERROR("Invalid argument: v4Gateway"); + } + + if (nullptr == dnsServers || 0 == numDnsServers) { - THROW_ERROR("Invalid argument: v4DnsHost"); + THROW_ERROR("Invalid argument: dnsServers"); } - std::vector<wfp::IpAddress> tunnelDnsServers = { wfp::IpAddress(v4DnsHost) }; + std::vector<wfp::IpAddress> tunnelDnsServers; + std::vector<wfp::IpAddress> nonTunnelDnsServers; + + const auto v4GatewayIp = wfp::IpAddress(v4Gateway); + const auto v6GatewayIp = (nullptr != v6Gateway) + ? std::make_optional(wfp::IpAddress(v6Gateway)) + : std::nullopt; - if (nullptr != v6DnsHost) + const auto addToDnsCollection = [&](const std::optional<wfp::IpAddress> &gatewayIp, wfp::IpAddress &&ip) { - tunnelDnsServers.emplace_back(wfp::IpAddress(v6DnsHost)); + if (gatewayIp.has_value() && *gatewayIp == ip) + { + // Requests to the gateway IP of the tunnel are only allowed on the tunnel interface. + tunnelDnsServers.emplace_back(ip); + return; + } + + for (const auto &network : g_privateIpRanges) + { + if (network.includes(ip)) + { + // + // Resolvers on the LAN must be accessible outside the tunnel. + // + + nonTunnelDnsServers.emplace_back(ip); + return; + } + } + + tunnelDnsServers.emplace_back(ip); + }; + + for (size_t i = 0; i < numDnsServers; i++) + { + auto ip = wfp::IpAddress(dnsServers[i]); + addToDnsCollection(ip.type() == wfp::IpAddress::Type::Ipv4 ? v4GatewayIp : v6GatewayIp, std::move(ip)); + } + + if (nullptr != g_logSink) + { + std::stringstream ss; + ss << "Non-tunnel DNS servers: "; + for (size_t i = 0; i < nonTunnelDnsServers.size(); i++) { + if (i > 0) + { + ss << ", "; + } + ss << common::string::ToAnsi(nonTunnelDnsServers[i].toString()); + } + g_logSink(MULLVAD_LOG_LEVEL_DEBUG, ss.str().c_str(), g_logSinkContext); + + ss.str(std::string()); + ss << "Tunnel DNS servers: "; + for (size_t i = 0; i < tunnelDnsServers.size(); i++) { + if (i > 0) + { + ss << ", "; + } + ss << common::string::ToAnsi(tunnelDnsServers[i].toString()); + } + g_logSink(MULLVAD_LOG_LEVEL_DEBUG, ss.str().c_str(), g_logSinkContext); } return g_fwContext->applyPolicyConnected( @@ -337,7 +420,8 @@ WinFw_ApplyPolicyConnected( *relay, relayClient, tunnelInterfaceAlias, - tunnelDnsServers + tunnelDnsServers, + nonTunnelDnsServers ) ? WINFW_POLICY_STATUS_SUCCESS : WINFW_POLICY_STATUS_GENERAL_FAILURE; } catch (common::error::WindowsException &err) diff --git a/windows/winfw/src/winfw/winfw.h b/windows/winfw/src/winfw/winfw.h index b3f95a2cbf..f0a487cb12 100644 --- a/windows/winfw/src/winfw/winfw.h +++ b/windows/winfw/src/winfw/winfw.h @@ -167,14 +167,15 @@ WinFw_ApplyPolicyConnecting( // - What is specified by settings // - Communication with the relay server // - Non-DNS traffic inside the VPN tunnel -// - DNS requests inside the VPN tunnel, to the specified DNS server +// - DNS requests inside the VPN tunnel to any specified remote DNS server +// - DNS requests outside the VPN tunnel to any specified local DNS servers // // Parameters: // // tunnelInterfaceAlias: // Friendly name of VPN tunnel interface -// v4DnsHost/v6DnsHost: -// String encoded IP address of DNS to use inside tunnel +// dnsServers: +// Array of string-encoded IP addresses of DNS servers to use // extern "C" WINFW_LINKAGE @@ -185,8 +186,10 @@ WinFw_ApplyPolicyConnected( const WinFwRelay *relay, const wchar_t *relayClient, const wchar_t *tunnelInterfaceAlias, - const wchar_t *v4DnsHost, - const wchar_t *v6DnsHost + const wchar_t *v4Gateway, + const wchar_t *v6Gateway, + const wchar_t **dnsServers, + size_t numDnsServers ); // |
