diff options
| author | Linus Färnstrand <linus@mullvad.net> | 2018-07-02 15:01:33 +0200 |
|---|---|---|
| committer | Linus Färnstrand <linus@mullvad.net> | 2018-07-02 15:01:33 +0200 |
| commit | b559a99f5d171bd4c271741dda675e6eb9baa5bc (patch) | |
| tree | 4c64b0fd767affcb926fa6b649795ea5a33b34b3 | |
| parent | f65edff37fc2ca3f14c2e8e9cb840c9fa57e4752 (diff) | |
| parent | b7823231c8b59a70745f1d38d7f0c97ccfc1b366 (diff) | |
| download | mullvadvpn-b559a99f5d171bd4c271741dda675e6eb9baa5bc.tar.xz mullvadvpn-b559a99f5d171bd4c271741dda675e6eb9baa5bc.zip | |
Merge branch 'linux-firewall-integration'
| -rw-r--r-- | .travis.yml | 2 | ||||
| -rw-r--r-- | Cargo.lock | 48 | ||||
| -rw-r--r-- | README.md | 6 | ||||
| -rwxr-xr-x | build.sh | 7 | ||||
| m--------- | dist-assets/binaries | 0 | ||||
| -rw-r--r-- | talpid-core/Cargo.toml | 12 | ||||
| -rw-r--r-- | talpid-core/src/firewall/linux/mod.rs | 437 | ||||
| -rw-r--r-- | talpid-core/src/firewall/macos/mod.rs | 27 | ||||
| -rw-r--r-- | talpid-core/src/firewall/mod.rs | 14 | ||||
| -rw-r--r-- | talpid-core/src/lib.rs | 11 |
10 files changed, 538 insertions, 26 deletions
diff --git a/.travis.yml b/.travis.yml index be815c7db0..3a4dcdf3a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,8 @@ sudo: false env: global: - MACOSX_DEPLOYMENT_TARGET="10.7" + - LIBMNL_LIB_DIR="$TRAVIS_BUILD_DIR/dist-assets/binaries/linux" + - LIBNFTNL_LIB_DIR="$TRAVIS_BUILD_DIR/dist-assets/binaries/linux" git: submodules: false diff --git a/Cargo.lock b/Cargo.lock index 10fa000d1a..3991661529 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -745,6 +745,25 @@ dependencies = [ ] [[package]] +name = "mnl" +version = "0.1.0" +source = "git+https://github.com/mullvad/mnl-rs#8ceadc9e4a43830cc78eb701453d44271b8628fe" +dependencies = [ + "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "mnl-sys 0.1.0 (git+https://github.com/mullvad/mnl-rs)", +] + +[[package]] +name = "mnl-sys" +version = "0.1.0" +source = "git+https://github.com/mullvad/mnl-rs#8ceadc9e4a43830cc78eb701453d44271b8628fe" +dependencies = [ + "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "mullvad-cli" version = "2018.1.0" dependencies = [ @@ -892,6 +911,27 @@ dependencies = [ ] [[package]] +name = "nftnl" +version = "0.1.0" +source = "git+https://github.com/mullvad/nftnl-rs#2ed837ac50b8a4fb94988d5b62527a347a3307ef" +dependencies = [ + "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "nftnl-sys 0.1.0 (git+https://github.com/mullvad/nftnl-rs)", +] + +[[package]] +name = "nftnl-sys" +version = "0.1.0" +source = "git+https://github.com/mullvad/nftnl-rs#2ed837ac50b8a4fb94988d5b62527a347a3307ef" +dependencies = [ + "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "nix" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1405,10 +1445,14 @@ dependencies = [ "core-foundation 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "duct 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ipnetwork 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 8.0.1 (git+https://github.com/paritytech/jsonrpc?tag=v8.0.1)", "jsonrpc-macros 8.0.0 (git+https://github.com/paritytech/jsonrpc?tag=v8.0.1)", + "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "mnl 0.1.0 (git+https://github.com/mullvad/mnl-rs)", + "nftnl 0.1.0 (git+https://github.com/mullvad/nftnl-rs)", "notify 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "openvpn-plugin 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "os_pipe 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1967,8 +2011,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)" = "6d771e3ef92d58a8da8df7d6976bfca9371ed1de6619d9d5a5ce5b1f29b85bfe" "checksum miow 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3e690c5df6b2f60acd45d56378981e827ff8295562fc8d34f573deb267a59cd1" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +"checksum mnl 0.1.0 (git+https://github.com/mullvad/mnl-rs)" = "<none>" +"checksum mnl-sys 0.1.0 (git+https://github.com/mullvad/mnl-rs)" = "<none>" "checksum native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f74dbadc8b43df7864539cedb7bc91345e532fdd913cfdc23ad94f4d2d40fbc0" "checksum net2 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)" = "9044faf1413a1057267be51b5afba8eb1090bd2231c693664aa1db716fe1eae0" +"checksum nftnl 0.1.0 (git+https://github.com/mullvad/nftnl-rs)" = "<none>" +"checksum nftnl-sys 0.1.0 (git+https://github.com/mullvad/nftnl-rs)" = "<none>" "checksum nix 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bfb3ddedaa14746434a02041940495bf11325c22f6d36125d3bdd56090d50a79" "checksum nix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2c5afeb0198ec7be8569d666644b574345aad2e95a53baf3a532da3e0f3fb32" "checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" @@ -93,6 +93,12 @@ homebrew: It must run as root since it it modifies the firewall and sets up virtual network interfaces etc. +### Environment variables controlling the execution + +* `TALPID_NFTABLES_COUNTERS` - Set to `"1"` to add packet counters to all firewall rules on + Linux. + + ## Building and running the Electron GUI app 1. Install all the JavaScript dependencies by running: @@ -7,13 +7,17 @@ set -eu +SCRIPT_DIR="$( cd "$(dirname "$0")" ; pwd -P )" + ################################################################################ # Platform specific configuration. ################################################################################ case "$(uname -s)" in Linux*) - # config + # Use static builds of libmnl and libnftnl from the binaries submodule + export LIBMNL_LIB_DIR="$SCRIPT_DIR/dist-assets/binaries/linux" + export LIBNFTNL_LIB_DIR="$SCRIPT_DIR/dist-assets/binaries/linux" ;; Darwin*) export MACOSX_DEPLOYMENT_TARGET="10.7" @@ -68,7 +72,6 @@ if [[ "${1:-""}" != "--dev-build" ]]; then fi cargo +stable clean - else echo "!! Development build. Not for general distribution !!" GIT_COMMIT=$(git rev-parse --short HEAD) diff --git a/dist-assets/binaries b/dist-assets/binaries -Subproject 29d6d19ee6ea8ada8a61dc67b79343fed9a0cb8 +Subproject 6e287421c2c0908091d7e1e47b6130a9b1f0558 diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml index 16de588b31..1040ebb007 100644 --- a/talpid-core/Cargo.toml +++ b/talpid-core/Cargo.toml @@ -11,18 +11,25 @@ duct = "0.10" error-chain = "0.12" jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc", tag = "v8.0.1" } jsonrpc-macros = { git = "https://github.com/paritytech/jsonrpc", tag = "v8.0.1" } +libc = "0.2.20" log = "0.4" +openvpn-plugin = { version = "0.3", features = ["serde"] } os_pipe = "0.6" -uuid = { version = "0.6", features = ["v4"] } shell-escape = "0.1" +uuid = { version = "0.6", features = ["v4"] } -openvpn-plugin = { version = "0.3", features = ["serde"] } talpid-ipc = { path = "../talpid-ipc" } talpid-types = { path = "../talpid-types" } +[target.'cfg(unix)'.dependencies] +ipnetwork = "0.13" +lazy_static = "1.0" + [target.'cfg(target_os = "linux")'.dependencies] notify = "4.0" resolv-conf = "0.6.1" +nftnl = { git = "https://github.com/mullvad/nftnl-rs", features = ["nftnl-1-1-0"] } +mnl = { git = "https://github.com/mullvad/mnl-rs", features = ["mnl-1-0-4"] } [target.'cfg(target_os = "macos")'.dependencies] pfctl = "0.2" @@ -31,7 +38,6 @@ core-foundation = "0.5" tokio-core = "0.1" [target.'cfg(windows)'.dependencies] -libc = "0.2.20" widestring = "0.3" [dev-dependencies] diff --git a/talpid-core/src/firewall/linux/mod.rs b/talpid-core/src/firewall/linux/mod.rs index 7761afd7e9..25e38f99cc 100644 --- a/talpid-core/src/firewall/linux/mod.rs +++ b/talpid-core/src/firewall/linux/mod.rs @@ -1,21 +1,80 @@ +extern crate mnl; + use error_chain::ChainedError; + +use ipnetwork::IpNetwork; +use libc; +use nftnl::{ + self, + expr::{self, Verdict}, + Batch, Chain, FinalizedBatch, ProtoFamily, Rule, Table, +}; +use talpid_types::net::{Endpoint, TransportProtocol}; +use tunnel; + +use std::env; +use std::ffi::CString; +use std::io; +use std::net::{IpAddr, Ipv4Addr}; use std::path::Path; use super::{Firewall, SecurityPolicy}; mod dns; - use self::dns::DnsSettings; error_chain! { + errors { + /// Unable to open netlink socket to netfilter + NetlinkOpenError { description("Unable to open netlink socket to netfilter") } + /// Unable to send netlink command to netfilter + NetlinkSendError { description("Unable to send netlink command to netfilter") } + /// Error while reading from netlink socket + NetlinkRecvError { description("Error while reading from netlink socket") } + /// Error while processing an incoming netlink message + ProcessNetlinkError { description("Error while processing an incoming netlink message") } + /// The name is not a valid Linux network interface name + InvalidInterfaceName(name: String) { + description("Invalid network interface name") + display("Invalid network interface name: {}", name) + } + } links { DnsSettings(self::dns::Error, self::dns::ErrorKind) #[doc = "DNS error"]; + Nftnl(nftnl::Error, nftnl::ErrorKind) #[doc = "Error in nftnl"]; } } +lazy_static! { + /// TODO(linus): This crate is not supposed to be Mullvad-aware. So at some point this should be + /// replaced by allowing the table name to be configured from the public API of this crate. + static ref TABLE_NAME: CString = CString::new("mullvad").unwrap(); + static ref IN_CHAIN_NAME: CString = CString::new("in").unwrap(); + static ref OUT_CHAIN_NAME: CString = CString::new("out").unwrap(); + + /// Allows controlling whether firewall rules should have packet counters or not from an env + /// variable. Useful for debugging the rules. + static ref ADD_COUNTERS: bool = env::var("TALPID_NFTABLES_COUNTERS") + .map(|v| v == "1") + .unwrap_or(false); +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +enum Direction { + In, + Out, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +enum End { + Src, + Dst, +} + /// The Linux implementation for the `Firewall` trait. pub struct Netfilter { dns_settings: DnsSettings, + table: Table, } impl Firewall for Netfilter { @@ -24,25 +83,387 @@ impl Firewall for Netfilter { fn new<P: AsRef<Path>>(_cache_dir: P) -> Result<Self> { Ok(Netfilter { dns_settings: DnsSettings::new()?, + table: Table::new(&*TABLE_NAME, ProtoFamily::Inet)?, }) } fn apply_policy(&mut self, policy: SecurityPolicy) -> Result<()> { - match policy { - SecurityPolicy::Connected { tunnel, .. } => { - self.dns_settings.set_dns(vec![tunnel.gateway.into()])?; - } - _ => (), + if let SecurityPolicy::Connected { ref tunnel, .. } = policy { + self.dns_settings.set_dns(vec![tunnel.gateway.into()])?; } - Ok(()) + let batch = PolicyBatch::new(&self.table)?.finalize(&policy)?; + self.send_and_process(&batch) } fn reset_policy(&mut self) -> Result<()> { if let Err(error) = self.dns_settings.reset() { - warn!("Failed to reset DNS settings: {}", error.display_chain()); + error!("Failed to reset DNS settings: {}", error.display_chain()); + } + + let batch = { + let mut batch = Batch::new()?; + // Our batch will add and remove the table even though the goal is just to remove it. + // This because only removing it throws a strange error if the table does not exist. + batch.add(&self.table, nftnl::MsgType::Add)?; + batch.add(&self.table, nftnl::MsgType::Del)?; + batch.finalize()? + }; + + debug!("Removing table and chain from netfilter"); + self.send_and_process(&batch) + } +} + +impl Netfilter { + fn send_and_process(&self, batch: &FinalizedBatch) -> Result<()> { + let socket = + mnl::Socket::new(mnl::Bus::Netfilter).chain_err(|| ErrorKind::NetlinkOpenError)?; + socket + .send_all(batch) + .chain_err(|| ErrorKind::NetlinkSendError)?; + + let portid = socket.portid(); + let mut buffer = vec![0; nftnl::nft_nlmsg_maxsize() as usize]; + + + while let Some(message) = Self::socket_recv(&socket, &mut buffer[..])? { + match mnl::cb_run(message, 2, portid).chain_err(|| ErrorKind::ProcessNetlinkError)? { + mnl::CbResult::Stop => { + trace!("cb_run STOP"); + break; + } + mnl::CbResult::Ok => trace!("cb_run OK"), + } } Ok(()) } + + fn socket_recv<'a>(socket: &mnl::Socket, buf: &'a mut [u8]) -> Result<Option<&'a [u8]>> { + let ret = socket.recv(buf).chain_err(|| ErrorKind::NetlinkRecvError)?; + trace!("Read {} bytes from netlink", ret); + if ret > 0 { + Ok(Some(&buf[..ret])) + } else { + Ok(None) + } + } +} + +struct PolicyBatch<'a> { + batch: Batch, + in_chain: Chain<'a>, + out_chain: Chain<'a>, +} + +impl<'a> PolicyBatch<'a> { + /// Bootstrap a new nftnl message batch object and add the initial messages creating the + /// table and chains. + pub fn new(table: &'a Table) -> Result<Self> { + let mut batch = Batch::new()?; + let mut out_chain = Chain::new(&*OUT_CHAIN_NAME, table)?; + let mut in_chain = Chain::new(&*IN_CHAIN_NAME, table)?; + out_chain.set_hook(nftnl::Hook::Out, 0); + in_chain.set_hook(nftnl::Hook::In, 0); + out_chain.set_policy(nftnl::Policy::Drop); + in_chain.set_policy(nftnl::Policy::Drop); + + batch.add(table, nftnl::MsgType::Add)?; + batch.add(table, nftnl::MsgType::Del)?; + batch.add(table, nftnl::MsgType::Add)?; + batch.add(&out_chain, nftnl::MsgType::Add)?; + batch.add(&in_chain, nftnl::MsgType::Add)?; + + Ok(PolicyBatch { + batch, + in_chain, + out_chain, + }) + } + + /// Finalize the nftnl message batch by adding every firewall rule needed to satisfy the given + /// policy. + pub fn finalize(mut self, policy: &SecurityPolicy) -> Result<FinalizedBatch> { + self.add_loopback_rules()?; + self.add_dhcp_rules()?; + self.add_policy_specific_rules(policy)?; + + Ok(self.batch.finalize()?) + } + + fn add_loopback_rules(&mut self) -> Result<()> { + const LOOPBACK_IFACE_NAME: &str = "lo"; + self.batch.add( + &allow_interface_rule(&self.out_chain, Direction::Out, LOOPBACK_IFACE_NAME)?, + nftnl::MsgType::Add, + )?; + self.batch.add( + &allow_interface_rule(&self.in_chain, Direction::In, LOOPBACK_IFACE_NAME)?, + nftnl::MsgType::Add, + )?; + Ok(()) + } + + fn add_dhcp_rules(&mut self) -> Result<()> { + self.batch.add( + &allow_dhcp_rule(&self.out_chain, Direction::Out)?, + nftnl::MsgType::Add, + )?; + self.batch.add( + &allow_dhcp_rule(&self.in_chain, Direction::In)?, + nftnl::MsgType::Add, + )?; + Ok(()) + } + + fn add_policy_specific_rules(&mut self, policy: &SecurityPolicy) -> Result<()> { + let (relay_endpoint, allow_lan, tunnel) = match policy { + SecurityPolicy::Connecting { + relay_endpoint, + allow_lan, + } => (relay_endpoint, *allow_lan, None), + SecurityPolicy::Connected { + relay_endpoint, + tunnel, + allow_lan, + } => (relay_endpoint, *allow_lan, Some(tunnel)), + }; + + self.add_allow_endpoint_rules(relay_endpoint)?; + if let Some(tunnel) = tunnel { + self.add_dns_rule(tunnel, TransportProtocol::Udp)?; + self.add_dns_rule(tunnel, TransportProtocol::Tcp)?; + self.add_allow_tunnel_rules(tunnel)?; + } + if allow_lan { + self.add_allow_lan_rules()?; + } + Ok(()) + } + + fn add_allow_endpoint_rules(&mut self, endpoint: &Endpoint) -> Result<()> { + let mut in_rule = Rule::new(&self.in_chain)?; + check_endpoint(&mut in_rule, End::Src, endpoint)?; + + in_rule.add_expr(nft_expr!(ct state))?; + let allowed_states = nftnl::expr::ct::States::ESTABLISHED.bits(); + in_rule.add_expr(nft_expr!(bitwise mask allowed_states, xor 0u32))?; + in_rule.add_expr(nft_expr!(cmp != 0u32))?; + add_verdict(&mut in_rule, Verdict::Accept)?; + + self.batch.add(&in_rule, nftnl::MsgType::Add)?; + + + let mut out_rule = Rule::new(&self.out_chain)?; + check_endpoint(&mut out_rule, End::Dst, endpoint)?; + add_verdict(&mut out_rule, Verdict::Accept)?; + + self.batch.add(&out_rule, nftnl::MsgType::Add)?; + + Ok(()) + } + + fn add_dns_rule( + &mut self, + tunnel: &tunnel::TunnelMetadata, + protocol: TransportProtocol, + ) -> Result<()> { + let mut rule = Rule::new(&self.out_chain)?; + + check_iface(&mut rule, Direction::Out, &tunnel.interface[..])?; + check_port(&mut rule, protocol, End::Dst, 53)?; + check_l3proto(&mut rule, IpAddr::V4(tunnel.gateway))?; + + rule.add_expr(nft_expr!(payload ipv4 daddr))?; + rule.add_expr(nft_expr!(cmp != tunnel.gateway))?; + + add_verdict(&mut rule, Verdict::Drop)?; + + self.batch.add(&rule, nftnl::MsgType::Add)?; + Ok(()) + } + + fn add_allow_tunnel_rules(&mut self, tunnel: &tunnel::TunnelMetadata) -> Result<()> { + self.batch.add( + &allow_interface_rule(&self.out_chain, Direction::Out, &tunnel.interface[..])?, + nftnl::MsgType::Add, + )?; + self.batch.add( + &allow_interface_rule(&self.in_chain, Direction::In, &tunnel.interface[..])?, + nftnl::MsgType::Add, + )?; + Ok(()) + } + + fn add_allow_lan_rules(&mut self) -> Result<()> { + // LAN -> LAN + for chain in &[&self.in_chain, &self.out_chain] { + for net in &*super::PRIVATE_NETS { + let mut rule = Rule::new(chain)?; + check_net(&mut rule, End::Src, IpNetwork::V4(*net))?; + check_net(&mut rule, End::Dst, IpNetwork::V4(*net))?; + + add_verdict(&mut rule, Verdict::Accept)?; + + self.batch.add(&rule, nftnl::MsgType::Add)?; + } + } + // LAN -> multicast + for net in &*super::PRIVATE_NETS { + let mut rule = Rule::new(&self.out_chain)?; + check_net(&mut rule, End::Src, IpNetwork::V4(*net))?; + check_net(&mut rule, End::Dst, IpNetwork::V4(*super::MULTICAST_NET))?; + + add_verdict(&mut rule, Verdict::Accept)?; + + self.batch.add(&rule, nftnl::MsgType::Add)?; + } + + Ok(()) + } +} + +fn allow_dhcp_rule<'a>(chain: &'a Chain, direction: Direction) -> Result<Rule<'a>> { + const SERVER_PORT: u16 = 67; + const CLIENT_PORT: u16 = 68; + let broadcast_addr = IpAddr::V4(Ipv4Addr::new(255, 255, 255, 255)); + + let mut rule = Rule::new(&chain)?; + + match direction { + Direction::In => { + check_port(&mut rule, TransportProtocol::Udp, End::Src, SERVER_PORT)?; + check_port(&mut rule, TransportProtocol::Udp, End::Dst, CLIENT_PORT)?; + } + Direction::Out => { + check_port(&mut rule, TransportProtocol::Udp, End::Src, CLIENT_PORT)?; + check_port(&mut rule, TransportProtocol::Udp, End::Dst, SERVER_PORT)?; + check_ip(&mut rule, End::Dst, broadcast_addr)?; + } + } + + add_verdict(&mut rule, Verdict::Accept)?; + + Ok(rule) +} + +fn allow_interface_rule<'a>( + chain: &'a Chain, + direction: Direction, + iface: &str, +) -> Result<Rule<'a>> { + let mut rule = Rule::new(&chain)?; + check_iface(&mut rule, direction, iface)?; + add_verdict(&mut rule, Verdict::Accept)?; + + Ok(rule) +} + +fn check_iface(rule: &mut Rule, direction: Direction, iface: &str) -> Result<()> { + let iface_index = iface_index(iface)?; + rule.add_expr(match direction { + Direction::In => nft_expr!(meta iif), + Direction::Out => nft_expr!(meta oif), + })?; + rule.add_expr(nft_expr!(cmp == iface_index))?; + Ok(()) +} + +fn iface_index(name: &str) -> Result<libc::c_uint> { + let c_name = CString::new(name).chain_err(|| ErrorKind::InvalidInterfaceName(name.to_owned()))?; + let index = unsafe { libc::if_nametoindex(c_name.as_ptr()) }; + if index == 0 { + let error = io::Error::last_os_error(); + Err(error).chain_err(|| ErrorKind::InvalidInterfaceName(name.to_owned())) + } else { + Ok(index) + } +} + +fn check_net(rule: &mut Rule, end: End, net: IpNetwork) -> Result<()> { + // Must check network layer protocol before loading network layer payload + check_l3proto(rule, net.ip())?; + + rule.add_expr(match (net, end) { + (IpNetwork::V4(_), End::Src) => nft_expr!(payload ipv4 saddr), + (IpNetwork::V4(_), End::Dst) => nft_expr!(payload ipv4 daddr), + (IpNetwork::V6(_), End::Src) => nft_expr!(payload ipv6 saddr), + (IpNetwork::V6(_), End::Dst) => nft_expr!(payload ipv6 daddr), + })?; + rule.add_expr(nft_expr!(bitwise mask net.mask(), xor 0))?; + rule.add_expr(nft_expr!(cmp == net.ip()))?; + + Ok(()) +} + +fn check_endpoint(rule: &mut Rule, end: End, endpoint: &Endpoint) -> Result<()> { + check_ip(rule, end, endpoint.address.ip())?; + check_port(rule, endpoint.protocol, end, endpoint.address.port())?; + Ok(()) +} + + +fn check_ip(rule: &mut Rule, end: End, ip: IpAddr) -> Result<()> { + // Must check network layer protocol before loading network layer payload + check_l3proto(rule, ip)?; + + rule.add_expr(match (ip, end) { + (IpAddr::V4(..), End::Src) => nft_expr!(payload ipv4 saddr), + (IpAddr::V4(..), End::Dst) => nft_expr!(payload ipv4 daddr), + (IpAddr::V6(..), End::Src) => nft_expr!(payload ipv6 saddr), + (IpAddr::V6(..), End::Dst) => nft_expr!(payload ipv6 daddr), + })?; + match ip { + IpAddr::V4(addr) => rule.add_expr(nft_expr!(cmp == addr))?, + IpAddr::V6(addr) => rule.add_expr(nft_expr!(cmp == addr))?, + } + Ok(()) +} + +fn check_port(rule: &mut Rule, protocol: TransportProtocol, end: End, port: u16) -> Result<()> { + // Must check transport layer protocol before loading transport layer payload + check_l4proto(rule, protocol)?; + + rule.add_expr(match (protocol, end) { + (TransportProtocol::Udp, End::Src) => nft_expr!(payload udp sport), + (TransportProtocol::Udp, End::Dst) => nft_expr!(payload udp dport), + (TransportProtocol::Tcp, End::Src) => nft_expr!(payload tcp sport), + (TransportProtocol::Tcp, End::Dst) => nft_expr!(payload tcp dport), + })?; + rule.add_expr(nft_expr!(cmp == port.to_be()))?; + Ok(()) +} + +fn check_l3proto(rule: &mut Rule, ip: IpAddr) -> Result<()> { + rule.add_expr(nft_expr!(meta nfproto))?; + rule.add_expr(nft_expr!(cmp == l3proto(ip)))?; + Ok(()) +} + +fn l3proto(addr: IpAddr) -> u8 { + match addr { + IpAddr::V4(_) => libc::NFPROTO_IPV4 as u8, + IpAddr::V6(_) => libc::NFPROTO_IPV6 as u8, + } +} + +fn check_l4proto(rule: &mut Rule, protocol: TransportProtocol) -> Result<()> { + rule.add_expr(nft_expr!(meta l4proto))?; + rule.add_expr(nft_expr!(cmp == l4proto(protocol)))?; + Ok(()) +} + +fn l4proto(protocol: TransportProtocol) -> u8 { + match protocol { + TransportProtocol::Udp => libc::IPPROTO_UDP as u8, + TransportProtocol::Tcp => libc::IPPROTO_TCP as u8, + } +} + +fn add_verdict(rule: &mut Rule, verdict: expr::Verdict) -> Result<()> { + if *ADD_COUNTERS { + rule.add_expr(nft_expr!(counter))?; + } + Ok(rule.add_expr(verdict)?) } diff --git a/talpid-core/src/firewall/macos/mod.rs b/talpid-core/src/firewall/macos/mod.rs index 394fd916d3..c4cb25f15b 100644 --- a/talpid-core/src/firewall/macos/mod.rs +++ b/talpid-core/src/firewall/macos/mod.rs @@ -1,9 +1,10 @@ extern crate pfctl; extern crate tokio_core; -use self::pfctl::ipnetwork::{IpNetwork, Ipv4Network}; use super::{Firewall, SecurityPolicy}; +use ipnetwork::IpNetwork; + use std::net::Ipv4Addr; use std::path::Path; @@ -20,6 +21,8 @@ error_chain! { } } +/// TODO(linus): This crate is not supposed to be Mullvad-aware. So at some point this should be +/// replaced by allowing the anchor name to be configured from the public API of this crate. const ANCHOR_NAME: &'static str = "mullvad"; /// The macOS firewall implementation. Acting as converter between the `Firewall` trait API @@ -183,25 +186,21 @@ impl PacketFilter { } fn get_allow_lan_rules() -> Result<Vec<pfctl::FilterRule>> { - let private_nets = [ - Ipv4Network::new(Ipv4Addr::new(10, 0, 0, 0), 8).unwrap(), - Ipv4Network::new(Ipv4Addr::new(172, 16, 0, 0), 12).unwrap(), - Ipv4Network::new(Ipv4Addr::new(192, 168, 0, 0), 16).unwrap(), - ]; - let multicast_net = Ipv4Network::new(Ipv4Addr::new(224, 0, 0, 0), 24).unwrap(); let mut rules = vec![]; - for net in &private_nets { + for net in &*super::PRIVATE_NETS { let mut rule_builder = pfctl::FilterRuleBuilder::default(); rule_builder .action(pfctl::FilterRuleAction::Pass) .quick(true) .af(pfctl::AddrFamily::Ipv4) - .from(pfctl::Ip::from(IpNetwork::V4(*net))); + .from(pfctl::Ip::from(ipnetwork_compat(IpNetwork::V4(*net)))); let allow_net = rule_builder - .to(pfctl::Ip::from(IpNetwork::V4(*net))) + .to(pfctl::Ip::from(ipnetwork_compat(IpNetwork::V4(*net)))) .build()?; let allow_multicast = rule_builder - .to(pfctl::Ip::from(IpNetwork::V4(multicast_net))) + .to(pfctl::Ip::from(ipnetwork_compat(IpNetwork::V4( + *super::MULTICAST_NET, + )))) .build()?; rules.push(allow_net); rules.push(allow_multicast); @@ -288,3 +287,9 @@ fn as_pfctl_proto(protocol: net::TransportProtocol) -> pfctl::Proto { net::TransportProtocol::Tcp => pfctl::Proto::Tcp, } } + +/// Converts a network from the struct version that talpid-core uses to the version pfctl uses. +fn ipnetwork_compat(net: ::ipnetwork::IpNetwork) -> pfctl::ipnetwork::IpNetwork { + pfctl::ipnetwork::IpNetwork::new(net.ip(), net.prefix()) + .expect("IpNetwork versions not compatible") +} diff --git a/talpid-core/src/firewall/mod.rs b/talpid-core/src/firewall/mod.rs index 2902e24ccd..3b5aa04703 100644 --- a/talpid-core/src/firewall/mod.rs +++ b/talpid-core/src/firewall/mod.rs @@ -1,6 +1,20 @@ +#[cfg(unix)] +use ipnetwork::Ipv4Network; +#[cfg(unix)] +use std::net::Ipv4Addr; use std::path::Path; use talpid_types::net::Endpoint; +#[cfg(unix)] +lazy_static! { + static ref PRIVATE_NETS: [Ipv4Network; 3] = [ + Ipv4Network::new(Ipv4Addr::new(10, 0, 0, 0), 8).unwrap(), + Ipv4Network::new(Ipv4Addr::new(172, 16, 0, 0), 12).unwrap(), + Ipv4Network::new(Ipv4Addr::new(192, 168, 0, 0), 16).unwrap(), + ]; + static ref MULTICAST_NET: Ipv4Network = + Ipv4Network::new(Ipv4Addr::new(224, 0, 0, 0), 24).unwrap(); +} /// A enum that describes firewall rules strategy #[derive(Debug, Clone, Eq, PartialEq)] diff --git a/talpid-core/src/lib.rs b/talpid-core/src/lib.rs index ea8e9dd890..fd26752fac 100644 --- a/talpid-core/src/lib.rs +++ b/talpid-core/src/lib.rs @@ -17,9 +17,15 @@ extern crate log; #[macro_use] extern crate error_chain; +#[cfg(unix)] +extern crate ipnetwork; extern crate jsonrpc_core; #[macro_use] extern crate jsonrpc_macros; +#[cfg(unix)] +#[macro_use] +extern crate lazy_static; +extern crate libc; extern crate shell_escape; extern crate uuid; @@ -27,8 +33,9 @@ extern crate openvpn_plugin; extern crate talpid_ipc; extern crate talpid_types; -#[cfg(windows)] -extern crate libc; +#[cfg(target_os = "linux")] +#[macro_use] +extern crate nftnl; /// Working with processes. pub mod process; |
