summaryrefslogtreecommitdiffhomepage
path: root/talpid-core
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2020-03-24 16:01:46 +0100
committerDavid Lönnhager <david.l@mullvad.net>2020-06-02 10:05:02 +0200
commit37e885e75b4ddce6bdaa5ffdcb5fa1557544231f (patch)
treed6c3ef609c84138c581db5ad085216cbe3eb4e63 /talpid-core
parent444458e588c5b14530d0bfa6872e7cbe3c786e15 (diff)
downloadmullvadvpn-37e885e75b4ddce6bdaa5ffdcb5fa1557544231f.tar.xz
mullvadvpn-37e885e75b4ddce6bdaa5ffdcb5fa1557544231f.zip
Encapsulate functions in split
Diffstat (limited to 'talpid-core')
-rw-r--r--talpid-core/src/split.rs424
-rw-r--r--talpid-core/src/tunnel_state_machine/connected_state.rs13
-rw-r--r--talpid-core/src/tunnel_state_machine/connecting_state.rs2
-rw-r--r--talpid-core/src/tunnel_state_machine/disconnected_state.rs4
-rw-r--r--talpid-core/src/tunnel_state_machine/mod.rs8
5 files changed, 236 insertions, 215 deletions
diff --git a/talpid-core/src/split.rs b/talpid-core/src/split.rs
index 678197a416..8973a6724c 100644
--- a/talpid-core/src/split.rs
+++ b/talpid-core/src/split.rs
@@ -1,7 +1,7 @@
use regex::Regex;
use std::{
fs,
- io::{self, BufRead, BufReader, Write},
+ io::{self, BufRead, BufReader, BufWriter, Write},
net::{AddrParseError, IpAddr},
path::Path,
process::Command,
@@ -70,6 +70,7 @@ struct DefaultRoute {
address: IpAddr,
}
+/// Obtain the IP/interface of the physical interface
fn get_default_route() -> Result<DefaultRoute, Error> {
// FIXME: use netlink
let mut cmd = Command::new("ip");
@@ -96,261 +97,282 @@ fn get_default_route() -> Result<DefaultRoute, Error> {
Err(Error::NoDefaultRoute)
}
-fn reset_table() -> Result<(), Error> {
- // Flush table
- let mut cmd = Command::new("ip");
- cmd.args(&["-4", "route", "flush", "table", ROUTING_TABLE_NAME]);
+/// Manage routing for split tunneling cgroup.
+pub struct SplitTunnel;
- log::trace!("running cmd - {:?}", &cmd);
- cmd.output().map_err(Error::RoutingTableSetup)?;
+impl SplitTunnel {
+ /// Object that allows specified applications to not pass through the tunnel
+ pub fn new() -> Result<SplitTunnel, Error> {
+ Self::initialize_routing_table()?;
+ Ok(SplitTunnel {})
+ }
- // Force routing through the physical interface
- let default_route = get_default_route()?;
- let mut cmd = Command::new("ip");
- cmd.args(&[
- "-4",
- "route",
- "add",
- "default",
- "via",
- &default_route.address.to_string(),
- "dev",
- &default_route.interface,
- "table",
- ROUTING_TABLE_NAME,
- ]);
+ /// Set up policy-based routing for marked packets.
+ fn initialize_routing_table() -> Result<(), Error> {
+ // TODO: ensure the ID does not conflict with that of another table
- log::trace!("running cmd - {:?}", &cmd);
- cmd.output().map(|_| ()).map_err(Error::RoutingTableSetup)
-}
+ // Add routing table to /etc/iproute2/rt_tables, if it does not exist
-/// Route PID-associated packets through the physical interface.
-pub fn route_marked_packets() -> Result<(), Error> {
- // TODO: IPv6
+ let mut file = fs::OpenOptions::new()
+ .read(true)
+ .append(true)
+ .create(true)
+ .open(RT_TABLES_PATH)
+ .map_err(Error::RoutingTableSetup)?;
+ let buf_reader = BufReader::new(file.try_clone().map_err(Error::RoutingTableSetup)?);
+ let expression = Regex::new(r"^\s*(\d+)\s+(\w+)").unwrap();
- // Create the rule if it does not exist
- let mut cmd = Command::new("ip");
- cmd.args(&["-4", "rule", "list", "table", ROUTING_TABLE_NAME]);
- log::trace!("running cmd - {:?}", &cmd);
- let out = cmd.output().map_err(Error::RoutingTableSetup)?;
- let out = if !out.status.success() {
- ""
- } else {
- std::str::from_utf8(&out.stdout)
- .map_err(|_| {
- Error::RoutingTableSetup(io::Error::new(
- io::ErrorKind::InvalidData,
- "Error parsing ip output",
- ))
- })?
- .trim()
- };
+ for line in buf_reader.lines() {
+ let line = line.map_err(Error::RoutingTableSetup)?;
+ if let Some(captures) = expression.captures(&line) {
+ let table_id = captures
+ .get(1)
+ .unwrap()
+ .as_str()
+ .parse::<i32>()
+ .expect("Table ID does not fit i32");
+ let table_name = captures.get(2).unwrap().as_str();
+
+ // Already added
+ if table_name == ROUTING_TABLE_NAME {
+ if table_id != unsafe { ROUTING_TABLE_ID } {
+ unsafe { ROUTING_TABLE_ID = table_id };
+ }
+
+ return Ok(());
+ }
+ }
+ }
+
+ write!(
+ file,
+ "{} {}",
+ unsafe { ROUTING_TABLE_ID },
+ ROUTING_TABLE_NAME
+ )
+ .map_err(Error::RoutingTableSetup)
+ }
+
+ /// Reset the split-tunneling routing table to its default state
+ fn reset_table() -> Result<(), Error> {
+ let mut cmd = Command::new("ip");
+ cmd.args(&["-4", "route", "flush", "table", ROUTING_TABLE_NAME]);
+
+ log::trace!("running cmd - {:?}", &cmd);
+ cmd.output().map_err(Error::RoutingTableSetup)?;
- if out == "" {
+ // Force routing through the physical interface
+ let default_route = get_default_route()?;
let mut cmd = Command::new("ip");
cmd.args(&[
"-4",
- "rule",
+ "route",
"add",
- "from",
- "all",
- "fwmark",
- &MARK.to_string(),
- "lookup",
+ "default",
+ "via",
+ &default_route.address.to_string(),
+ "dev",
+ &default_route.interface,
+ "table",
ROUTING_TABLE_NAME,
]);
log::trace!("running cmd - {:?}", &cmd);
- cmd.output().map_err(Error::RoutingTableSetup)?;
+ cmd.output().map(|_| ()).map_err(Error::RoutingTableSetup)
}
- reset_table()
-}
+ /// Route PID-associated packets through the physical interface.
+ pub fn enable_routing(&self) -> Result<(), Error> {
+ // TODO: IPv6
-/// Stop routing PID-associated packets through the physical interface.
-pub fn disable_routing() -> Result<(), Error> {
- // TODO: IPv6
-
- let mut cmd = Command::new("ip");
- cmd.args(&[
- "-4",
- "rule",
- "del",
- "from",
- "all",
- "fwmark",
- &MARK.to_string(),
- "lookup",
- ROUTING_TABLE_NAME,
- ]);
-
- log::trace!("running cmd - {:?}", &cmd);
- let out = cmd.output();
- if out.is_err() {
- log::warn!("Failed to delete routing policy: {}", out.err().unwrap());
- } else {
- let out = out.unwrap();
- if !out.status.success() {
- log::warn!(
- "Failed to delete routing policy: {}",
- String::from_utf8_lossy(&out.stderr)
- );
- }
- }
-
- Ok(())
-}
-
-/// Route DNS requests through the tunnel interface.
-pub fn route_dns(tunnel_alias: &str, dns_servers: &[IpAddr]) -> Result<(), Error> {
- reset_table()?;
-
- for server in dns_servers {
- if let IpAddr::V4(addr) = server {
- let addr = addr.to_string();
+ // Create the rule if it does not exist
+ let mut cmd = Command::new("ip");
+ cmd.args(&["-4", "rule", "list", "table", ROUTING_TABLE_NAME]);
+ log::trace!("running cmd - {:?}", &cmd);
+ let out = cmd.output().map_err(Error::RoutingTableSetup)?;
+ let out = if !out.status.success() {
+ ""
+ } else {
+ std::str::from_utf8(&out.stdout)
+ .map_err(|_| {
+ Error::RoutingTableSetup(io::Error::new(
+ io::ErrorKind::InvalidData,
+ "Error parsing ip output",
+ ))
+ })?
+ .trim()
+ };
+ if out == "" {
let mut cmd = Command::new("ip");
cmd.args(&[
"-4",
- "route",
+ "rule",
"add",
- &addr,
- "via",
- &addr,
- "dev",
- tunnel_alias,
- "table",
+ "from",
+ "all",
+ "fwmark",
+ &MARK.to_string(),
+ "lookup",
ROUTING_TABLE_NAME,
]);
log::trace!("running cmd - {:?}", &cmd);
- cmd.output().map_err(Error::SetDns)?;
+ cmd.output().map_err(Error::RoutingTableSetup)?;
}
+
+ Self::reset_table()
}
- Ok(())
-}
+ /// Stop routing PID-associated packets through the physical interface.
+ pub fn disable_routing(&self) -> Result<(), Error> {
+ // TODO: IPv6
-/// Reset DNS rules.
-pub fn flush_dns() -> Result<(), Error> {
- // For now, simply flush it
- reset_table()
-}
+ let mut cmd = Command::new("ip");
+ cmd.args(&[
+ "-4",
+ "rule",
+ "del",
+ "from",
+ "all",
+ "fwmark",
+ &MARK.to_string(),
+ "lookup",
+ ROUTING_TABLE_NAME,
+ ]);
-/// Set up policy-based routing for marked packets.
-pub fn initialize_routing_table() -> Result<(), Error> {
- // TODO: ensure the ID does not conflict with that of another table
+ log::trace!("running cmd - {:?}", &cmd);
+ let out = cmd.output();
+ if out.is_err() {
+ log::warn!("Failed to delete routing policy: {}", out.err().unwrap());
+ } else {
+ let out = out.unwrap();
+ if !out.status.success() {
+ log::warn!(
+ "Failed to delete routing policy: {}",
+ String::from_utf8_lossy(&out.stderr)
+ );
+ }
+ }
- // Add routing table to /etc/iproute2/rt_tables, if it does not exist
+ Ok(())
+ }
- let mut file = fs::OpenOptions::new()
- .read(true)
- .append(true)
- .create(true)
- .open(RT_TABLES_PATH)
- .map_err(Error::RoutingTableSetup)?;
- let buf_reader = BufReader::new(file.try_clone().map_err(Error::RoutingTableSetup)?);
- let expression = Regex::new(r"^\s*(\d+)\s+(\w+)").unwrap();
+ /// Route DNS requests through the tunnel interface.
+ pub fn route_dns(&self, tunnel_alias: &str, dns_servers: &[IpAddr]) -> Result<(), Error> {
+ for server in dns_servers {
+ if let IpAddr::V4(addr) = server {
+ let addr = addr.to_string();
- for line in buf_reader.lines() {
- let line = line.map_err(Error::RoutingTableSetup)?;
- if let Some(captures) = expression.captures(&line) {
- let table_id = captures
- .get(1)
- .unwrap()
- .as_str()
- .parse::<i32>()
- .expect("Table ID does not fit i32");
- let table_name = captures.get(2).unwrap().as_str();
+ let mut cmd = Command::new("ip");
+ cmd.args(&[
+ "-4",
+ "route",
+ "replace",
+ &addr,
+ "dev",
+ tunnel_alias,
+ "table",
+ ROUTING_TABLE_NAME,
+ ]);
- // Already added
- if table_name == ROUTING_TABLE_NAME {
- if table_id != unsafe { ROUTING_TABLE_ID } {
- unsafe { ROUTING_TABLE_ID = table_id };
- }
-
- return Ok(());
+ log::trace!("running cmd - {:?}", &cmd);
+ cmd.output().map_err(Error::SetDns)?;
}
}
+
+ Ok(())
}
- write!(
- file,
- "{} {}",
- unsafe { ROUTING_TABLE_ID },
- ROUTING_TABLE_NAME
- )
- .map_err(Error::RoutingTableSetup)
+ /// Reset DNS rules.
+ pub fn flush_dns(&self) -> Result<(), Error> {
+ // For now, simply flush it
+ Self::reset_table()
+ }
}
-/// Set up cgroup used to track PIDs for split tunneling.
-pub fn create_cgroup() -> Result<(), Error> {
- let exclusions_dir = Path::new(NETCLS_DIR).join(CGROUP_NAME);
+/// Manages PIDs to exclude from the tunnel.
+pub struct PidManager;
- if !exclusions_dir.exists() {
- fs::create_dir(exclusions_dir.clone()).map_err(Error::CreateCGroup)?;
+impl PidManager {
+ /// Create object to manage split-tunnel PIDs.
+ pub fn new() -> Result<PidManager, Error> {
+ Self::create_cgroup()?;
+ Ok(PidManager {})
}
- let classid_path = exclusions_dir.join("net_cls.classid");
- fs::write(classid_path, NETCLS_CLASSID.to_string().as_bytes()).map_err(Error::SetCGroupClassId)
-}
+ /// Set up cgroup used to track PIDs for split tunneling.
+ fn create_cgroup() -> Result<(), Error> {
+ let exclusions_dir = Path::new(NETCLS_DIR).join(CGROUP_NAME);
-/// Add a PID to exclude from the tunnel.
-pub fn add_pid(pid: i32) -> Result<(), Error> {
- let exclusions_path = Path::new(NETCLS_DIR).join(CGROUP_NAME).join("cgroup.procs");
+ if !exclusions_dir.exists() {
+ fs::create_dir(exclusions_dir.clone()).map_err(Error::CreateCGroup)?;
+ }
- let mut file = fs::OpenOptions::new()
- .write(true)
- .create(true)
- .open(exclusions_path)
- .map_err(Error::AddCGroupPid)?;
+ let classid_path = exclusions_dir.join("net_cls.classid");
+ fs::write(classid_path, NETCLS_CLASSID.to_string().as_bytes())
+ .map_err(Error::SetCGroupClassId)
+ }
- file.write_all(pid.to_string().as_bytes())
- .map_err(Error::AddCGroupPid)
-}
+ /// Add a PID to exclude from the tunnel.
+ pub fn add(&self, pid: i32) -> Result<(), Error> {
+ let exclusions_path = Path::new(NETCLS_DIR).join(CGROUP_NAME).join("cgroup.procs");
-/// Remove a PID from processes to exclude from the tunnel.
-pub fn remove_pid(pid: i32) -> Result<(), Error> {
- // FIXME: We remove PIDs from our cgroup here by adding
- // them to the parent cgroup. This seems wrong.
- let exclusions_path = Path::new(NETCLS_DIR).join("cgroup.procs");
+ let mut file = fs::OpenOptions::new()
+ .write(true)
+ .create(true)
+ .open(exclusions_path)
+ .map_err(Error::AddCGroupPid)?;
- let mut file = fs::OpenOptions::new()
- .write(true)
- .create(true)
- .open(exclusions_path)
- .map_err(Error::RemoveCGroupPid)?;
+ file.write_all(pid.to_string().as_bytes())
+ .map_err(Error::AddCGroupPid)
+ }
- file.write_all(pid.to_string().as_bytes())
- .map_err(Error::RemoveCGroupPid)
-}
+ /// Remove a PID from processes to exclude from the tunnel.
+ pub fn remove(&self, pid: i32) -> Result<(), Error> {
+ // FIXME: We remove PIDs from our cgroup here by adding
+ // them to the parent cgroup. This seems wrong.
+ let exclusions_path = Path::new(NETCLS_DIR).join(CGROUP_NAME).join("cgroup.procs");
-/// Return a list of PIDs that are excluded from the tunnel.
-pub fn list_pids() -> Result<Vec<i32>, Error> {
- let exclusions_path = Path::new(NETCLS_DIR).join(CGROUP_NAME).join("cgroup.procs");
+ let mut file = fs::OpenOptions::new()
+ .write(true)
+ .create(true)
+ .open(exclusions_path)
+ .map_err(Error::RemoveCGroupPid)?;
- let file = fs::File::open(exclusions_path).map_err(Error::ListCGroupPids)?;
+ file.write_all(pid.to_string().as_bytes())
+ .map_err(Error::RemoveCGroupPid)
+ }
- let result: Result<Vec<i32>, io::Error> = BufReader::new(file)
- .lines()
- .map(|line| {
- line.and_then(|v| {
- v.parse()
- .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
- })
- })
- .collect();
- result.map_err(Error::ListCGroupPids)
-}
+ /// Return a list of PIDs that are excluded from the tunnel.
+ pub fn list(&self) -> Result<Vec<i32>, Error> {
+ // TODO: manage child PIDs somehow?
-/// Clear list of PIDs to exclude from the tunnel.
-pub fn clear_pids() -> Result<(), Error> {
- // TODO: reuse file handle
- let pids = list_pids()?;
+ let exclusions_path = Path::new(NETCLS_DIR).join(CGROUP_NAME).join("cgroup.procs");
- for pid in pids {
- remove_pid(pid)?;
+ let file = fs::File::open(exclusions_path).map_err(Error::ListCGroupPids)?;
+
+ let result: Result<Vec<i32>, io::Error> = BufReader::new(file)
+ .lines()
+ .map(|line| {
+ line.and_then(|v| {
+ v.parse()
+ .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
+ })
+ })
+ .collect();
+ result.map_err(Error::ListCGroupPids)
}
- Ok(())
+ /// Clear list of PIDs to exclude from the tunnel.
+ pub fn clear(&self) -> Result<(), Error> {
+ // TODO: reuse file handle
+ let pids = self.list()?;
+
+ for pid in pids {
+ self.remove(pid)?;
+ }
+
+ Ok(())
+ }
}
diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs
index e936499f47..55c4a28697 100644
--- a/talpid-core/src/tunnel_state_machine/connected_state.rs
+++ b/talpid-core/src/tunnel_state_machine/connected_state.rs
@@ -4,7 +4,6 @@ use super::{
};
use crate::{
firewall::FirewallPolicy,
- split,
tunnel::{CloseHandle, TunnelEvent, TunnelMetadata},
};
use futures01::{
@@ -81,11 +80,14 @@ impl ConnectedState {
.set(&self.metadata.interface, &dns_ips)
.map_err(BoxedError::new)?;
- split::route_dns(&self.metadata.interface, &dns_ips).map_err(BoxedError::new)
+ shared_values
+ .split_tunnel
+ .route_dns(&self.metadata.interface, &dns_ips)
+ .map_err(BoxedError::new)
}
fn reset_dns(shared_values: &mut SharedTunnelStateValues) {
- if let Err(error) = split::flush_dns() {
+ if let Err(error) = shared_values.split_tunnel.flush_dns() {
log::error!(
"{}",
error.display_chain_with_msg("Unable to update split-tunnel route")
@@ -243,10 +245,7 @@ impl TunnelState for ConnectedState {
),
)
} else if let Err(error) = connected_state.set_dns(shared_values) {
- log::error!(
- "{}",
- error.display_chain_with_msg("Failed to set system DNS settings")
- );
+ log::error!("{}", error.display_chain_with_msg("Failed to set DNS"));
DisconnectingState::enter(
shared_values,
(
diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs
index e43f9203fe..da61626de1 100644
--- a/talpid-core/src/tunnel_state_machine/connecting_state.rs
+++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs
@@ -358,7 +358,7 @@ impl TunnelState for ConnectingState {
)
);
ErrorState::enter(shared_values, ErrorStateCause::StartTunnelError)
- } else if let Err(error) = split::route_marked_packets() {
+ } else if let Err(error) = shared_values.split_tunnel.enable_routing() {
error!(
"{}",
error.display_chain_with_msg("Failed to set up split tunneling")
diff --git a/talpid-core/src/tunnel_state_machine/disconnected_state.rs b/talpid-core/src/tunnel_state_machine/disconnected_state.rs
index 8277f89013..7742f276ad 100644
--- a/talpid-core/src/tunnel_state_machine/disconnected_state.rs
+++ b/talpid-core/src/tunnel_state_machine/disconnected_state.rs
@@ -2,7 +2,7 @@ use super::{
ConnectingState, ErrorState, EventConsequence, SharedTunnelStateValues, TunnelCommand,
TunnelState, TunnelStateTransition, TunnelStateWrapper,
};
-use crate::{firewall::FirewallPolicy, split};
+use crate::firewall::FirewallPolicy;
use futures01::{sync::mpsc, Stream};
use talpid_types::ErrorExt;
@@ -39,7 +39,7 @@ impl TunnelState for DisconnectedState {
shared_values: &mut SharedTunnelStateValues,
_: Self::Bootstrap,
) -> (TunnelStateWrapper, TunnelStateTransition) {
- if let Err(error) = split::disable_routing() {
+ if let Err(error) = shared_values.split_tunnel.disable_routing() {
log::error!(
"{}",
error.display_chain_with_msg("Failed to update routing")
diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs
index f1aae86017..3515a72529 100644
--- a/talpid-core/src/tunnel_state_machine/mod.rs
+++ b/talpid-core/src/tunnel_state_machine/mod.rs
@@ -242,10 +242,7 @@ impl TunnelStateMachine {
};
#[cfg(unix)]
- {
- split::initialize_routing_table().map_err(Error::InitSplitTunneling)?;
- split::create_cgroup().map_err(Error::InitSplitTunneling)?;
- }
+ let split_tunnel = split::SplitTunnel::new().map_err(Error::InitSplitTunneling)?;
let firewall = Firewall::new(args).map_err(Error::InitFirewallError)?;
let dns_monitor = DnsMonitor::new(cache_dir).map_err(Error::InitDnsMonitorError)?;
@@ -253,6 +250,8 @@ impl TunnelStateMachine {
RouteManager::new(HashSet::new()).map_err(Error::InitRouteManagerError)?;
let mut shared_values = SharedTunnelStateValues {
firewall,
+ #[cfg(unix)]
+ split_tunnel,
dns_monitor,
route_manager,
allow_lan,
@@ -337,6 +336,7 @@ pub trait TunnelParametersGenerator: Send + 'static {
/// Values that are common to all tunnel states.
struct SharedTunnelStateValues {
firewall: Firewall,
+ split_tunnel: split::SplitTunnel,
dns_monitor: DnsMonitor,
route_manager: RouteManager,
/// Should LAN access be allowed outside the tunnel.