diff options
| author | Linus Färnstrand <linus@mullvad.net> | 2017-09-21 19:53:43 +0200 |
|---|---|---|
| committer | Linus Färnstrand <linus@mullvad.net> | 2017-09-21 19:53:43 +0200 |
| commit | fd706f069252ad80d3d53517a523c786ed68797b (patch) | |
| tree | 50ee610aea80c049191bd5544b7ffc673e38391d | |
| parent | 1854efd1b5494d6371b49bda113fb02781fa6116 (diff) | |
| parent | 80df5535ffc0fa7229f5b5b28a800cd8c14894f6 (diff) | |
| download | mullvadvpn-fd706f069252ad80d3d53517a523c786ed68797b.tar.xz mullvadvpn-fd706f069252ad80d3d53517a523c786ed68797b.zip | |
Merge branch 'dns-proxy'
| -rw-r--r-- | Cargo.lock | 8 | ||||
| -rw-r--r-- | mullvad-daemon/src/main.rs | 18 | ||||
| -rw-r--r-- | socket-relay/src/udp.rs | 2 | ||||
| -rw-r--r-- | talpid-core/Cargo.toml | 4 | ||||
| -rw-r--r-- | talpid-core/src/firewall/macos.rs | 157 | ||||
| -rw-r--r-- | talpid-core/src/firewall/mod.rs | 2 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/mod.rs | 34 |
7 files changed, 179 insertions, 46 deletions
diff --git a/Cargo.lock b/Cargo.lock index 1a42817892..7dff8bc43a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -866,7 +866,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "pfctl" version = "0.1.0" -source = "git+https://github.com/mullvad/pfctl-rs.git#17c90bca15244ae0a28fa54339c074ff306206d0" +source = "git+ssh://git@github.com/mullvad/pfctl-rs.git#35ca17308409ffab07e3a2a7c9c9ba3c81f8ee5e" dependencies = [ "bindgen 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)", "derive_builder 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1196,9 +1196,11 @@ dependencies = [ "libc 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "openvpn-plugin 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pfctl 0.1.0 (git+https://github.com/mullvad/pfctl-rs.git)", + "pfctl 0.1.0 (git+ssh://git@github.com/mullvad/pfctl-rs.git)", + "socket-relay 0.1.0", "talpid-ipc 0.1.0", "talpid-types 0.1.0", + "tokio-core 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1587,7 +1589,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum parking_lot_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0ad2c4d148942b3560034785bf19df586ebba53351e8c78f84984147d5795eef" "checksum peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" "checksum percent-encoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de154f638187706bde41d9b4738748933d64e6b37bdbffc0b47a97d16a6ae356" -"checksum pfctl 0.1.0 (git+https://github.com/mullvad/pfctl-rs.git)" = "<none>" +"checksum pfctl 0.1.0 (git+ssh://git@github.com/mullvad/pfctl-rs.git)" = "<none>" "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" "checksum quasi 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18c45c4854d6d1cf5d531db97c75880feb91c958b0720f4ec1057135fec358b3" "checksum quasi_codegen 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "51b9e25fa23c044c1803f43ca59c98dac608976dd04ce799411edd58ece776d4" diff --git a/mullvad-daemon/src/main.rs b/mullvad-daemon/src/main.rs index f252d762d3..017c428fc2 100644 --- a/mullvad-daemon/src/main.rs +++ b/mullvad-daemon/src/main.rs @@ -63,7 +63,7 @@ use std::thread; use talpid_core::firewall::{Firewall, FirewallProxy, SecurityPolicy}; use talpid_core::mpsc::IntoSender; -use talpid_core::tunnel::{self, TunnelEvent, TunnelMonitor}; +use talpid_core::tunnel::{self, TunnelEvent, TunnelMetadata, TunnelMonitor}; use talpid_types::net::{Endpoint, TransportProtocol}; error_chain!{ @@ -177,7 +177,7 @@ struct Daemon { accounts_proxy: AccountsProxy<HttpHandle>, firewall: FirewallProxy, relay_endpoint: Option<Endpoint>, - tunnel_interface: Option<String>, + tunnel_metadata: Option<TunnelMetadata>, // Just for testing. A cyclic iterator iterating over the hardcoded relays, // picking a new one for each retry. @@ -207,7 +207,7 @@ impl Daemon { .chain_err(|| "Unable to bootstrap RPC client")?, firewall: FirewallProxy::new().chain_err(|| ErrorKind::FirewallError)?, relay_endpoint: None, - tunnel_interface: None, + tunnel_metadata: None, relay_iter: RELAYS.iter().cloned().cycle(), }) } @@ -278,8 +278,8 @@ impl Daemon { fn handle_tunnel_event(&mut self, tunnel_event: TunnelEvent) -> Result<()> { debug!("Tunnel event: {:?}", tunnel_event); if self.state == TunnelState::Connecting { - if let TunnelEvent::Up { tunnel_interface } = tunnel_event { - self.tunnel_interface = Some(tunnel_interface); + if let TunnelEvent::Up(metadata) = tunnel_event { + self.tunnel_metadata = Some(metadata); self.set_security_policy()?; self.set_state(TunnelState::Connected) } else { @@ -297,7 +297,7 @@ impl Daemon { error!("{}", e.display_chain()); } self.relay_endpoint = None; - self.tunnel_interface = None; + self.tunnel_metadata = None; self.reset_security_policy()?; self.tunnel_close_handle = None; self.set_state(TunnelState::NotRunning) @@ -567,9 +567,11 @@ impl Daemon { } fn set_security_policy(&mut self) -> Result<()> { - let policy = match (self.relay_endpoint, self.tunnel_interface.as_ref()) { + let policy = match (self.relay_endpoint, self.tunnel_metadata.as_ref()) { (Some(relay), None) => SecurityPolicy::Connecting(relay), - (Some(relay), Some(interface)) => SecurityPolicy::Connected(relay, interface.clone()), + (Some(relay), Some(tunnel_metadata)) => { + SecurityPolicy::Connected(relay, tunnel_metadata.clone()) + } _ => bail!(ErrorKind::InvalidState), }; debug!("Set security policy: {:?}", policy); diff --git a/socket-relay/src/udp.rs b/socket-relay/src/udp.rs index 4e3a2ba600..032b238442 100644 --- a/socket-relay/src/udp.rs +++ b/socket-relay/src/udp.rs @@ -15,7 +15,7 @@ use tokio_core::reactor::Handle; use tokio_timer::Timer; /// The amount of idle (no replies) time needed for the forwarding socket to close. -pub static FORWARD_TIMEOUT_MS: u64 = 60000; +pub static FORWARD_TIMEOUT_MS: u64 = 8000; /// Number of slots in internal channel transfering responses back to clients. pub static CLIENT_SINK_CHANNEL_SIZE: usize = 10; diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml index ba70f49071..bf21f63001 100644 --- a/talpid-core/Cargo.toml +++ b/talpid-core/Cargo.toml @@ -22,7 +22,9 @@ talpid-types = { path = "../talpid-types" } libc = "0.2.20" [target.'cfg(target_os = "macos")'.dependencies] -pfctl = { git = "https://github.com/mullvad/pfctl-rs.git" } +pfctl = { git = "ssh://git@github.com/mullvad/pfctl-rs.git" } +socket-relay = { path = "../socket-relay" } +tokio-core = "0.1" [dev-dependencies] assert_matches = "1.0" diff --git a/talpid-core/src/firewall/macos.rs b/talpid-core/src/firewall/macos.rs index 14c9fb5033..70bb2cfb20 100644 --- a/talpid-core/src/firewall/macos.rs +++ b/talpid-core/src/firewall/macos.rs @@ -1,17 +1,27 @@ +extern crate socket_relay; +extern crate tokio_core; + use super::{Firewall, SecurityPolicy}; use pfctl; -use std::net::Ipv4Addr; + +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::sync::mpsc; +use std::thread; + +use self::socket_relay::udp::{Relay, RelayCloseHandle}; use talpid_types::net; +use tunnel::TunnelMetadata; // alias used to instantiate firewall implementation pub type ConcreteFirewall = PacketFilter; -pub use pfctl::{Error, ErrorKind, Result}; +pub use pfctl::{Error, ErrorKind, Result, ResultExt}; const ANCHOR_NAME: &'static str = "talpid_core"; pub struct PacketFilter { pf: pfctl::PfCtl, pf_was_enabled: Option<bool>, + dns_proxy_close_handle: Option<RelayCloseHandle>, } impl Firewall<Error> for PacketFilter { @@ -19,6 +29,7 @@ impl Firewall<Error> for PacketFilter { Ok(PacketFilter { pf: pfctl::PfCtl::new()?, pf_was_enabled: None, + dns_proxy_close_handle: None, }) } @@ -29,6 +40,7 @@ impl Firewall<Error> for PacketFilter { } fn reset_policy(&mut self) -> Result<()> { + self.stop_dns_proxy(); vec![ self.remove_rules(), self.remove_anchor(), @@ -41,35 +53,71 @@ impl Firewall<Error> for PacketFilter { impl PacketFilter { fn set_rules(&mut self, policy: SecurityPolicy) -> Result<()> { - let drop_all_rule = pfctl::FilterRuleBuilder::default() - .action(pfctl::FilterRuleAction::Drop) - .quick(true) - .build()?; - let allow_dns_rule = pfctl::FilterRuleBuilder::default() - .action(pfctl::FilterRuleAction::Pass) - .direction(pfctl::Direction::Out) - .quick(true) - .to(pfctl::Port::One(53, pfctl::PortUnaryModifier::Equal)) - .keep_state(pfctl::StatePolicy::Keep) - .tcp_flags(Self::get_tcp_flags()) - .build()?; - let mut new_rules = self.get_loopback_rules()?; + let mut new_filter_rules = self.get_loopback_rules()?; + let mut new_redirect_rules = vec![]; match policy { SecurityPolicy::Connecting(relay_endpoint) => { - new_rules.push(Self::get_relay_rule(relay_endpoint)?); + self.stop_dns_proxy(); + new_filter_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)?); + SecurityPolicy::Connected(relay_endpoint, tunnel) => { + let dns_proxy_listen_addr = self.start_dns_proxy(&tunnel)?; + + let allow_dns_to_relay_rule = pfctl::FilterRuleBuilder::default() + .action(pfctl::FilterRuleAction::Pass) + .direction(pfctl::Direction::Out) + .quick(true) + .interface(&tunnel.interface) + .proto(pfctl::Proto::Udp) + .to(pfctl::Endpoint::new(tunnel.gateway, 53)) + .build()?; + let reroute_dns_rule = pfctl::FilterRuleBuilder::default() + .action(pfctl::FilterRuleAction::Pass) + .direction(pfctl::Direction::Out) + .quick(true) + .route(pfctl::Route::route_to(pfctl::Interface::from("lo0"))) + .proto(pfctl::Proto::Udp) + .to(pfctl::Port::from(53)) + .build()?; + let block_all_other_dns_rule = pfctl::FilterRuleBuilder::default() + .action(pfctl::FilterRuleAction::Drop) + .direction(pfctl::Direction::Out) + .quick(true) + .proto(pfctl::Proto::Tcp) + .to(pfctl::Port::from(53)) + .build()?; + + new_filter_rules.push(allow_dns_to_relay_rule); + new_filter_rules.push(reroute_dns_rule); + new_filter_rules.push(block_all_other_dns_rule); + + let dns_redirect_rule = pfctl::RedirectRuleBuilder::default() + .action(pfctl::RedirectRuleAction::Redirect) + .interface("lo0") + .proto(pfctl::Proto::Udp) + .to(pfctl::Port::from(53)) + .redirect_to(dns_proxy_listen_addr) + .build()?; + new_redirect_rules.push(dns_redirect_rule); + + new_filter_rules.push(Self::get_relay_rule(relay_endpoint)?); + new_filter_rules.push(Self::get_tunnel_rule(tunnel.interface.as_str())?); } }; - new_rules.push(allow_dns_rule); - new_rules.append(&mut Self::get_dhcp_rules()?); - new_rules.push(drop_all_rule); + new_filter_rules.append(&mut Self::get_dhcp_rules()?); + + let drop_all_rule = pfctl::FilterRuleBuilder::default() + .action(pfctl::FilterRuleAction::Drop) + .quick(true) + .build()?; + new_filter_rules.push(drop_all_rule); - self.pf.set_rules(ANCHOR_NAME, &new_rules) + let mut anchor_change = pfctl::AnchorChange::new(); + anchor_change.set_filter_rules(new_filter_rules); + anchor_change.set_redirect_rules(new_redirect_rules); + self.pf.set_rules(ANCHOR_NAME, anchor_change) } fn get_relay_rule(relay_endpoint: net::Endpoint) -> Result<pfctl::FilterRule> { @@ -86,7 +134,7 @@ impl PacketFilter { .build() } - fn get_tunnel_rule(tunnel_interface: String) -> Result<pfctl::FilterRule> { + fn get_tunnel_rule(tunnel_interface: &str) -> Result<pfctl::FilterRule> { pfctl::FilterRuleBuilder::default() .action(pfctl::FilterRuleAction::Pass) .interface(tunnel_interface) @@ -158,12 +206,29 @@ impl PacketFilter { fn add_anchor(&mut self) -> Result<()> { self.pf - .try_add_anchor(ANCHOR_NAME, pfctl::AnchorKind::Filter) + .try_add_anchor(ANCHOR_NAME, pfctl::AnchorKind::Filter)?; + self.pf + .try_add_anchor(ANCHOR_NAME, pfctl::AnchorKind::Redirect) } fn remove_anchor(&mut self) -> Result<()> { self.pf - .try_remove_anchor(ANCHOR_NAME, pfctl::AnchorKind::Filter) + .try_remove_anchor(ANCHOR_NAME, pfctl::AnchorKind::Filter)?; + self.pf + .try_remove_anchor(ANCHOR_NAME, pfctl::AnchorKind::Redirect) + } + + fn start_dns_proxy(&mut self, tunnel: &TunnelMetadata) -> Result<SocketAddr> { + self.stop_dns_proxy(); + let (listen_addr, close_handle) = spawn_dns_proxy(tunnel.ip, tunnel.gateway)?; + self.dns_proxy_close_handle = Some(close_handle); + Ok(listen_addr) + } + + fn stop_dns_proxy(&mut self) { + if let Some(close_handle) = self.dns_proxy_close_handle.take() { + close_handle.close(); + } } } @@ -173,3 +238,43 @@ fn as_pfctl_proto(protocol: net::TransportProtocol) -> pfctl::Proto { net::TransportProtocol::Tcp => pfctl::Proto::Tcp, } } + +fn spawn_dns_proxy( + tunnel_ip: Ipv4Addr, + tunnel_gateway: Ipv4Addr, +) -> Result<(SocketAddr, RelayCloseHandle)> { + let (tx, rx) = mpsc::channel(); + thread::spawn(move || { + match spawn_dns_proxy_helper(tunnel_ip, tunnel_gateway) { + Ok((mut core, relay)) => { + tx.send(Ok((relay.listen_addr(), relay.close_handle()))) + .unwrap(); + match core.run(relay) { + Err(e) => error!("DNS proxy died with an error: {}", e), + Ok(_) => info!("DNS proxy exiting"), + } + } + Err(e) => { + tx.send(Err(e)).unwrap(); + } + } + }); + rx.recv().unwrap() +} + +fn spawn_dns_proxy_helper( + tunnel_ip: Ipv4Addr, + tunnel_gateway: Ipv4Addr, +) -> Result<(tokio_core::reactor::Core, Relay)> { + let core = tokio_core::reactor::Core::new().chain_err(|| "Unable to init Tokio event loop")?; + + let relay = Relay::new( + "127.0.0.1:0".parse().unwrap(), + IpAddr::V4(tunnel_ip), + SocketAddr::from((tunnel_gateway, 53)), + core.handle(), + ).chain_err(|| "Unable to create DNS proxy socket relay")?; + info!("DNS proxy listening on {}", relay.listen_addr()); + + Ok((core, relay)) +} diff --git a/talpid-core/src/firewall/mod.rs b/talpid-core/src/firewall/mod.rs index a1fa7edbc3..6ac63e2493 100644 --- a/talpid-core/src/firewall/mod.rs +++ b/talpid-core/src/firewall/mod.rs @@ -32,7 +32,7 @@ pub enum SecurityPolicy { Connecting(Endpoint), /// Allow traffic only to relay server and over tunnel interface - Connected(Endpoint, String), + Connected(Endpoint, ::tunnel::TunnelMetadata), } /// Abstract firewall interaction trait diff --git a/talpid-core/src/tunnel/mod.rs b/talpid-core/src/tunnel/mod.rs index 5f73e5be20..4ee7b24cee 100644 --- a/talpid-core/src/tunnel/mod.rs +++ b/talpid-core/src/tunnel/mod.rs @@ -9,7 +9,9 @@ use std::env; use std::ffi::{OsStr, OsString}; use std::fs; use std::io::{self, Write}; +use std::net::Ipv4Addr; use std::path::{Path, PathBuf}; + use talpid_types::net; /// A module for all OpenVPN related tunnel management. @@ -46,14 +48,22 @@ pub use self::errors::*; #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum TunnelEvent { /// Sent when the tunnel comes up and is ready for traffic. - Up { - /// The name of the device which the tunnel is running on. - tunnel_interface: String, - }, + Up(TunnelMetadata), /// Sent when the tunnel goes down. Down, } +/// Information about a VPN tunnel. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct TunnelMetadata { + /// The name of the device which the tunnel is running on. + pub interface: String, + /// The local IP on the tunnel interface. + pub ip: Ipv4Addr, + /// The IP to the default gateway on the tunnel interface. + pub gateway: Ipv4Addr, +} + impl TunnelEvent { /// Converts an `OpenVpnPluginEvent` to a `TunnelEvent`. /// Returns `None` if there is no corresponding `TunnelEvent`. @@ -63,10 +73,22 @@ impl TunnelEvent { ) -> Option<TunnelEvent> { match *event { OpenVpnPluginEvent::Up => { - let tunnel_interface = env.get("dev") + let interface = env.get("dev") .expect("No \"dev\" in tunnel up event") .to_owned(); - Some(TunnelEvent::Up { tunnel_interface }) + let ip = env.get("ifconfig_local") + .expect("No \"ifconfig_local\" in tunnel up event") + .parse() + .expect("Tunnel IP not in valid format"); + let gateway = env.get("route_vpn_gateway") + .expect("No \"route_vpn_gateway\" in tunnel up event") + .parse() + .expect("Tunnel gateway IP not in valid format"); + Some(TunnelEvent::Up(TunnelMetadata { + interface, + ip, + gateway, + })) } OpenVpnPluginEvent::RoutePredown => Some(TunnelEvent::Down), _ => None, |
