diff options
| author | Emīls Piņķis <emils@mullvad.net> | 2018-04-13 11:19:47 +0100 |
|---|---|---|
| committer | Emīls Piņķis <emils@mullvad.net> | 2018-04-13 11:19:47 +0100 |
| commit | d6f07449a13537b6f7d778b84f0394c53978b900 (patch) | |
| tree | 3353db4e833ba98033b9d19353bc323922f3182d | |
| parent | 692671f73f8c4a03588359f1ab0f4e459fed1afa (diff) | |
| parent | ea69957e801d85cbffc7c8c97443a193fd3cdef8 (diff) | |
| download | mullvadvpn-d6f07449a13537b6f7d778b84f0394c53978b900.tar.xz mullvadvpn-d6f07449a13537b6f7d778b84f0394c53978b900.zip | |
Merge branch 'wfp-integration'
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | Cargo.lock | 7 | ||||
| -rw-r--r-- | README.md | 16 | ||||
| -rw-r--r-- | appveyor.yml | 25 | ||||
| -rw-r--r-- | build_wfp.sh | 149 | ||||
| -rw-r--r-- | talpid-core/Cargo.toml | 4 | ||||
| -rw-r--r-- | talpid-core/build.rs | 60 | ||||
| -rw-r--r-- | talpid-core/src/firewall/windows.rs | 21 | ||||
| -rw-r--r-- | talpid-core/src/firewall/windows/mod.rs | 241 |
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" @@ -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; + } +} |
