diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2017-08-15 22:18:06 +0100 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2017-08-17 19:09:02 +0100 |
| commit | 89b685bde4b30523987986bcaf046d196b7d74a5 (patch) | |
| tree | f786d92626ab1474a6d80f3b110fab2faf9204a2 /talpid-core/src | |
| parent | ad59695517f40d4c3c90a222212232d309d37edc (diff) | |
| download | mullvadvpn-89b685bde4b30523987986bcaf046d196b7d74a5.tar.xz mullvadvpn-89b685bde4b30523987986bcaf046d196b7d74a5.zip | |
Add macOS firewall implementation
Diffstat (limited to 'talpid-core/src')
| -rw-r--r-- | talpid-core/src/firewall/macos.rs | 141 | ||||
| -rw-r--r-- | talpid-core/src/lib.rs | 3 |
2 files changed, 138 insertions, 6 deletions
diff --git a/talpid-core/src/firewall/macos.rs b/talpid-core/src/firewall/macos.rs index d55150a2a1..3bf6bc8db6 100644 --- a/talpid-core/src/firewall/macos.rs +++ b/talpid-core/src/firewall/macos.rs @@ -1,21 +1,150 @@ use super::{Firewall, SecurityPolicy}; +use net; +use pfctl; // alias used to instantiate firewall implementation pub type ConcreteFirewall = PacketFilter; +pub use pfctl::{Error, ErrorKind, Result}; -error_chain!{} +const ANCHOR_NAME: &'static str = "talpid_core"; + +impl From<net::Endpoint> for pfctl::Endpoint { + fn from(endpoint: net::Endpoint) -> Self { + pfctl::Endpoint( + pfctl::Ip::from(endpoint.address.ip()), + pfctl::Port::from(endpoint.address.port()), + ) + } +} + +impl From<net::Endpoint> for pfctl::Proto { + fn from(endpoint: net::Endpoint) -> Self { + match endpoint.protocol { + net::TransportProtocol::Udp => pfctl::Proto::Udp, + net::TransportProtocol::Tcp => pfctl::Proto::Tcp, + } + } +} + +pub struct PacketFilter { + pf: pfctl::PfCtl, + pf_was_enabled: Option<bool>, +} -pub struct PacketFilter; impl Firewall<Error> for PacketFilter { fn new() -> Result<Self> { - Ok(PacketFilter) + Ok( + PacketFilter { + pf: pfctl::PfCtl::new()?, + pf_was_enabled: None, + }, + ) } - fn apply_policy(&mut self, _policy: SecurityPolicy) -> Result<()> { - Ok(()) + fn apply_policy(&mut self, policy: SecurityPolicy) -> Result<()> { + self.enable()?; + self.add_anchor()?; + self.set_rules(policy) } fn reset_policy(&mut self) -> Result<()> { - Ok(()) + vec![ + self.remove_rules(), + self.remove_anchor(), + self.restore_state(), + ] + .into_iter() + .collect::<Result<Vec<_>>>() + .map(|_| ()) + } +} + +impl PacketFilter { + fn set_rules(&mut self, policy: SecurityPolicy) -> Result<()> { + let drop_all_rule = pfctl::FilterRuleBuilder::default() + .action(pfctl::RuleAction::Drop) + .quick(true) + .build()?; + let allow_dns_rule = pfctl::FilterRuleBuilder::default() + .action(pfctl::RuleAction::Pass) + .direction(pfctl::Direction::Out) + .quick(true) + .to(pfctl::Port::One(53, pfctl::PortUnaryModifier::Equal)) + .keep_state(pfctl::StatePolicy::Keep) + .build()?; + let mut new_rules = self.get_loopback_rules()?; + + match policy { + SecurityPolicy::Connecting(relay_endpoint) => { + new_rules.push(Self::get_relay_rule(relay_endpoint)?); + } + SecurityPolicy::Connected(relay_endpoint, tunnel_interface) => { + new_rules.push(Self::get_relay_rule(relay_endpoint)?); + new_rules.push(Self::get_tunnel_rule(tunnel_interface)?); + } + }; + + new_rules.push(allow_dns_rule); + new_rules.push(drop_all_rule); + + self.pf.set_rules(ANCHOR_NAME, &new_rules) + } + + fn get_relay_rule(relay_endpoint: net::Endpoint) -> Result<pfctl::FilterRule> { + pfctl::FilterRuleBuilder::default() + .action(pfctl::RuleAction::Pass) + .direction(pfctl::Direction::Out) + .to(relay_endpoint) + .proto(relay_endpoint) + .keep_state(pfctl::StatePolicy::Keep) + .quick(true) + .build() + } + + fn get_tunnel_rule(tunnel_interface: String) -> Result<pfctl::FilterRule> { + pfctl::FilterRuleBuilder::default() + .action(pfctl::RuleAction::Pass) + .interface(tunnel_interface) + .keep_state(pfctl::StatePolicy::Keep) + .quick(true) + .build() + } + + fn get_loopback_rules(&self) -> Result<Vec<pfctl::FilterRule>> { + let lo0_rule = pfctl::FilterRuleBuilder::default() + .action(pfctl::RuleAction::Pass) + .interface("lo0") + .keep_state(pfctl::StatePolicy::Keep) + .quick(true) + .build()?; + Ok(vec![lo0_rule]) + } + + fn remove_rules(&mut self) -> Result<()> { + // remove_anchor() does not deactivate active rules + self.pf.flush_rules(ANCHOR_NAME, pfctl::RulesetKind::Filter) + } + + fn enable(&mut self) -> Result<()> { + if self.pf_was_enabled.is_none() { + self.pf_was_enabled = Some(self.pf.is_enabled()?); + } + self.pf.try_enable() + } + + fn restore_state(&mut self) -> Result<()> { + match self.pf_was_enabled.take() { + Some(true) => self.pf.try_enable(), + Some(false) => self.pf.try_disable(), + None => Ok(()), + } + } + + fn add_anchor(&mut self) -> Result<()> { + self.pf.try_add_anchor(ANCHOR_NAME, pfctl::AnchorKind::Filter) + } + + fn remove_anchor(&mut self) -> Result<()> { + self.pf.try_remove_anchor(ANCHOR_NAME, pfctl::AnchorKind::Filter) } } diff --git a/talpid-core/src/lib.rs b/talpid-core/src/lib.rs index 9c78ef02e8..cbdc08886e 100644 --- a/talpid-core/src/lib.rs +++ b/talpid-core/src/lib.rs @@ -23,6 +23,9 @@ extern crate jsonrpc_macros; extern crate talpid_ipc; extern crate openvpn_plugin; +#[cfg(target_os = "macos")] +extern crate pfctl; + /// Working with processes. pub mod process; |
