diff options
| author | Jonathan <jonathan@mullvad.net> | 2023-12-05 10:03:08 +0100 |
|---|---|---|
| committer | Jonathan <jonathan@mullvad.net> | 2024-01-03 14:38:41 +0100 |
| commit | 4fdc34acbba60d5092e45ce3e513d30ec996c317 (patch) | |
| tree | 80d3a23c1a96bd3d80e05ac66b530e39c252d48a /talpid-core | |
| parent | c510df96772b1e4ab7998e739ced42806c78e931 (diff) | |
| download | mullvadvpn-4fdc34acbba60d5092e45ce3e513d30ec996c317.tar.xz mullvadvpn-4fdc34acbba60d5092e45ce3e513d30ec996c317.zip | |
Allow app to use custom socks5 and shadwosocks proxies
This PR has a couple of different purposes
- Allow users to use socks5 local proxies with the CLI without
having to be root nor use split-tunneling. This only works for
OpenVPN.
- Unify the types used by different proxy parts of the codebase,
such as the Access Methods as well as some already existing
OpenVPN proxy code.
This PR changes the firewall on all desktop platforms as well as changes
the routing table slightly on MacOS and Windows.
On Linux the firewall code is modified to apply the appropriate firewall
marks to all packages that go to a remote endpoint corresponding to the
remote part of a local socks5 proxy. The firewall marks will allow the
routing to be done without having to modify the routing table.
On MacOS and Windows the routing table is modified to allow packages to
go to that same endpoint to pass outside the VPN tunnel, it will
additionally punch a hole in the firewall.
The PR also migrates the settings file from version 7 to version 8 in order
to properly and neatly unify Proxy related types.
Finally it provides some slight extensions to the gRPC interface in
order to allow for control over the custom proxy settings.
Diffstat (limited to 'talpid-core')
| -rw-r--r-- | talpid-core/src/firewall/linux.rs | 21 | ||||
| -rw-r--r-- | talpid-core/src/firewall/macos.rs | 26 | ||||
| -rw-r--r-- | talpid-core/src/firewall/mod.rs | 14 | ||||
| -rw-r--r-- | talpid-core/src/firewall/windows.rs | 77 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/mod.rs | 33 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connected_state.rs | 31 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connecting_state.rs | 22 |
7 files changed, 147 insertions, 77 deletions
diff --git a/talpid-core/src/firewall/linux.rs b/talpid-core/src/firewall/linux.rs index 6cbff79b32..79ac00fc8d 100644 --- a/talpid-core/src/firewall/linux.rs +++ b/talpid-core/src/firewall/linux.rs @@ -567,6 +567,7 @@ impl<'a> PolicyBatch<'a> { self.add_allow_tunnel_endpoint_rules(peer_endpoint, fwmark); self.add_allow_dns_rules(tunnel, dns_servers, TransportProtocol::Udp)?; self.add_allow_dns_rules(tunnel, dns_servers, TransportProtocol::Tcp)?; + // Important to block DNS *before* we allow the tunnel and allow LAN. So DNS // can't leak to the wrong IPs in the tunnel or on the LAN. self.add_drop_dns_rule(); @@ -607,10 +608,10 @@ impl<'a> PolicyBatch<'a> { Ok(()) } - fn add_allow_tunnel_endpoint_rules(&mut self, endpoint: &Endpoint, fwmark: u32) { + fn add_allow_tunnel_endpoint_rules(&mut self, endpoint: &AllowedEndpoint, fwmark: u32) { let mut prerouting_rule = Rule::new(&self.prerouting_chain); // Mark incoming traffic from endpoint with fwmark - check_endpoint(&mut prerouting_rule, End::Src, endpoint); + check_endpoint(&mut prerouting_rule, End::Src, &endpoint.endpoint); prerouting_rule.add_expr(&nft_expr!(immediate data fwmark)); prerouting_rule.add_expr(&nft_expr!(meta mark set)); @@ -621,7 +622,7 @@ impl<'a> PolicyBatch<'a> { self.batch.add(&prerouting_rule, nftnl::MsgType::Add); let mut in_rule = Rule::new(&self.in_chain); - check_endpoint(&mut in_rule, End::Src, endpoint); + check_endpoint(&mut in_rule, End::Src, &endpoint.endpoint); // Allow all incoming traffic from established connections to the endpoint let allowed_states = nftnl::expr::ct::States::ESTABLISHED.bits(); @@ -637,12 +638,24 @@ impl<'a> PolicyBatch<'a> { // Allow any traffic to the endpoint which is marked with fwmark let mut out_rule = Rule::new(&self.out_chain); - check_endpoint(&mut out_rule, End::Dst, endpoint); + check_endpoint(&mut out_rule, End::Dst, &endpoint.endpoint); out_rule.add_expr(&nft_expr!(meta mark)); out_rule.add_expr(&nft_expr!(cmp == fwmark)); add_verdict(&mut out_rule, &Verdict::Accept); self.batch.add(&out_rule, nftnl::MsgType::Add); + + // Used for local custom bridge, allows some local socks5 proxy to send traffic to the + // endpoint + if endpoint.clients.allow_all() { + let mut rule = Rule::new(&self.mangle_chain); + check_endpoint(&mut rule, End::Dst, &endpoint.endpoint); + rule.add_expr(&nft_expr!(immediate data split_tunnel::MARK)); + rule.add_expr(&nft_expr!(ct mark set)); + rule.add_expr(&nft_expr!(immediate data fwmark)); + rule.add_expr(&nft_expr!(meta mark set)); + self.batch.add(&rule, nftnl::MsgType::Add); + } } /// Adds firewall rules allow traffic to flow to the API. Allows the app to reach the API in diff --git a/talpid-core/src/firewall/macos.rs b/talpid-core/src/firewall/macos.rs index 54f72a79c2..27fc6200fa 100644 --- a/talpid-core/src/firewall/macos.rs +++ b/talpid-core/src/firewall/macos.rs @@ -121,7 +121,7 @@ impl Firewall { allowed_endpoint, allowed_tunnel_traffic, } => { - let mut rules = vec![self.get_allow_relay_rule(*peer_endpoint)?]; + let mut rules = vec![self.get_allow_relay_rule(peer_endpoint)?]; rules.push(self.get_allowed_endpoint_rule(allowed_endpoint)?); // Important to block DNS after allow relay rule (so the relay can operate @@ -151,7 +151,7 @@ impl Firewall { rules.append(&mut self.get_allow_dns_rules_when_connected(tunnel, *server)?); } - rules.push(self.get_allow_relay_rule(*peer_endpoint)?); + rules.push(self.get_allow_relay_rule(peer_endpoint)?); // Important to block DNS *before* we allow the tunnel and allow LAN. So DNS // can't leak to the wrong IPs in the tunnel or on the LAN. @@ -273,18 +273,26 @@ impl Firewall { Ok(rules) } - fn get_allow_relay_rule(&self, relay_endpoint: net::Endpoint) -> Result<pfctl::FilterRule> { - let pfctl_proto = as_pfctl_proto(relay_endpoint.protocol); + fn get_allow_relay_rule( + &self, + relay_endpoint: &net::AllowedEndpoint, + ) -> Result<pfctl::FilterRule> { + let pfctl_proto = as_pfctl_proto(relay_endpoint.endpoint.protocol); - self.create_rule_builder(FilterRuleAction::Pass) + let mut builder = self.create_rule_builder(FilterRuleAction::Pass); + builder .direction(pfctl::Direction::Out) - .to(relay_endpoint.address) + .to(relay_endpoint.endpoint.address) .proto(pfctl_proto) .keep_state(pfctl::StatePolicy::Keep) .tcp_flags(Self::get_tcp_flags()) - .user(Uid::from(super::ROOT_UID)) - .quick(true) - .build() + .quick(true); + + if !relay_endpoint.clients.allow_all() { + builder.user(Uid::from(super::ROOT_UID)); + } + + builder.build() } /// Produces a rule that allows traffic to flow to the API. Allows the app (or other apps if configured) diff --git a/talpid-core/src/firewall/mod.rs b/talpid-core/src/firewall/mod.rs index 5a35b1a7ae..a0afb39f5d 100644 --- a/talpid-core/src/firewall/mod.rs +++ b/talpid-core/src/firewall/mod.rs @@ -1,12 +1,10 @@ use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network}; use once_cell::sync::Lazy; -#[cfg(windows)] -use std::path::PathBuf; use std::{ fmt, net::{IpAddr, Ipv4Addr, Ipv6Addr}, }; -use talpid_types::net::{AllowedEndpoint, AllowedTunnelTraffic, Endpoint}; +use talpid_types::net::{AllowedEndpoint, AllowedTunnelTraffic}; #[cfg(target_os = "macos")] #[path = "macos.rs"] @@ -116,7 +114,7 @@ pub enum FirewallPolicy { /// Allow traffic only to server Connecting { /// The peer endpoint that should be allowed. - peer_endpoint: Endpoint, + peer_endpoint: AllowedEndpoint, /// Metadata about the tunnel and tunnel interface. tunnel: Option<crate::tunnel::TunnelMetadata>, /// Flag setting if communication with LAN networks should be possible. @@ -125,15 +123,12 @@ pub enum FirewallPolicy { allowed_endpoint: AllowedEndpoint, /// Networks for which to permit in-tunnel traffic. allowed_tunnel_traffic: AllowedTunnelTraffic, - /// A process that is allowed to send packets to the relay. - #[cfg(windows)] - relay_client: PathBuf, }, /// Allow traffic only to server and over tunnel interface Connected { /// The peer endpoint that should be allowed. - peer_endpoint: Endpoint, + peer_endpoint: AllowedEndpoint, /// Metadata about the tunnel and tunnel interface. tunnel: crate::tunnel::TunnelMetadata, /// Flag setting if communication with LAN networks should be possible. @@ -141,9 +136,6 @@ pub enum FirewallPolicy { /// Servers that are allowed to respond to DNS requests. #[cfg(not(target_os = "android"))] dns_servers: Vec<IpAddr>, - /// A process that is allowed to send packets to the relay. - #[cfg(windows)] - relay_client: PathBuf, }, /// Block all network traffic in and out from the computer. diff --git a/talpid-core/src/firewall/windows.rs b/talpid-core/src/firewall/windows.rs index ed3e0a19fe..f26102820a 100644 --- a/talpid-core/src/firewall/windows.rs +++ b/talpid-core/src/firewall/windows.rs @@ -1,11 +1,11 @@ use crate::tunnel::TunnelMetadata; -use std::{ffi::CStr, io, net::IpAddr, path::Path, ptr}; +use std::{ffi::CStr, io, net::IpAddr, ptr}; use self::winfw::*; use super::{FirewallArguments, FirewallPolicy, InitialFirewallState}; use talpid_types::{ - net::{AllowedEndpoint, AllowedTunnelTraffic, Endpoint}, + net::{AllowedEndpoint, AllowedTunnelTraffic}, tunnel::FirewallPolicyError, }; use widestring::WideCString; @@ -99,7 +99,6 @@ impl Firewall { allow_lan, allowed_endpoint, allowed_tunnel_traffic, - relay_client, } => { let cfg = &WinFwSettings::new(allow_lan); @@ -109,7 +108,6 @@ impl Firewall { &tunnel, &WinFwAllowedEndpointContainer::from(allowed_endpoint).as_endpoint(), &allowed_tunnel_traffic, - &relay_client, ) } FirewallPolicy::Connected { @@ -117,10 +115,9 @@ impl Firewall { tunnel, allow_lan, dns_servers, - relay_client, } => { let cfg = &WinFwSettings::new(allow_lan); - self.set_connected_state(&peer_endpoint, cfg, &tunnel, &dns_servers, &relay_client) + self.set_connected_state(&peer_endpoint, cfg, &tunnel, &dns_servers) } FirewallPolicy::Blocked { allow_lan, @@ -142,22 +139,33 @@ impl Firewall { fn set_connecting_state( &mut self, - endpoint: &Endpoint, + endpoint: &AllowedEndpoint, winfw_settings: &WinFwSettings, tunnel_metadata: &Option<TunnelMetadata>, allowed_endpoint: &WinFwAllowedEndpoint<'_>, allowed_tunnel_traffic: &AllowedTunnelTraffic, - relay_client: &Path, ) -> Result<(), Error> { log::trace!("Applying 'connecting' firewall policy"); - let ip_str = widestring_ip(endpoint.address.ip()); + let ip_str = widestring_ip(endpoint.endpoint.address.ip()); let winfw_relay = WinFwEndpoint { ip: ip_str.as_ptr(), - port: endpoint.address.port(), - protocol: WinFwProt::from(endpoint.protocol), + port: endpoint.endpoint.address.port(), + protocol: WinFwProt::from(endpoint.endpoint.protocol), }; - let relay_client = WideCString::from_os_str_truncate(relay_client); + // SAFETY: `endpoint1_ip`, `endpoint2_ip`, `endpoint1`, `endpoint2`, `relay_client_wstrs` must not be dropped + // until `WinFw_ApplyPolicyConnecting` has returned. + + let relay_client_wstrs: Vec<_> = endpoint + .clients + .iter() + .map(WideCString::from_os_str_truncate) + .collect(); + let relay_client_wstr_ptrs: Vec<*const u16> = relay_client_wstrs + .iter() + .map(|wstr| wstr.as_ptr()) + .collect(); + let relay_client_wstr_ptrs_len = relay_client_wstr_ptrs.len(); let interface_wstr = tunnel_metadata .as_ref() @@ -168,8 +176,6 @@ impl Firewall { ptr::null() }; - // SAFETY: `endpoint1_ip`, `endpoint2_ip`, `endpoint1` and `endpoint2` must not be dropped - // until `WinFw_ApplyPolicyConnecting` has returned. let mut endpoint1_ip = WideCString::new(); let mut endpoint2_ip = WideCString::new(); let (endpoint1, endpoint2) = match allowed_tunnel_traffic { @@ -218,7 +224,8 @@ impl Firewall { WinFw_ApplyPolicyConnecting( winfw_settings, &winfw_relay, - relay_client.as_ptr(), + relay_client_wstr_ptrs.as_ptr(), + relay_client_wstr_ptrs_len, interface_wstr_ptr, allowed_endpoint, &allowed_tunnel_traffic, @@ -235,19 +242,19 @@ impl Firewall { drop(endpoint1); #[allow(clippy::drop_non_drop)] drop(endpoint2); + drop(relay_client_wstrs); res } fn set_connected_state( &mut self, - endpoint: &Endpoint, + endpoint: &AllowedEndpoint, winfw_settings: &WinFwSettings, tunnel_metadata: &TunnelMetadata, dns_servers: &[IpAddr], - relay_client: &Path, ) -> Result<(), Error> { log::trace!("Applying 'connected' firewall policy"); - let ip_str = widestring_ip(endpoint.address.ip()); + let ip_str = widestring_ip(endpoint.endpoint.address.ip()); let v4_gateway = widestring_ip(tunnel_metadata.ipv4_gateway.into()); let v6_gateway = tunnel_metadata .ipv6_gateway @@ -258,8 +265,8 @@ impl Firewall { // ip_str, gateway_str and tunnel_alias have to outlive winfw_relay let winfw_relay = WinFwEndpoint { ip: ip_str.as_ptr(), - port: endpoint.address.port(), - protocol: WinFwProt::from(endpoint.protocol), + port: endpoint.endpoint.address.port(), + protocol: WinFwProt::from(endpoint.endpoint.protocol), }; let v6_gateway_ptr = match &v6_gateway { @@ -267,17 +274,28 @@ impl Firewall { None => ptr::null(), }; - let relay_client = WideCString::from_os_str_truncate(relay_client); + // SAFETY: `relay_client_wstrs` must not be dropped until `WinFw_ApplyPolicyConnected` has returned. + let relay_client_wstrs: Vec<_> = endpoint + .clients + .iter() + .map(WideCString::from_os_str_truncate) + .collect(); + let relay_client_wstr_ptrs: Vec<*const u16> = relay_client_wstrs + .iter() + .map(|wstr| wstr.as_ptr()) + .collect(); + let relay_client_wstr_ptrs_len = relay_client_wstr_ptrs.len(); let dns_servers: Vec<WideCString> = dns_servers.iter().cloned().map(widestring_ip).collect(); let dns_servers: Vec<*const u16> = dns_servers.iter().map(|ip| ip.as_ptr()).collect(); - unsafe { + let result = unsafe { WinFw_ApplyPolicyConnected( winfw_settings, &winfw_relay, - relay_client.as_ptr(), + relay_client_wstr_ptrs.as_ptr(), + relay_client_wstr_ptrs_len, tunnel_alias.as_ptr(), v4_gateway.as_ptr(), v6_gateway_ptr, @@ -286,7 +304,12 @@ impl Firewall { ) .into_result() .map_err(Error::ApplyingConnectedPolicy) - } + }; + + // SAFETY: `relay_client_wstrs` holds memory pointed to by pointers used in C++ and must + // not be dropped until after `WinFw_ApplyPolicyConnected` has returned. + drop(relay_client_wstrs); + result } fn set_blocked_state( @@ -598,7 +621,8 @@ mod winfw { pub fn WinFw_ApplyPolicyConnecting( settings: &WinFwSettings, relay: &WinFwEndpoint, - relayClient: *const libc::wchar_t, + relayClient: *const *const libc::wchar_t, + relayClientLen: usize, tunnelIfaceAlias: *const libc::wchar_t, allowedEndpoint: *const WinFwAllowedEndpoint<'_>, allowedTunnelTraffic: &WinFwAllowedTunnelTraffic, @@ -608,7 +632,8 @@ mod winfw { pub fn WinFw_ApplyPolicyConnected( settings: &WinFwSettings, relay: &WinFwEndpoint, - relayClient: *const libc::wchar_t, + relayClient: *const *const libc::wchar_t, + relayClientLen: usize, tunnelIfaceAlias: *const libc::wchar_t, v4Gateway: *const libc::wchar_t, v6Gateway: *const libc::wchar_t, diff --git a/talpid-core/src/tunnel/mod.rs b/talpid-core/src/tunnel/mod.rs index 4ab6bb8d3d..3d953e965a 100644 --- a/talpid-core/src/tunnel/mod.rs +++ b/talpid-core/src/tunnel/mod.rs @@ -4,7 +4,7 @@ use futures::channel::oneshot; use std::path; #[cfg(not(target_os = "android"))] use talpid_openvpn; -#[cfg(any(target_os = "linux", target_os = "windows"))] +#[cfg(not(target_os = "android"))] use talpid_routing::RouteManagerHandle; pub use talpid_tunnel::{TunnelArgs, TunnelEvent, TunnelMetadata}; #[cfg(not(target_os = "android"))] @@ -91,7 +91,6 @@ impl TunnelMonitor { args.resource_dir, args.on_event, args.tunnel_close_rx, - #[cfg(target_os = "linux")] args.route_manager, )), #[cfg(target_os = "android")] @@ -104,20 +103,23 @@ impl TunnelMonitor { } /// Returns a path to an executable that communicates with relay servers. + /// Returns `None` if the executable is unknown. #[cfg(windows)] - pub fn get_relay_client(resource_dir: &path::Path, params: &TunnelParameters) -> path::PathBuf { + pub fn get_relay_client( + resource_dir: &path::Path, + params: &TunnelParameters, + ) -> Option<path::PathBuf> { + use talpid_types::net::proxy::CustomProxy; + let resource_dir = resource_dir.to_path_buf(); - let process_string = match params { - TunnelParameters::OpenVpn(params) => { - if let Some(openvpn_types::ProxySettings::Shadowsocks(..)) = ¶ms.proxy { - return std::env::current_exe().unwrap(); - } else { - "openvpn.exe" - } - } - _ => return std::env::current_exe().unwrap(), - }; - resource_dir.join(process_string) + match params { + TunnelParameters::OpenVpn(params) => match ¶ms.proxy { + Some(CustomProxy::Shadowsocks(_)) => Some(std::env::current_exe().unwrap()), + Some(CustomProxy::Socks5Local(_)) => None, + Some(CustomProxy::Socks5Remote(_)) | None => Some(resource_dir.join("openvpn.exe")), + }, + _ => Some(std::env::current_exe().unwrap()), + } } fn start_wireguard_tunnel<L>( @@ -211,7 +213,7 @@ impl TunnelMonitor { resource_dir: &path::Path, on_event: L, tunnel_close_rx: oneshot::Receiver<()>, - #[cfg(target_os = "linux")] route_manager: RouteManagerHandle, + route_manager: RouteManagerHandle, ) -> Result<Self> where L: (Fn(TunnelEvent) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send>>) @@ -224,7 +226,6 @@ impl TunnelMonitor { config, log, resource_dir, - #[cfg(target_os = "linux")] route_manager, ) .await?; diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs index 21375f056b..6f32b796c3 100644 --- a/talpid-core/src/tunnel_state_machine/connected_state.rs +++ b/talpid-core/src/tunnel_state_machine/connected_state.rs @@ -14,7 +14,7 @@ use futures::{ }; use std::net::IpAddr; use talpid_types::{ - net::TunnelParameters, + net::{AllowedClients, AllowedEndpoint, TunnelParameters}, tunnel::{ErrorStateCause, FirewallPolicyError}, BoxedError, ErrorExt, }; @@ -130,17 +130,34 @@ impl ConnectedState { } fn get_firewall_policy(&self, shared_values: &SharedTunnelStateValues) -> FirewallPolicy { + let endpoint = self.tunnel_parameters.get_next_hop_endpoint(); + + #[cfg(target_os = "windows")] + let clients = AllowedClients::from( + TunnelMonitor::get_relay_client(&shared_values.resource_dir, &self.tunnel_parameters) + .into_iter() + .collect::<Vec<_>>(), + ); + + #[cfg(not(target_os = "windows"))] + let clients = if self + .tunnel_parameters + .get_openvpn_local_proxy_settings() + .is_some() + { + AllowedClients::All + } else { + AllowedClients::Root + }; + + let peer_endpoint = AllowedEndpoint { endpoint, clients }; + FirewallPolicy::Connected { - peer_endpoint: self.tunnel_parameters.get_next_hop_endpoint(), + peer_endpoint, tunnel: self.metadata.clone(), allow_lan: shared_values.allow_lan, #[cfg(not(target_os = "android"))] dns_servers: self.get_dns_servers(shared_values), - #[cfg(windows)] - relay_client: TunnelMonitor::get_relay_client( - &shared_values.resource_dir, - &self.tunnel_parameters, - ), } } diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs index d7d93da9d4..e49e5b69d8 100644 --- a/talpid-core/src/tunnel_state_machine/connecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs @@ -21,7 +21,7 @@ use std::{ use talpid_routing::RouteManager; use talpid_tunnel::{tun_provider::TunProvider, TunnelArgs, TunnelEvent, TunnelMetadata}; use talpid_types::{ - net::{AllowedTunnelTraffic, TunnelParameters}, + net::{AllowedClients, AllowedEndpoint, AllowedTunnelTraffic, TunnelParameters}, tunnel::{ErrorStateCause, FirewallPolicyError}, ErrorExt, }; @@ -140,7 +140,23 @@ impl ConnectingState { #[cfg(target_os = "linux")] shared_values.disable_connectivity_check(); - let peer_endpoint = params.get_next_hop_endpoint(); + let endpoint = params.get_next_hop_endpoint(); + + #[cfg(target_os = "windows")] + let clients = AllowedClients::from( + TunnelMonitor::get_relay_client(&shared_values.resource_dir, params) + .into_iter() + .collect::<Vec<_>>(), + ); + + #[cfg(not(target_os = "windows"))] + let clients = if params.get_openvpn_local_proxy_settings().is_some() { + AllowedClients::All + } else { + AllowedClients::Root + }; + + let peer_endpoint = AllowedEndpoint { endpoint, clients }; let policy = FirewallPolicy::Connecting { peer_endpoint, @@ -148,8 +164,6 @@ impl ConnectingState { allow_lan: shared_values.allow_lan, allowed_endpoint: shared_values.allowed_endpoint.clone(), allowed_tunnel_traffic, - #[cfg(windows)] - relay_client: TunnelMonitor::get_relay_client(&shared_values.resource_dir, params), }; shared_values .firewall |
