summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2020-10-22 09:52:19 +0200
committerDavid Lönnhager <david.l@mullvad.net>2020-10-22 09:52:19 +0200
commitd88d5f32768fe14c7aa6276e61104e66dd05d1fe (patch)
tree8747ab07d076a5927c9144bb8123a63be0e0315b
parent82d224e656001e23f674db58b9408dca0ff4ed98 (diff)
parenteb7a77321c818e64325c6d328cb45d026bf56b21 (diff)
downloadmullvadvpn-d88d5f32768fe14c7aa6276e61104e66dd05d1fe.tar.xz
mullvadvpn-d88d5f32768fe14c7aa6276e61104e66dd05d1fe.zip
Merge branch 'win-custom-dns'
-rw-r--r--CHANGELOG.md3
-rw-r--r--docs/security.md16
-rw-r--r--mullvad-cli/src/cmds/custom_dns.rs74
-rw-r--r--mullvad-cli/src/cmds/mod.rs5
-rw-r--r--mullvad-daemon/src/lib.rs26
-rw-r--r--mullvad-daemon/src/management_interface.rs42
-rw-r--r--mullvad-daemon/src/settings.rs11
-rw-r--r--mullvad-management-interface/proto/management_interface.proto6
-rw-r--r--mullvad-types/src/settings/mod.rs2
-rw-r--r--talpid-core/src/firewall/mod.rs3
-rw-r--r--talpid-core/src/firewall/windows.rs22
-rw-r--r--talpid-core/src/tunnel_state_machine/connected_state.rs59
-rw-r--r--talpid-core/src/tunnel_state_machine/connecting_state.rs5
-rw-r--r--talpid-core/src/tunnel_state_machine/disconnected_state.rs5
-rw-r--r--talpid-core/src/tunnel_state_machine/disconnecting_state.rs15
-rw-r--r--talpid-core/src/tunnel_state_machine/error_state.rs5
-rw-r--r--talpid-core/src/tunnel_state_machine/mod.rs14
-rw-r--r--talpid-types/src/net/mod.rs3
m---------windows/libwfp0
-rw-r--r--windows/winfw/src/winfw/fwcontext.cpp19
-rw-r--r--windows/winfw/src/winfw/fwcontext.h3
-rw-r--r--windows/winfw/src/winfw/winfw.cpp100
-rw-r--r--windows/winfw/src/winfw/winfw.h13
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
);
//