summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorLinus Färnstrand <linus@mullvad.net>2018-07-02 15:01:33 +0200
committerLinus Färnstrand <linus@mullvad.net>2018-07-02 15:01:33 +0200
commitb559a99f5d171bd4c271741dda675e6eb9baa5bc (patch)
tree4c64b0fd767affcb926fa6b649795ea5a33b34b3
parentf65edff37fc2ca3f14c2e8e9cb840c9fa57e4752 (diff)
parentb7823231c8b59a70745f1d38d7f0c97ccfc1b366 (diff)
downloadmullvadvpn-b559a99f5d171bd4c271741dda675e6eb9baa5bc.tar.xz
mullvadvpn-b559a99f5d171bd4c271741dda675e6eb9baa5bc.zip
Merge branch 'linux-firewall-integration'
-rw-r--r--.travis.yml2
-rw-r--r--Cargo.lock48
-rw-r--r--README.md6
-rwxr-xr-xbuild.sh7
m---------dist-assets/binaries0
-rw-r--r--talpid-core/Cargo.toml12
-rw-r--r--talpid-core/src/firewall/linux/mod.rs437
-rw-r--r--talpid-core/src/firewall/macos/mod.rs27
-rw-r--r--talpid-core/src/firewall/mod.rs14
-rw-r--r--talpid-core/src/lib.rs11
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"
diff --git a/README.md b/README.md
index 1bd128bdcc..1828525206 100644
--- a/README.md
+++ b/README.md
@@ -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:
diff --git a/build.sh b/build.sh
index bb86e5c2cb..47f5e66bbe 100755
--- a/build.sh
+++ b/build.sh
@@ -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;