summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorEmīls Piņķis <emils@mullvad.net>2018-04-13 11:19:47 +0100
committerEmīls Piņķis <emils@mullvad.net>2018-04-13 11:19:47 +0100
commitd6f07449a13537b6f7d778b84f0394c53978b900 (patch)
tree3353db4e833ba98033b9d19353bc323922f3182d
parent692671f73f8c4a03588359f1ab0f4e459fed1afa (diff)
parentea69957e801d85cbffc7c8c97443a193fd3cdef8 (diff)
downloadmullvadvpn-d6f07449a13537b6f7d778b84f0394c53978b900.tar.xz
mullvadvpn-d6f07449a13537b6f7d778b84f0394c53978b900.zip
Merge branch 'wfp-integration'
-rw-r--r--CHANGELOG.md1
-rw-r--r--Cargo.lock7
-rw-r--r--README.md16
-rw-r--r--appveyor.yml25
-rw-r--r--build_wfp.sh149
-rw-r--r--talpid-core/Cargo.toml4
-rw-r--r--talpid-core/build.rs60
-rw-r--r--talpid-core/src/firewall/windows.rs21
-rw-r--r--talpid-core/src/firewall/windows/mod.rs241
9 files changed, 495 insertions, 29 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a5a61ddbbf..90e4886b75 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -42,6 +42,7 @@ Line wrap the file at 100 chars. Th
paths and window captions etc.
- Bundle an IP address with the app and introduce a disk cache fallback method for when the DNS
resolution of the Mullvad API server hostname fails.
+- Manage firewall rules on Windows depending on connection state.
### Fixed
- Fix a bug in account input field that advanced the cursor to the end regardless its prior
diff --git a/Cargo.lock b/Cargo.lock
index 0152eb979c..c0043253e7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1166,6 +1166,7 @@ dependencies = [
"talpid-types 0.1.0",
"tokio-core 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
"uuid 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "widestring 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1411,6 +1412,11 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
+name = "widestring"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
name = "winapi"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1629,6 +1635,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c"
"checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
+"checksum widestring 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a212922ea58fbf5044f83663aa4fc6281ff890f1fd7546c0c3f52f5290831781"
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
"checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3"
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
diff --git a/README.md b/README.md
index 73b24d5e33..fe5680b0d5 100644
--- a/README.md
+++ b/README.md
@@ -51,6 +51,22 @@ homebrew:
It must run as root since it it modifies the firewall and sets up virtual network interfaces
etc.
+### Prerequisites for Windows
+There are some extra steps to build the daemon on Windows:
+- The host has to have Microsoft's _Build Tools for Visual Studio 2017_ (a
+regular installation of Visual Studio 2017 Community edition works as well).
+The specific build tool version that is required is `v141`.
+
+- The host has to have `msbuild.exe` available in `%PATH%`.
+
+- The host has to have `bash` installed.
+
+- Before compiling the daemon, one must run `build_wfp.sh` to build a C++
+ library that sets firewall rules on Windows.
+ ```bash
+ bash build_wfp.sh
+ ```
+
## Building and running the Electron GUI app
1. Install all the JavaScript dependencies by running:
diff --git a/appveyor.yml b/appveyor.yml
index 6e5638c9e9..444a57cbd2 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,29 +1,35 @@
# Based on the "trust" template v0.1.1
# https://github.com/japaric/trust/tree/v0.1.1
+image:
+- Visual Studio 2017
+
+platform:
+- x64
+
environment:
global:
# This is the Rust channel that build jobs will use by default but can be
# overridden on a case by case basis down below
RUST_VERSION: stable
+ RUST_BACKTRACE: "1"
+ WFP_BUILD_MODES: "Debug"
# These are all the build jobs. Adjust as necessary. Comment out what you
# don't need
matrix:
- # MinGW
- - TARGET: i686-pc-windows-gnu
- - TARGET: x86_64-pc-windows-gnu
-
- # MSVC
- TARGET: i686-pc-windows-msvc
+ WFP_BUILD_TARGETS: "x86"
+
- TARGET: x86_64-pc-windows-msvc
+ WFP_BUILD_TARGETS: "x64"
# Testing other channels
- - TARGET: x86_64-pc-windows-gnu
+ - TARGET: x86_64-pc-windows-msvc
+ WFP_BUILD_TARGETS: "x64"
RUST_VERSION: beta
- - TARGET: x86_64-pc-windows-gnu
- RUST_VERSION: nightly
- TARGET: x86_64-pc-windows-msvc
+ WFP_BUILD_TARGETS: "x64"
RUST_VERSION: nightly
install:
@@ -39,10 +45,13 @@ install:
# TMP fix for https://github.com/rust-lang-nursery/rustup.rs/issues/893:
- set PATH=%PATH%;C:\Users\appveyor\.rustup\toolchains\%RUST_VERSION%-%TARGET%\bin
- rustc -Vv
+ - rustc --print cfg
- cargo -V
+ - git submodule update --init --recursive
# This is the "test phase", tweak it as you see fit
test_script:
+ - bash -x build_wfp.sh
- cargo build
- cargo test
diff --git a/build_wfp.sh b/build_wfp.sh
new file mode 100644
index 0000000000..8ffa6f9d6c
--- /dev/null
+++ b/build_wfp.sh
@@ -0,0 +1,149 @@
+set -eu
+
+# List of solutions to build
+WFP_SOLUTIONS=${WFP_SOLUTIONS:-"wfpctl"}
+
+# Override this variable to set your own list of build configurations for
+# wfpctl
+WFP_BUILD_MODES=${WFP_BUILD_MODES:-"Debug Release"}
+# Override this variable to set different target platforms for wfpctl
+WFP_BUILD_TARGETS=${WFP_BUILD_TARGETS:-"x86 x64"}
+# Override this to set a different cargo target directory
+CARGO_TARGET_DIR=${CARGO_TARGET_DIR:-"./target/"}
+
+# Builds all 4 variations of the wfpctl.dll library. Takes an argument that is
+# the root of the WFP repository.
+function build_wfpctl
+{
+ local path="$1/wfpctl.sln"
+ set -x
+ for mode in $WFP_BUILD_MODES; do
+ for target in $WFP_BUILD_TARGETS; do
+ msbuild.exe "$(to_win_path $path)" \
+ //p:Configuration=$mode \
+ //p:Platform=$target \
+ //t:$WFP_SOLUTIONS
+ done
+ done
+ set +x
+}
+
+function to_win_path
+{
+ echo $1 | sed -e 's/^\///' -e 's/\//\\/g' -e 's/^./\0:/'
+}
+
+function copy_outputs
+{
+ local wfp_root_path=$1
+
+ for mode in $WFP_BUILD_MODES; do
+ for target in $WFP_BUILD_TARGETS; do
+ local dll_path=$(get_wfp_output_path $wfp_root_path $target $mode)
+ local cargo_target=$(get_cargo_target_dir $target $mode)
+ mkdir -p $cargo_target
+ cp "$dll_path/wfpctl.dll" $cargo_target
+ done
+ done
+
+}
+
+function get_wfp_output_path
+{
+ local wfp_root=$1
+ local build_target=$2
+ local build_mode=$3
+
+ case $build_target in
+ "x86")
+ echo "$wfp_root/bin/Win32-$build_mode"
+ ;;
+ "x64")
+ echo "$wfp_root/bin/x64-$build_mode"
+ ;;
+ *)
+ echo Unkown build target $build_target
+ exit 1
+ ;;
+ esac
+}
+
+# builds an appropriate cargo target path for the specified build target and
+# build mode
+function get_cargo_target_dir
+{
+ local build_target=$1
+ local build_mode=$2
+
+ local host_arch=$(rustc_host_arch)
+ local host_target_arch=$(arch_from_build_target $host_arch)
+ local build_target_arch=$(arch_from_build_target $build_target)
+ # if the target is the same as the host, cargo omits the platform triplet
+ if [ "$host_target_arch" == "$build_target_arch" ]; then
+ platform_triplet=""
+ # otherwise, the cargo target path is build with the platform triplet
+ else
+ platform_triplet="$build_target_arch-pc-windows-msvc"
+ fi
+
+ echo "$CARGO_TARGET_DIR/$platform_triplet/${build_mode,,}"
+}
+
+# Rust isn't internally consistent w.r.t. architecture names - for build
+# targets 32 bit x86 is calles i686, for hosts, it's x86. Hence these need to
+# be converted.
+function host_arch_to_target_arch
+{
+ local host_arch=$1
+
+ case $host_arch in
+ "x86")
+ echo "i686"
+ ;;
+ "x64")
+ echo "x86_64"
+ ;;
+ *)
+ echo $build_target
+ ;;
+ esac
+}
+
+# Since Microsoft likes to name their architectures differently from Rust, this
+# function tries to match microsoft names to Rust names.
+function arch_from_build_target
+{
+ local build_target=$1
+
+ case $build_target in
+ "x86")
+ echo "i686"
+ ;;
+ "x64")
+ echo "x86_64"
+ ;;
+ *)
+ echo $build_target
+ ;;
+ esac
+}
+
+function rustc_host_arch
+{
+ rustc --print cfg \
+ | grep '^target_arch=' \
+ | cut -d'=' -f2 \
+ | tr -d '"'
+}
+
+
+function main
+{
+
+ local wfp_root_path=${WFP_ROOT_PATH:-"$(pwd)/wfpctl"}
+
+ build_wfpctl $wfp_root_path
+ copy_outputs $wfp_root_path
+}
+
+main
diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml
index b49a7bf5d8..a95d46eb9e 100644
--- a/talpid-core/Cargo.toml
+++ b/talpid-core/Cargo.toml
@@ -27,3 +27,7 @@ pfctl = "0.1"
system-configuration = "0.1"
core-foundation = "0.5"
tokio-core = "0.1"
+
+[target.'cfg(windows)'.dependencies]
+libc = "0.2.20"
+widestring = "0.3"
diff --git a/talpid-core/build.rs b/talpid-core/build.rs
new file mode 100644
index 0000000000..a9a80d11eb
--- /dev/null
+++ b/talpid-core/build.rs
@@ -0,0 +1,60 @@
+#[cfg(windows)]
+mod win {
+ use std::env;
+ use std::path::PathBuf;
+
+ static WFP_BUILD_DIR: &'static str = "..\\wfpctl\\bin";
+
+ pub fn default_wfpctl_output_dir() -> PathBuf {
+ let target = env::var("TARGET").expect("TARGET env var not set");
+
+ let target_dir = match target.as_str() {
+ "i686-pc-windows-msvc" => format!("Win32-{}", get_build_mode()),
+ "x86_64-pc-windows-msvc" => format!("x64-{}", get_build_mode()),
+ _ => panic!("uncrecognized target: {}", target),
+ };
+
+ let mut lib_dir = manifest_dir();
+ lib_dir.push(WFP_BUILD_DIR);
+ lib_dir.push(&target_dir);
+
+ lib_dir
+ }
+
+ fn manifest_dir() -> PathBuf {
+ env::var("CARGO_MANIFEST_DIR")
+ .map(PathBuf::from)
+ .expect("CARGO_MANIFEST_DIR env var not set")
+ }
+
+ fn get_build_mode() -> &'static str {
+ let profile = env::var("PROFILE").expect("PROFILE env var not set");
+ if profile == "release" {
+ "Release"
+ } else {
+ "Debug"
+ }
+ }
+}
+
+#[cfg(windows)]
+fn main() {
+ use std::env;
+ use std::path::PathBuf;
+ use win::*;
+
+ let wfpctl_dir = env::var_os("WFPCTL_INCLUDE_DIR")
+ .map(PathBuf::from)
+ .unwrap_or_else(default_wfpctl_output_dir);
+
+ println!(
+ "cargo:rustc-link-search={}",
+ wfpctl_dir
+ .to_str()
+ .expect("failed to construct path for wfpctl include directory")
+ );
+ println!("cargo:rustc-link-lib=dylib=wfpctl");
+}
+
+#[cfg(not(windows))]
+fn main() {}
diff --git a/talpid-core/src/firewall/windows.rs b/talpid-core/src/firewall/windows.rs
deleted file mode 100644
index 4df9d87e6a..0000000000
--- a/talpid-core/src/firewall/windows.rs
+++ /dev/null
@@ -1,21 +0,0 @@
-use super::{Firewall, SecurityPolicy};
-
-error_chain!{}
-
-/// The Windows implementation for the `Firewall` trait.
-pub struct WindowsFirewall;
-impl Firewall for WindowsFirewall {
- type Error = Error;
-
- fn new() -> Result<Self> {
- Ok(WindowsFirewall)
- }
-
- fn apply_policy(&mut self, _policy: SecurityPolicy) -> Result<()> {
- Ok(())
- }
-
- fn reset_policy(&mut self) -> Result<()> {
- Ok(())
- }
-}
diff --git a/talpid-core/src/firewall/windows/mod.rs b/talpid-core/src/firewall/windows/mod.rs
new file mode 100644
index 0000000000..c0a3782253
--- /dev/null
+++ b/talpid-core/src/firewall/windows/mod.rs
@@ -0,0 +1,241 @@
+extern crate libc;
+extern crate widestring;
+
+use super::{Firewall, SecurityPolicy};
+use std::net::IpAddr;
+use std::ptr;
+
+use self::ffi::*;
+use talpid_types::net::Endpoint;
+
+use self::widestring::WideCString;
+
+error_chain!{
+ errors{
+ #[doc = "Windows firewall module error"]
+ WfpctlFailure(desc: &'static str){
+ description("Opaque Wfpctl failure")
+ display("Wfpctl failed when {}", desc)
+ }
+ }
+}
+
+const WFPCTL_TIMEOUT_SECONDS: u32 = 2;
+
+/// The Windows implementation for the `Firewall` trait.
+pub struct WindowsFirewall {
+ _unused: [u8; 0],
+}
+
+impl Firewall for WindowsFirewall {
+ type Error = Error;
+
+ fn new() -> Result<Self> {
+ let ok =
+ unsafe { Wfpctl_Initialize(WFPCTL_TIMEOUT_SECONDS, Some(error_sink), ptr::null_mut()) };
+ ok.into_result("initialise wfpctl").map(|_| {
+ trace!("Successfully initialized wfpctl");
+ WindowsFirewall { _unused: [] }
+ })
+ }
+
+ fn apply_policy(&mut self, policy: SecurityPolicy) -> Result<()> {
+ match policy {
+ SecurityPolicy::Connecting {
+ relay_endpoint,
+ allow_lan,
+ } => {
+ let cfg = &WfpCtlSettings::new(allow_lan);
+ self.set_connecting_state(&relay_endpoint, &cfg)
+ }
+ SecurityPolicy::Connected {
+ relay_endpoint,
+ tunnel,
+ allow_lan,
+ } => {
+ let cfg = &WfpCtlSettings::new(allow_lan);
+ self.set_connected_state(&relay_endpoint, &cfg, &tunnel)
+ }
+ }
+ }
+
+ fn reset_policy(&mut self) -> Result<()> {
+ trace!("Resetting firewall policy");
+ let ok = unsafe { Wfpctl_Reset() };
+ ok.into_result("resetting firewall")
+ }
+}
+
+impl Drop for WindowsFirewall {
+ fn drop(&mut self) {
+ if unsafe { Wfpctl_Deinitialize().is_ok() } {
+ trace!("Successfully deinitialized wfpctl");
+ } else {
+ error!("Failed to deinitialize wfpctl");
+ };
+ }
+}
+
+impl WindowsFirewall {
+ fn set_connecting_state(
+ &mut self,
+ endpoint: &Endpoint,
+ wfp_settings: &WfpCtlSettings,
+ ) -> Result<()> {
+ trace!("Applying 'connecting' firewall policy");
+ let ip_str = Self::widestring_ip(&endpoint.address.ip());
+
+ // ip_str has to outlive wfp_relay
+ let wfp_relay = WfpCtlRelay {
+ ip: ip_str.as_wide_c_str().as_ptr(),
+ port: endpoint.address.port(),
+ protocol: WfpCtlProt::from(endpoint.protocol),
+ };
+
+ let ok = unsafe { Wfpctl_ApplyPolicyConnecting(wfp_settings, &wfp_relay) };
+ ok.into_result("applying 'connecting' policy")
+ }
+
+ fn widestring_ip(ip: &IpAddr) -> WideCString {
+ let buf = ip.to_string().encode_utf16().collect::<Vec<_>>();
+ WideCString::new(buf).unwrap()
+ }
+
+ fn set_connected_state(
+ &mut self,
+ endpoint: &Endpoint,
+ wfp_settings: &WfpCtlSettings,
+ tunnel_metadata: &::tunnel::TunnelMetadata,
+ ) -> Result<()> {
+ trace!("Applying 'connected' firewall policy");
+ let ip_str = Self::widestring_ip(&endpoint.address.ip());
+ let gateway_str = Self::widestring_ip(&tunnel_metadata.gateway.into());
+
+ let tunnel_alias =
+ WideCString::new(tunnel_metadata.interface.encode_utf16().collect::<Vec<_>>()).unwrap();
+
+ // ip_str, gateway_str and tunnel_alias have to outlive wfp_relay
+ let wfp_relay = WfpCtlRelay {
+ ip: ip_str.as_wide_c_str().as_ptr(),
+ port: endpoint.address.port(),
+ protocol: WfpCtlProt::from(endpoint.protocol),
+ };
+
+ let ok = unsafe {
+ Wfpctl_ApplyPolicyConnected(
+ wfp_settings,
+ &wfp_relay,
+ tunnel_alias.as_wide_c_str().as_ptr(),
+ gateway_str.as_wide_c_str().as_ptr(),
+ )
+ };
+ ok.into_result("applying 'connected' policy")
+ }
+}
+
+
+#[allow(non_snake_case)]
+mod ffi {
+
+ use super::libc;
+ use super::{ErrorKind, Result};
+ use std::ffi::CStr;
+ use std::os::raw::c_char;
+ use std::ptr;
+ use talpid_types::net::TransportProtocol;
+
+ #[repr(C)]
+ pub struct WfpCtlResult {
+ ok: bool,
+ }
+
+ impl WfpCtlResult {
+ pub fn into_result(self, description: &'static str) -> Result<()> {
+ match self.ok {
+ true => Ok(()),
+ false => Err(ErrorKind::WfpctlFailure(description).into()),
+ }
+ }
+
+ pub fn is_ok(&self) -> bool {
+ self.ok
+ }
+ }
+
+ pub type ErrorSink = extern "system" fn(msg: *const c_char, ctx: *mut libc::c_void);
+
+ pub extern "system" fn error_sink(msg: *const c_char, _ctx: *mut libc::c_void) {
+ if msg == ptr::null() {
+ error!("log message from wfpctl is NULL");
+ } else {
+ error!("{}", unsafe { CStr::from_ptr(msg).to_string_lossy() });
+ }
+ }
+
+ #[repr(C)]
+ pub struct WfpCtlRelay {
+ pub ip: *const libc::wchar_t,
+ pub port: u16,
+ pub protocol: WfpCtlProt,
+ }
+
+ #[repr(u8)]
+ #[derive(Clone, Copy)]
+ pub enum WfpCtlProt {
+ Tcp = 0u8,
+ Udp = 1u8,
+ }
+
+ impl From<TransportProtocol> for WfpCtlProt {
+ fn from(prot: TransportProtocol) -> WfpCtlProt {
+ match prot {
+ TransportProtocol::Tcp => WfpCtlProt::Tcp,
+ TransportProtocol::Udp => WfpCtlProt::Udp,
+ }
+ }
+ }
+
+ #[repr(C)]
+ pub struct WfpCtlSettings {
+ permitDhcp: bool,
+ permitLan: bool,
+ }
+
+ impl WfpCtlSettings {
+ pub fn new(permit_lan: bool) -> WfpCtlSettings {
+ WfpCtlSettings {
+ permitDhcp: true,
+ permitLan: permit_lan,
+ }
+ }
+ }
+
+ extern "system" {
+ #[link_name(Wfpctl_Initialize)]
+ pub fn Wfpctl_Initialize(
+ timeout: libc::c_uint,
+ sink: Option<ErrorSink>,
+ sink_context: *mut libc::c_void,
+ ) -> WfpCtlResult;
+
+ #[link_name(Wfpctl_Deinitialize)]
+ pub fn Wfpctl_Deinitialize() -> WfpCtlResult;
+
+ #[link_name(Wfpctl_ApplyPolicyConnecting)]
+ pub fn Wfpctl_ApplyPolicyConnecting(
+ settings: &WfpCtlSettings,
+ relay: &WfpCtlRelay,
+ ) -> WfpCtlResult;
+
+ #[link_name(Wfpctl_ApplyPolicyConnected)]
+ pub fn Wfpctl_ApplyPolicyConnected(
+ settings: &WfpCtlSettings,
+ relay: &WfpCtlRelay,
+ tunnelIfaceAlias: *const libc::wchar_t,
+ primaryDns: *const libc::wchar_t,
+ ) -> WfpCtlResult;
+
+ #[link_name(Wfpctl_Reset)]
+ pub fn Wfpctl_Reset() -> WfpCtlResult;
+ }
+}