diff options
| author | Emīls Piņķis <emils@mullvad.net> | 2018-04-03 18:17:05 +0100 |
|---|---|---|
| committer | Emīls Piņķis <emils@mullvad.net> | 2018-04-13 11:18:46 +0100 |
| commit | ddac7bb327d7109e6922a545a838a37f14dd6701 (patch) | |
| tree | 7f6f470f2e954854673857acad2dcf93d6d0705b | |
| parent | 2397bab569521dcd6d4b9670cd546e29f461a85b (diff) | |
| download | mullvadvpn-ddac7bb327d7109e6922a545a838a37f14dd6701.tar.xz mullvadvpn-ddac7bb327d7109e6922a545a838a37f14dd6701.zip | |
Add windows firewall implementation
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | Cargo.lock | 7 | ||||
| -rw-r--r-- | talpid-core/Cargo.toml | 4 | ||||
| -rw-r--r-- | talpid-core/src/firewall/windows.rs | 21 | ||||
| -rw-r--r-- | talpid-core/src/firewall/windows/mod.rs | 241 |
5 files changed, 253 insertions, 21 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index a5a61ddbbf..d469e341bb 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/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/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..80251d7638 --- /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::{ErrorKind, Result}; + use super::libc; + use std::ffi::CStr; + use std::os::raw::c_char; + use talpid_types::net::TransportProtocol; + use std::ptr; + + #[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; + } +} |
