diff options
| author | Linus Färnstrand <linus@mullvad.net> | 2019-05-08 15:48:20 +0200 |
|---|---|---|
| committer | Linus Färnstrand <linus@mullvad.net> | 2019-05-08 15:48:20 +0200 |
| commit | 304695494a8e4a3f34fd9882db43b3305ca88503 (patch) | |
| tree | 4d1fdb28a373a3ab9c491300d89edd6aad26aea9 | |
| parent | 583f0c493cb5dc103ce28f13bea116bcde7bfa3c (diff) | |
| parent | e365175bc7822982fb819d0d05043aadaeb3ef2d (diff) | |
| download | mullvadvpn-304695494a8e4a3f34fd9882db43b3305ca88503.tar.xz mullvadvpn-304695494a8e4a3f34fd9882db43b3305ca88503.zip | |
Merge branch 'fix-ipv6-firewall-rules-ndp-multicast'
| -rw-r--r-- | CHANGELOG.md | 9 | ||||
| -rw-r--r-- | Cargo.lock | 17 | ||||
| -rw-r--r-- | talpid-core/Cargo.toml | 2 | ||||
| -rw-r--r-- | talpid-core/src/firewall/linux.rs | 144 | ||||
| -rw-r--r-- | talpid-core/src/firewall/macos.rs | 128 | ||||
| -rw-r--r-- | talpid-core/src/firewall/mod.rs | 72 |
6 files changed, 251 insertions, 121 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index f2127ff8cb..a1b5a83339 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,15 @@ Line wrap the file at 100 chars. Th ### Added #### Linux - Add standard window decorations to the application window. +- Allow a subset of NDP (Router solicitation, router advertisement and redirects) in the firewall. + +### Changed +- Relax the allow local network rules slightly. only checking either source or destination IP field + instead of both. They are still unroutable + +### Fixed +- Stop allowing the wrong IPv6 net fe02::/16 in the firewall when allow local network was enabled. + Instead allow the correct multicast nets ff02::/16 and ff05::/16. ## [2019.4-beta1] - 2019-05-02 diff --git a/Cargo.lock b/Cargo.lock index 94e2e8b782..99dcef5a24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1254,21 +1254,22 @@ dependencies = [ [[package]] name = "nftnl" -version = "0.1.0" -source = "git+https://github.com/mullvad/nftnl-rs?rev=29651f4370fdf22cc2e3abf5097a51f8ff17e3a3#29651f4370fdf22cc2e3abf5097a51f8ff17e3a3" +version = "0.2.0" +source = "git+https://github.com/mullvad/nftnl-rs?rev=86b30cdc38a6d4b30a900c21f7c644857d6f7401#86b30cdc38a6d4b30a900c21f7c644857d6f7401" dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "err-derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "nftnl-sys 0.1.0 (git+https://github.com/mullvad/nftnl-rs?rev=29651f4370fdf22cc2e3abf5097a51f8ff17e3a3)", + "nftnl-sys 0.2.0 (git+https://github.com/mullvad/nftnl-rs?rev=86b30cdc38a6d4b30a900c21f7c644857d6f7401)", ] [[package]] name = "nftnl-sys" -version = "0.1.0" -source = "git+https://github.com/mullvad/nftnl-rs?rev=29651f4370fdf22cc2e3abf5097a51f8ff17e3a3#29651f4370fdf22cc2e3abf5097a51f8ff17e3a3" +version = "0.2.0" +source = "git+https://github.com/mullvad/nftnl-rs?rev=86b30cdc38a6d4b30a900c21f7c644857d6f7401#86b30cdc38a6d4b30a900c21f7c644857d6f7401" dependencies = [ + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1996,7 +1997,7 @@ dependencies = [ "netlink-proto 0.1.1 (git+https://github.com/mullvad/netlink?branch=hack-older-kernel-compat)", "netlink-socket 0.0.2 (git+https://github.com/mullvad/netlink?branch=ignore-hw-address)", "netlink-sys 0.1.0 (git+https://github.com/mullvad/netlink?branch=hack-older-kernel-compat)", - "nftnl 0.1.0 (git+https://github.com/mullvad/nftnl-rs?rev=29651f4370fdf22cc2e3abf5097a51f8ff17e3a3)", + "nftnl 0.2.0 (git+https://github.com/mullvad/nftnl-rs?rev=86b30cdc38a6d4b30a900c21f7c644857d6f7401)", "nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "notify 4.0.10 (registry+https://github.com/rust-lang/crates.io-index)", "openvpn-plugin 0.3.0 (git+https://github.com/mullvad/openvpn-plugin-rs?branch=auth-failed-event)", @@ -2726,8 +2727,8 @@ dependencies = [ "checksum netlink-proto 0.1.1 (git+https://github.com/mullvad/netlink?branch=hack-older-kernel-compat)" = "<none>" "checksum netlink-socket 0.0.2 (git+https://github.com/mullvad/netlink?branch=ignore-hw-address)" = "<none>" "checksum netlink-sys 0.1.0 (git+https://github.com/mullvad/netlink?branch=hack-older-kernel-compat)" = "<none>" -"checksum nftnl 0.1.0 (git+https://github.com/mullvad/nftnl-rs?rev=29651f4370fdf22cc2e3abf5097a51f8ff17e3a3)" = "<none>" -"checksum nftnl-sys 0.1.0 (git+https://github.com/mullvad/nftnl-rs?rev=29651f4370fdf22cc2e3abf5097a51f8ff17e3a3)" = "<none>" +"checksum nftnl 0.2.0 (git+https://github.com/mullvad/nftnl-rs?rev=86b30cdc38a6d4b30a900c21f7c644857d6f7401)" = "<none>" +"checksum nftnl-sys 0.2.0 (git+https://github.com/mullvad/nftnl-rs?rev=86b30cdc38a6d4b30a900c21f7c644857d6f7401)" = "<none>" "checksum nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d37e713a259ff641624b6cb20e3b12b2952313ba36b6823c0f16e6cfd9e5de17" "checksum nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46f0f3210768d796e8fa79ec70ee6af172dacbe7147f5e69be5240a47778302b" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml index c2fd7b90bc..982b1d500d 100644 --- a/talpid-core/Cargo.toml +++ b/talpid-core/Cargo.toml @@ -44,7 +44,7 @@ rtnetlink = { git = "https://github.com/mullvad/netlink", branch = "hack-older-k netlink-proto = { git = "https://github.com/mullvad/netlink", branch = "hack-older-kernel-compat" } netlink-packet = { git = "https://github.com/mullvad/netlink", branch = "hack-older-kernel-compat" } netlink-sys = { git = "https://github.com/mullvad/netlink", branch = "hack-older-kernel-compat" } -nftnl = { git = "https://github.com/mullvad/nftnl-rs", rev = "29651f4370fdf22cc2e3abf5097a51f8ff17e3a3", features = ["nftnl-1-1-0"] } +nftnl = { git = "https://github.com/mullvad/nftnl-rs", rev = "86b30cdc38a6d4b30a900c21f7c644857d6f7401", features = ["nftnl-1-1-0"] } mnl = { git = "https://github.com/mullvad/mnl-rs", rev = "f0d19501b9b85be9a1ffaec8317a378bcbdf4fa6", features = ["mnl-1-0-4"] } which = "2.0" err-derive = "0.1.5" diff --git a/talpid-core/src/firewall/linux.rs b/talpid-core/src/firewall/linux.rs index 540bdff1ee..244b320098 100644 --- a/talpid-core/src/firewall/linux.rs +++ b/talpid-core/src/firewall/linux.rs @@ -4,7 +4,8 @@ use ipnetwork::IpNetwork; use lazy_static::lazy_static; use libc; use nftnl::{ - expr::{self, Verdict}, + self, + expr::{self, Payload, Verdict}, nft_expr, table, Batch, Chain, FinalizedBatch, ProtoFamily, Rule, Table, }; use std::{ @@ -199,6 +200,7 @@ impl<'a> PolicyBatch<'a> { out_chain.set_policy(nftnl::Policy::Drop); in_chain.set_policy(nftnl::Policy::Drop); + // A little dance that will make sure the table exists, but is cleared. batch.add(table, nftnl::MsgType::Add); batch.add(table, nftnl::MsgType::Del); batch.add(table, nftnl::MsgType::Add); @@ -237,43 +239,103 @@ impl<'a> PolicyBatch<'a> { fn add_dhcp_rules(&mut self) { use self::TransportProtocol::Udp; - const SERVER_PORT_V4: u16 = 67; - const CLIENT_PORT_V4: u16 = 68; - const SERVER_PORT_V6: u16 = 547; - const CLIENT_PORT_V6: u16 = 546; { let mut out_v4 = Rule::new(&self.out_chain); - check_port(&mut out_v4, Udp, End::Src, CLIENT_PORT_V4); + check_port(&mut out_v4, Udp, End::Src, super::DHCPV4_CLIENT_PORT); check_ip(&mut out_v4, End::Dst, IpAddr::V4(Ipv4Addr::BROADCAST)); - check_port(&mut out_v4, Udp, End::Dst, SERVER_PORT_V4); + check_port(&mut out_v4, Udp, End::Dst, super::DHCPV4_SERVER_PORT); add_verdict(&mut out_v4, &Verdict::Accept); self.batch.add(&out_v4, nftnl::MsgType::Add); } { let mut in_v4 = Rule::new(&self.in_chain); - check_port(&mut in_v4, Udp, End::Src, SERVER_PORT_V4); - check_port(&mut in_v4, Udp, End::Dst, CLIENT_PORT_V4); + check_port(&mut in_v4, Udp, End::Src, super::DHCPV4_SERVER_PORT); + check_port(&mut in_v4, Udp, End::Dst, super::DHCPV4_CLIENT_PORT); add_verdict(&mut in_v4, &Verdict::Accept); self.batch.add(&in_v4, nftnl::MsgType::Add); } for dhcpv6_server in &*super::DHCPV6_SERVER_ADDRS { let mut out_v6 = Rule::new(&self.out_chain); - check_net(&mut out_v6, End::Src, *super::LOCAL_INET6_NET); - check_port(&mut out_v6, Udp, End::Src, CLIENT_PORT_V6); + check_net(&mut out_v6, End::Src, *super::IPV6_LINK_LOCAL); + check_port(&mut out_v6, Udp, End::Src, super::DHCPV6_CLIENT_PORT); check_ip(&mut out_v6, End::Dst, *dhcpv6_server); - check_port(&mut out_v6, Udp, End::Dst, SERVER_PORT_V6); + check_port(&mut out_v6, Udp, End::Dst, super::DHCPV6_SERVER_PORT); add_verdict(&mut out_v6, &Verdict::Accept); self.batch.add(&out_v6, nftnl::MsgType::Add); } { let mut in_v6 = Rule::new(&self.in_chain); - check_net(&mut in_v6, End::Src, *super::LOCAL_INET6_NET); - check_port(&mut in_v6, Udp, End::Src, SERVER_PORT_V6); - check_net(&mut in_v6, End::Dst, *super::LOCAL_INET6_NET); - check_port(&mut in_v6, Udp, End::Dst, CLIENT_PORT_V6); + check_net(&mut in_v6, End::Src, *super::IPV6_LINK_LOCAL); + check_port(&mut in_v6, Udp, End::Src, super::DHCPV6_SERVER_PORT); + check_net(&mut in_v6, End::Dst, *super::IPV6_LINK_LOCAL); + check_port(&mut in_v6, Udp, End::Dst, super::DHCPV6_CLIENT_PORT); add_verdict(&mut in_v6, &Verdict::Accept); self.batch.add(&in_v6, nftnl::MsgType::Add); } + // Outgoing Router solicitation (part of NDP) + { + let mut rule = Rule::new(&self.out_chain); + check_ip( + &mut rule, + End::Dst, + *super::ROUTER_SOLICITATION_OUT_DST_ADDR, + ); + + rule.add_expr(&nft_expr!(meta l4proto)); + rule.add_expr(&nft_expr!(cmp == libc::IPPROTO_ICMPV6 as u8)); + + rule.add_expr(&Payload::Transport( + nftnl::expr::TransportHeaderField::Icmpv6(nftnl::expr::Icmpv6HeaderField::Type), + )); + rule.add_expr(&nft_expr!(cmp == 133u8)); + rule.add_expr(&nftnl::expr::Payload::Transport( + nftnl::expr::TransportHeaderField::Icmpv6(nftnl::expr::Icmpv6HeaderField::Code), + )); + rule.add_expr(&nft_expr!(cmp == 0u8)); + + add_verdict(&mut rule, &Verdict::Accept); + self.batch.add(&rule, nftnl::MsgType::Add); + } + // Incoming Router advertisement (part of NDP) + { + let mut rule = Rule::new(&self.in_chain); + check_net(&mut rule, End::Src, *super::IPV6_LINK_LOCAL); + + rule.add_expr(&nft_expr!(meta l4proto)); + rule.add_expr(&nft_expr!(cmp == libc::IPPROTO_ICMPV6 as u8)); + + rule.add_expr(&Payload::Transport( + nftnl::expr::TransportHeaderField::Icmpv6(nftnl::expr::Icmpv6HeaderField::Type), + )); + rule.add_expr(&nft_expr!(cmp == 134u8)); + rule.add_expr(&nftnl::expr::Payload::Transport( + nftnl::expr::TransportHeaderField::Icmpv6(nftnl::expr::Icmpv6HeaderField::Code), + )); + rule.add_expr(&nft_expr!(cmp == 0u8)); + + add_verdict(&mut rule, &Verdict::Accept); + self.batch.add(&rule, nftnl::MsgType::Add); + } + // Incoming Redirect (part of NDP) + { + let mut rule = Rule::new(&self.in_chain); + check_net(&mut rule, End::Src, *super::IPV6_LINK_LOCAL); + + rule.add_expr(&nft_expr!(meta l4proto)); + rule.add_expr(&nft_expr!(cmp == libc::IPPROTO_ICMPV6 as u8)); + + rule.add_expr(&Payload::Transport( + nftnl::expr::TransportHeaderField::Icmpv6(nftnl::expr::Icmpv6HeaderField::Type), + )); + rule.add_expr(&nft_expr!(cmp == 137u8)); + rule.add_expr(&nftnl::expr::Payload::Transport( + nftnl::expr::TransportHeaderField::Icmpv6(nftnl::expr::Icmpv6HeaderField::Code), + )); + rule.add_expr(&nft_expr!(cmp == 0u8)); + + add_verdict(&mut rule, &Verdict::Accept); + self.batch.add(&rule, nftnl::MsgType::Add); + } } fn add_policy_specific_rules(&mut self, policy: &FirewallPolicy) -> Result<()> { @@ -406,42 +468,24 @@ impl<'a> PolicyBatch<'a> { fn add_allow_lan_rules(&mut self) { // 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, *net); - check_net(&mut rule, End::Dst, *net); - add_verdict(&mut rule, &Verdict::Accept); - self.batch.add(&rule, nftnl::MsgType::Add); - } - let mut rule = Rule::new(chain); - check_net(&mut rule, End::Src, *super::LOCAL_INET6_NET); - check_net(&mut rule, End::Dst, *super::LOCAL_INET6_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, *net); - check_net(&mut rule, End::Dst, *super::MULTICAST_NET); - add_verdict(&mut rule, &Verdict::Accept); - - self.batch.add(&rule, nftnl::MsgType::Add); + for net in &*super::ALLOWED_LAN_NETS { + let mut out_rule = Rule::new(&self.out_chain); + check_net(&mut out_rule, End::Dst, *net); + add_verdict(&mut out_rule, &Verdict::Accept); + self.batch.add(&out_rule, nftnl::MsgType::Add); - // LAN -> SSDP + WS-Discovery protocols + let mut in_rule = Rule::new(&self.in_chain); + check_net(&mut in_rule, End::Src, *net); + add_verdict(&mut in_rule, &Verdict::Accept); + self.batch.add(&in_rule, nftnl::MsgType::Add); + } + // LAN -> Multicast + for net in &*super::ALLOWED_LAN_MULTICAST_NETS { let mut rule = Rule::new(&self.out_chain); - check_net(&mut rule, End::Src, *net); - check_ip(&mut rule, End::Dst, *super::SSDP_IP); + check_net(&mut rule, End::Dst, *net); add_verdict(&mut rule, &Verdict::Accept); - self.batch.add(&rule, nftnl::MsgType::Add); } - let mut rule = Rule::new(&self.out_chain); - check_net(&mut rule, End::Src, *super::LOCAL_INET6_NET); - check_net(&mut rule, End::Dst, *super::MULTICAST_INET6_NET); - add_verdict(&mut rule, &Verdict::Accept); - self.batch.add(&rule, nftnl::MsgType::Add); } } @@ -468,7 +512,8 @@ fn check_iface(rule: &mut Rule<'_>, direction: Direction, iface: &str) -> Result Ok(()) } -fn check_net(rule: &mut Rule<'_>, end: End, net: IpNetwork) { +fn check_net(rule: &mut Rule<'_>, end: End, net: impl Into<IpNetwork>) { + let net = net.into(); // Must check network layer protocol before loading network layer payload check_l3proto(rule, net.ip()); @@ -490,7 +535,8 @@ fn check_endpoint(rule: &mut Rule<'_>, end: End, endpoint: &Endpoint) { check_port(rule, endpoint.protocol, end, endpoint.address.port()); } -fn check_ip(rule: &mut Rule<'_>, end: End, ip: IpAddr) { +fn check_ip(rule: &mut Rule<'_>, end: End, ip: impl Into<IpAddr>) { + let ip = ip.into(); // Must check network layer protocol before loading network layer payload check_l3proto(rule, ip); diff --git a/talpid-core/src/firewall/macos.rs b/talpid-core/src/firewall/macos.rs index ba920d4e79..04dfa7c8d2 100644 --- a/talpid-core/src/firewall/macos.rs +++ b/talpid-core/src/firewall/macos.rs @@ -1,4 +1,5 @@ use super::{FirewallArguments, FirewallPolicy, FirewallT}; +use ipnetwork::IpNetwork; use pfctl::FilterRuleAction; use std::{ env, @@ -206,21 +207,21 @@ impl Firewall { let out_rule = self .create_rule_builder(FilterRuleAction::Pass) + .quick(true) .direction(pfctl::Direction::Out) - .to(pfctl::Endpoint::new(*host, 0)) .proto(icmp_proto) + .to(pfctl::Endpoint::new(*host, 0)) .keep_state(pfctl::StatePolicy::Keep) - .quick(true) .build()?; rules.push(out_rule); let in_rule = self .create_rule_builder(FilterRuleAction::Pass) + .quick(true) .direction(pfctl::Direction::In) - .from(pfctl::Endpoint::new(*host, 0)) .proto(icmp_proto) + .from(pfctl::Endpoint::new(*host, 0)) .keep_state(pfctl::StatePolicy::Keep) - .quick(true) .build()?; rules.push(in_rule); } @@ -230,108 +231,131 @@ impl Firewall { fn get_allow_tunnel_rule(&self, tunnel_interface: &str) -> Result<pfctl::FilterRule> { Ok(self .create_rule_builder(FilterRuleAction::Pass) + .quick(true) .interface(tunnel_interface) .keep_state(pfctl::StatePolicy::Keep) .tcp_flags(Self::get_tcp_flags()) - .quick(true) .build()?) } fn get_allow_loopback_rules(&self) -> Result<Vec<pfctl::FilterRule>> { let lo0_rule = self .create_rule_builder(FilterRuleAction::Pass) + .quick(true) .interface("lo0") .keep_state(pfctl::StatePolicy::Keep) - .quick(true) .build()?; Ok(vec![lo0_rule]) } fn get_allow_lan_rules(&self) -> Result<Vec<pfctl::FilterRule>> { let mut rules = vec![]; - // IPv4 - for net in &*super::PRIVATE_NETS { + for net in &*super::ALLOWED_LAN_NETS { let mut rule_builder = self.create_rule_builder(FilterRuleAction::Pass); - rule_builder + rule_builder.quick(true).af(match net { + IpNetwork::V4(_) => pfctl::AddrFamily::Ipv4, + IpNetwork::V6(_) => pfctl::AddrFamily::Ipv6, + }); + let allow_out = rule_builder + .direction(pfctl::Direction::Out) + .from(pfctl::Ip::Any) + .to(pfctl::Ip::from(*net)) + .build()?; + let allow_in = rule_builder + .direction(pfctl::Direction::In) + .from(pfctl::Ip::from(*net)) + .to(pfctl::Ip::Any) + .build()?; + rules.push(allow_out); + rules.push(allow_in); + } + for multicast_net in &*super::ALLOWED_LAN_MULTICAST_NETS { + let allow_multicast_out = self + .create_rule_builder(FilterRuleAction::Pass) .quick(true) - .af(pfctl::AddrFamily::Ipv4) - .from(pfctl::Ip::from(*net)); - let allow_net = rule_builder.to(pfctl::Ip::from(*net)).build()?; - let allow_multicast = rule_builder - .to(pfctl::Ip::from(*super::MULTICAST_NET)) + .direction(pfctl::Direction::Out) + .af(match multicast_net { + IpNetwork::V4(_) => pfctl::AddrFamily::Ipv4, + IpNetwork::V6(_) => pfctl::AddrFamily::Ipv6, + }) + .to(pfctl::Ip::from(*multicast_net)) .build()?; - let allow_ssdp = rule_builder.to(pfctl::Ip::from(*super::SSDP_IP)).build()?; - rules.push(allow_net); - rules.push(allow_multicast); - rules.push(allow_ssdp); + rules.push(allow_multicast_out); } - // IPv6 - let mut rule_builder = self.create_rule_builder(FilterRuleAction::Pass); - rule_builder - .quick(true) - .af(pfctl::AddrFamily::Ipv6) - .from(pfctl::Ip::from(*super::LOCAL_INET6_NET)); - let allow_net_v6 = rule_builder - .to(pfctl::Ip::from(*super::LOCAL_INET6_NET)) - .build()?; - let allow_multicast_v6 = rule_builder - .to(pfctl::Ip::from(*super::MULTICAST_INET6_NET)) - .build()?; - rules.push(allow_net_v6); - rules.push(allow_multicast_v6); - Ok(rules) } fn get_allow_dhcp_rules(&self) -> Result<Vec<pfctl::FilterRule>> { - let server_port_v4 = pfctl::Port::from(67); - let client_port_v4 = pfctl::Port::from(68); - let server_port_v6 = pfctl::Port::from(547); - let client_port_v6 = pfctl::Port::from(546); let mut dhcp_rule_builder = self.create_rule_builder(FilterRuleAction::Pass); dhcp_rule_builder.quick(true).proto(pfctl::Proto::Udp); let mut rules = Vec::new(); + + // DHCPv4 + dhcp_rule_builder.af(pfctl::AddrFamily::Ipv4); let allow_outgoing_dhcp_v4 = dhcp_rule_builder .direction(pfctl::Direction::Out) - .from(client_port_v4) - .to(pfctl::Endpoint::new(Ipv4Addr::BROADCAST, server_port_v4)) + .from(pfctl::Port::from(super::DHCPV4_CLIENT_PORT)) + .to(pfctl::Endpoint::new( + Ipv4Addr::BROADCAST, + pfctl::Port::from(super::DHCPV4_SERVER_PORT), + )) .build()?; - rules.push(allow_outgoing_dhcp_v4); let allow_incoming_dhcp_v4 = dhcp_rule_builder - .af(pfctl::AddrFamily::Ipv4) .direction(pfctl::Direction::In) - .from(server_port_v4) - .to(client_port_v4) + .from(pfctl::Port::from(super::DHCPV4_SERVER_PORT)) + .to(pfctl::Port::from(super::DHCPV4_CLIENT_PORT)) .build()?; + rules.push(allow_outgoing_dhcp_v4); rules.push(allow_incoming_dhcp_v4); + // DHCPv6 + dhcp_rule_builder.af(pfctl::AddrFamily::Ipv6); for dhcpv6_server in &*super::DHCPV6_SERVER_ADDRS { let allow_outgoing_dhcp_v6 = dhcp_rule_builder - .af(pfctl::AddrFamily::Ipv6) .direction(pfctl::Direction::Out) .from(pfctl::Endpoint::new( - *super::LOCAL_INET6_NET, - client_port_v6, + IpNetwork::V6(*super::IPV6_LINK_LOCAL), + pfctl::Port::from(super::DHCPV6_CLIENT_PORT), + )) + .to(pfctl::Endpoint::new( + *dhcpv6_server, + pfctl::Port::from(super::DHCPV6_SERVER_PORT), )) - .to(pfctl::Endpoint::new(*dhcpv6_server, server_port_v6)) .build()?; rules.push(allow_outgoing_dhcp_v6); } let allow_incoming_dhcp_v6 = dhcp_rule_builder - .af(pfctl::AddrFamily::Ipv6) .direction(pfctl::Direction::In) .from(pfctl::Endpoint::new( - *super::LOCAL_INET6_NET, - server_port_v6, + pfctl::Ip::from(IpNetwork::V6(*super::IPV6_LINK_LOCAL)), + pfctl::Port::from(super::DHCPV6_SERVER_PORT), )) .to(pfctl::Endpoint::new( - *super::LOCAL_INET6_NET, - client_port_v6, + pfctl::Ip::from(IpNetwork::V6(*super::IPV6_LINK_LOCAL)), + pfctl::Port::from(super::DHCPV6_CLIENT_PORT), )) .build()?; rules.push(allow_incoming_dhcp_v6); + // NDP (router solicitation, advertisement and redirect) + let allow_router_solicitation = self + .create_rule_builder(FilterRuleAction::Pass) + .quick(true) + .proto(pfctl::Proto::IcmpV6) + .direction(pfctl::Direction::Out) + .to(*super::ROUTER_SOLICITATION_OUT_DST_ADDR) + .build()?; + let allow_router_advertisement_and_redirect = self + .create_rule_builder(FilterRuleAction::Pass) + .quick(true) + .proto(pfctl::Proto::IcmpV6) + .direction(pfctl::Direction::In) + .from(pfctl::Ip::from(IpNetwork::V6(*super::IPV6_LINK_LOCAL))) + .build()?; + rules.push(allow_router_solicitation); + rules.push(allow_router_advertisement_and_redirect); + Ok(rules) } diff --git a/talpid-core/src/firewall/mod.rs b/talpid-core/src/firewall/mod.rs index c86ac65b68..6561b3298b 100644 --- a/talpid-core/src/firewall/mod.rs +++ b/talpid-core/src/firewall/mod.rs @@ -30,26 +30,76 @@ pub use self::imp::Error; #[cfg(unix)] lazy_static! { - static ref PRIVATE_NETS: [IpNetwork; 4] = [ + /// When "allow local network" is enabled the app will allow traffic to and from these networks. + static ref ALLOWED_LAN_NETS: [IpNetwork; 5] = [ IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(10, 0, 0, 0), 8).unwrap()), IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(172, 16, 0, 0), 12).unwrap()), IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(192, 168, 0, 0), 16).unwrap()), IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(169, 254, 0, 0), 16).unwrap()), + IpNetwork::V6(Ipv6Network::new(Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0), 10).unwrap()), ]; - static ref LOCAL_INET6_NET: IpNetwork = - IpNetwork::V6(Ipv6Network::new(Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0), 10).unwrap()); - static ref MULTICAST_NET: IpNetwork = - IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(224, 0, 0, 0), 24).unwrap()); - static ref MULTICAST_INET6_NET: IpNetwork = - IpNetwork::V6(Ipv6Network::new(Ipv6Addr::new(0xfe02, 0, 0, 0, 0, 0, 0, 0), 16).unwrap()); - static ref SSDP_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(239, 255, 255, 250)); - static ref DHCPV6_SERVER_ADDRS: [IpAddr; 2] = [ - IpAddr::V6(Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 1, 2)), - IpAddr::V6(Ipv6Addr::new(0xff05, 0, 0, 0, 0, 0, 1, 3)), + /// When "allow local network" is enabled the app will allow traffic to these networks. + static ref ALLOWED_LAN_MULTICAST_NETS: [IpNetwork; 4] = [ + // Local subnetwork multicast. Not routable + IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(224, 0, 0, 0), 24).unwrap()), + // Simple Service Discovery Protocol (SSDP) address + IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(239, 255, 255, 250), 32).unwrap()), + // Link-local IPv6 multicast. IPv6 equivalent of 224.0.0.0/24 + IpNetwork::V6(Ipv6Network::new(Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0, 0), 16).unwrap()), + // Site-local IPv6 multicast. + IpNetwork::V6(Ipv6Network::new(Ipv6Addr::new(0xff05, 0, 0, 0, 0, 0, 0, 0), 16).unwrap()), ]; + static ref IPV6_LINK_LOCAL: Ipv6Network = Ipv6Network::new(Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0), 10).unwrap(); + /// The allowed target addresses of outbound DHCPv6 requests + static ref DHCPV6_SERVER_ADDRS: [Ipv6Addr; 2] = [ + Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 1, 2), + Ipv6Addr::new(0xff05, 0, 0, 0, 0, 0, 1, 3), + ]; + static ref ROUTER_SOLICITATION_OUT_DST_ADDR: Ipv6Addr = Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0, 2); } +#[cfg(all(unix, not(target_os = "android")))] +const DHCPV4_SERVER_PORT: u16 = 67; +#[cfg(all(unix, not(target_os = "android")))] +const DHCPV4_CLIENT_PORT: u16 = 68; +#[cfg(all(unix, not(target_os = "android")))] +const DHCPV6_SERVER_PORT: u16 = 547; +#[cfg(all(unix, not(target_os = "android")))] +const DHCPV6_CLIENT_PORT: u16 = 546; + /// A enum that describes network security strategy +/// +/// # Firewall block/allow specification. +/// +/// Except what's described as allowed below, all network packets should be blocked. +/// +/// ## In all policies the firewall should always allow the following traffic +/// +/// 1. All traffic on loopback adapters +/// 2. DHCPv4 and DHCPv6 requests to go out and responses to come in: +/// * Outgoing from *:DHCPV4_CLIENT_PORT to 255.255.255.255:DHCPV4_SERVER_PORT +/// * Incoming *:DHCPV4_SERVER_PORT to *:DHCPV4_CLIENT_PORT +/// * Outgoing from IPV6_LINK_LOCAL:DHCPV6_CLIENT_PORT to DHCPV6_SERVER_ADDRS:DHCPV6_SERVER_PORT +/// * Incoming from IPV6_LINK_LOCAL:DHCPV6_SERVER_PORT to IPV6_LINK_LOCAL:DHCPV6_CLIENT_PORT +/// 3. Router solicitation, advertisement and redirects (subset of NDP): +/// * Outgoing to ROUTER_SOLICITATION_OUT_DST_ADDR, but only ICMPv6 with type 133 and code 0. +/// * Incoming from IPV6_LINK_LOCAL, but only ICMPv6 type 134 or 137 and code 0. +/// 4. If `allow_lan` is enabled, all policies should allow the following traffic: +/// * Outgoing to, and incoming from, any IP in the networks listed in ALLOWED_LAN_NETS +/// * Outgoing to any IP in the networks listed in ALLOWED_LAN_MULTICAST_NETS +/// +/// ## Policy specific rules +/// +/// 1. In the `Connecting` and `Connected` policies traffic should be allowed to and from the IP and +/// port in `peer_endpoint` +/// 2. In the `Connecting` policy, ICMP packets should be allowed to and from all IPs in +/// `pingable_hosts`. +/// 3. In the `Connected` policy, DNS requests (destination port 53 on both UDP and TCP) should be +/// allowed over the tunnel interface in `tunnel.interface` and to the IPs `tunnel.ipv4_gateway` +/// and `tunnel.ipv6_gateway`. But blocked to all other destinations and over all other +/// interfaces. +/// 4. In the `Connected` policy, all traffic should be allowed over the tunnel interface in +/// `tunnel.interface`, minus the DNS packets described above. #[derive(Debug, Clone, Eq, PartialEq)] pub enum FirewallPolicy { /// Allow traffic only to server |
