summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorLinus Färnstrand <linus@mullvad.net>2017-09-21 19:53:43 +0200
committerLinus Färnstrand <linus@mullvad.net>2017-09-21 19:53:43 +0200
commitfd706f069252ad80d3d53517a523c786ed68797b (patch)
tree50ee610aea80c049191bd5544b7ffc673e38391d
parent1854efd1b5494d6371b49bda113fb02781fa6116 (diff)
parent80df5535ffc0fa7229f5b5b28a800cd8c14894f6 (diff)
downloadmullvadvpn-fd706f069252ad80d3d53517a523c786ed68797b.tar.xz
mullvadvpn-fd706f069252ad80d3d53517a523c786ed68797b.zip
Merge branch 'dns-proxy'
-rw-r--r--Cargo.lock8
-rw-r--r--mullvad-daemon/src/main.rs18
-rw-r--r--socket-relay/src/udp.rs2
-rw-r--r--talpid-core/Cargo.toml4
-rw-r--r--talpid-core/src/firewall/macos.rs157
-rw-r--r--talpid-core/src/firewall/mod.rs2
-rw-r--r--talpid-core/src/tunnel/mod.rs34
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,