diff options
| author | David Lönnhager <david.l@mullvad.net> | 2021-05-17 12:58:39 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2021-05-17 12:58:39 +0200 |
| commit | 3dde5e0693dc9b6364bf4207e70d936b45ef3230 (patch) | |
| tree | 7aee2ef848c4181c1b59c7619843dcb9518c767c | |
| parent | af9f0236984d5e41b2517a3fd52c417290094c82 (diff) | |
| parent | 113f5bdf8f5d9f5aba3bb4543837ccf9c425d0a6 (diff) | |
| download | mullvadvpn-3dde5e0693dc9b6364bf4207e70d936b45ef3230.tar.xz mullvadvpn-3dde5e0693dc9b6364bf4207e70d936b45ef3230.zip | |
Merge branch 'wg-multihop'
22 files changed, 382 insertions, 76 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index cee3e30c77..b57b14d27d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ Line wrap the file at 100 chars. Th - Support WireGuard over TCP for custom VPN relays in the CLI. - Make app native on Apple Silicon. - Add DNS options for ad and tracker blocking to the CLI. +- Support WireGuard multihop using an entry endpoint constraint. ### Changed - Upgrade OpenVPN from 2.5.0 to 2.5.1. diff --git a/mullvad-cli/src/cmds/bridge.rs b/mullvad-cli/src/cmds/bridge.rs index 1e2a223817..0599c7aef6 100644 --- a/mullvad-cli/src/cmds/bridge.rs +++ b/mullvad-cli/src/cmds/bridge.rs @@ -218,7 +218,7 @@ impl Bridge { } async fn handle_set_bridge_location(matches: &clap::ArgMatches<'_>) -> Result<()> { - Self::update_bridge_settings(Some(location::get_constraint(matches)), None).await + Self::update_bridge_settings(Some(location::get_constraint_from_args(matches)), None).await } async fn handle_set_bridge_provider(matches: &clap::ArgMatches<'_>) -> Result<()> { diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs index d9067c7dbb..245eda9e91 100644 --- a/mullvad-cli/src/cmds/relay.rs +++ b/mullvad-cli/src/cmds/relay.rs @@ -2,6 +2,7 @@ use crate::{location, new_rpc_client, Command, Error, Result}; use clap::{value_t, values_t}; use itertools::Itertools; use std::{ + fmt::Write, io::{self, BufRead}, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, str::FromStr, @@ -171,6 +172,17 @@ impl Command for Relay { .default_value("any") .possible_values(&["any", "4", "6"]), ) + .arg( + clap::Arg::with_name("entry location") + .help("Entry endpoint to use. This can be 'any', 'none', or \ + any location that is valid with 'set location', \ + such as 'se got'.") + .default_value("none") + .long("entry-location") + .multiple(true) + .min_values(1) + .max_values(3), + ) ) ) .subcommand(clap::SubCommand::with_name("tunnel-protocol") @@ -415,7 +427,7 @@ impl Relay { } async fn set_location(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { - let location_constraint = location::get_constraint(matches); + let location_constraint = location::get_constraint_from_args(matches); let mut found = false; if !location_constraint.country.is_empty() { @@ -517,6 +529,8 @@ impl Relay { async fn set_wireguard_constraints(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { let port = parse_port_constraint(matches.value_of("port").unwrap())?; let ip_version = parse_ip_version_constraint(matches.value_of("ip version").unwrap()); + let entry_location = + parse_entry_location_constraint(matches.values_of("entry location").unwrap()); self.update_constraints(RelaySettingsUpdate { r#type: Some(relay_settings_update::Type::Normal( @@ -526,6 +540,7 @@ impl Relay { ip_version: ip_version.option().map(|protocol| IpVersionConstraint { protocol: protocol as i32, }), + entry_location, }), ..Default::default() }, @@ -719,7 +734,7 @@ impl Relay { fn format_wireguard_constraints(constraints: Option<&WireguardConstraints>) -> String { if let Some(constraints) = constraints { - format!( + let mut out = format!( "{} over {}", Self::format_port(constraints.port), Self::format_ip_version( @@ -728,7 +743,18 @@ impl Relay { .clone() .map(|protocol| IpVersion::from_i32(protocol.protocol).unwrap()) ) - ) + ); + + if let Some(ref entry) = constraints.entry_location { + write!( + &mut out, + " (via {})", + location::format_location(Some(entry)) + ) + .unwrap(); + } + + out } else { "any port over IPv4 or IPv6".to_string() } @@ -800,3 +826,19 @@ fn parse_ip_version_constraint(raw_protocol: &str) -> Constraint<IpVersion> { _ => unreachable!(), } } + +fn parse_entry_location_constraint<'a, T: Iterator<Item = &'a str>>( + mut location: T, +) -> Option<RelayLocation> { + let country = location.next().unwrap(); + + if country == "none" { + return None; + } + + Some(location::get_constraint( + country, + location.next(), + location.next(), + )) +} diff --git a/mullvad-cli/src/format.rs b/mullvad-cli/src/format.rs index 250bc70ba7..52014076c3 100644 --- a/mullvad-cli/src/format.rs +++ b/mullvad-cli/src/format.rs @@ -57,9 +57,10 @@ pub fn print_state(state: &TunnelState) { } fn format_endpoint(endpoint: &TunnelEndpoint) -> String { + let tunnel_type = TunnelType::from_i32(endpoint.tunnel_type).expect("invalid tunnel protocol"); let mut out = format!( "{} {} over {}", - match TunnelType::from_i32(endpoint.tunnel_type).expect("invalid tunnel protocol") { + match tunnel_type { TunnelType::Wireguard => "WireGuard", TunnelType::Openvpn => "OpenVPN", }, @@ -69,20 +70,39 @@ fn format_endpoint(endpoint: &TunnelEndpoint) -> String { ), ); - if let Some(ref proxy) = endpoint.proxy { - write!( - &mut out, - " via {} {} over {}", - match ProxyType::from_i32(proxy.proxy_type).expect("invalid proxy type") { - ProxyType::Shadowsocks => "Shadowsocks", - ProxyType::Custom => "custom bridge", - }, - proxy.address, - format_protocol( - TransportProtocol::from_i32(proxy.protocol).expect("invalid transport protocol") - ), - ) - .unwrap(); + match tunnel_type { + TunnelType::Openvpn => { + if let Some(ref proxy) = endpoint.proxy { + write!( + &mut out, + " via {} {} over {}", + match ProxyType::from_i32(proxy.proxy_type).expect("invalid proxy type") { + ProxyType::Shadowsocks => "Shadowsocks", + ProxyType::Custom => "custom bridge", + }, + proxy.address, + format_protocol( + TransportProtocol::from_i32(proxy.protocol) + .expect("invalid transport protocol") + ), + ) + .unwrap(); + } + } + TunnelType::Wireguard => { + if let Some(ref entry_endpoint) = endpoint.entry_endpoint { + write!( + &mut out, + " via {} over {}", + entry_endpoint.address, + format_protocol( + TransportProtocol::from_i32(entry_endpoint.protocol) + .expect("invalid transport protocol") + ) + ) + .unwrap(); + } + } } out diff --git a/mullvad-cli/src/location.rs b/mullvad-cli/src/location.rs index 09f39720ba..cadeaa24fe 100644 --- a/mullvad-cli/src/location.rs +++ b/mullvad-cli/src/location.rs @@ -22,11 +22,22 @@ pub fn get_subcommand() -> clap::App<'static, 'static> { ) } -pub fn get_constraint(matches: &clap::ArgMatches<'_>) -> RelayLocation { - let country_original = matches.value_of("country").unwrap(); +pub fn get_constraint_from_args(matches: &clap::ArgMatches<'_>) -> RelayLocation { + let country = matches.value_of("country").unwrap(); + let city = matches.value_of("city"); + let hostname = matches.value_of("hostname"); + get_constraint(country, city, hostname) +} + +pub fn get_constraint<T: AsRef<str>>( + country: T, + city: Option<T>, + hostname: Option<T>, +) -> RelayLocation { + let country_original = country.as_ref(); let country = country_original.to_lowercase(); - let city = matches.value_of("city").map(str::to_lowercase); - let hostname = matches.value_of("hostname").map(str::to_lowercase); + let city = city.map(|s| s.as_ref().to_lowercase()); + let hostname = hostname.map(|s| s.as_ref().to_lowercase()); match (country_original, city, hostname) { ("any", None, None) => RelayLocation::default(), @@ -81,16 +92,16 @@ pub fn format_providers(providers: &Vec<String>) -> String { } } -fn country_code_validator(code: String) -> std::result::Result<(), String> { - if code.len() == 2 || code == "any" { +pub fn country_code_validator<T: AsRef<str>>(code: T) -> std::result::Result<(), String> { + if code.as_ref().len() == 2 || code.as_ref() == "any" { Ok(()) } else { Err(String::from("Country codes must be two letters, or 'any'.")) } } -fn city_code_validator(code: String) -> std::result::Result<(), String> { - if code.len() == 3 { +pub fn city_code_validator<T: AsRef<str>>(code: T) -> std::result::Result<(), String> { + if code.as_ref().len() == 3 { Ok(()) } else { Err(String::from("City codes must be three letters")) diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index d44ce55153..546f06647b 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -116,6 +116,9 @@ pub enum Error { #[error(display = "No bridge available")] NoBridgeAvailable, + #[error(display = "No matching entry relay was found")] + NoEntryRelayAvailable, + #[error(display = "No account token is set")] NoAccountToken, @@ -1062,6 +1065,28 @@ where ipv4_gateway, ipv6_gateway, } => { + let entry_peer = match self.settings.get_relay_settings() { + RelaySettings::Normal(ref relay_constraints) + if relay_constraints + .wireguard_constraints + .entry_location + .is_some() => + { + Some( + self.relay_selector + .get_tunnel_entry_endpoint(&peer, relay_constraints, retry_attempt) + .and_then(|(_relay, mullvad_endpoint)| match mullvad_endpoint { + MullvadEndpoint::Wireguard { peer, .. } => Some(peer), + _ => None, + }) + .ok_or(Error::NoEntryRelayAvailable)?, + ) + } + _ => None, + }; + let exit_peer = entry_peer.as_ref().map(|_| peer.clone()); + let entry_peer = entry_peer.unwrap_or(peer); + let wg_data = self .account_history .get(&account_token) @@ -1079,7 +1104,8 @@ where Ok(wireguard::TunnelParameters { connection: wireguard::ConnectionConfig { tunnel, - peer, + peer: entry_peer, + exit_peer, ipv4_gateway, ipv6_gateway: Some(ipv6_gateway), }, diff --git a/mullvad-daemon/src/relays.rs b/mullvad-daemon/src/relays.rs index 54d664b640..62489255ce 100644 --- a/mullvad-daemon/src/relays.rs +++ b/mullvad-daemon/src/relays.rs @@ -7,6 +7,7 @@ use futures::{ future::{Fuse, FusedFuture}, FutureExt, SinkExt, StreamExt, }; +use ipnetwork::IpNetwork; use log::{debug, error, info, warn}; use mullvad_rpc::{rest::MullvadRestHandle, RelayListProxy}; use mullvad_types::{ @@ -50,6 +51,13 @@ const UPDATE_INTERVAL: Duration = Duration::from_secs(60 * 60); const EXPONENTIAL_BACKOFF_INITIAL: Duration = Duration::from_secs(16); const EXPONENTIAL_BACKOFF_FACTOR: u32 = 8; +const DEFAULT_WIREGUARD_PORT: u16 = 51820; +const WIREGUARD_EXIT_CONSTRAINTS: WireguardConstraints = WireguardConstraints { + port: Constraint::Only(DEFAULT_WIREGUARD_PORT), + ip_version: Constraint::Only(IpVersion::V4), + entry_location: None, +}; + #[derive(err_derive::Error, Debug)] #[error(no_from)] pub enum Error { @@ -222,8 +230,16 @@ impl RelaySelector { retry_attempt: u32, wg_key_exists: bool, ) -> Result<(Relay, MullvadEndpoint), Error> { + let mut relay_constraints = relay_constraints.clone(); + if relay_constraints + .wireguard_constraints + .entry_location + .is_some() + { + relay_constraints.wireguard_constraints = WIREGUARD_EXIT_CONSTRAINTS; + } let preferred_constraints = self.preferred_constraints( - relay_constraints, + &relay_constraints, bridge_state, retry_attempt, wg_key_exists, @@ -234,7 +250,8 @@ impl RelaySelector { retry_attempt ); Ok((relay, endpoint)) - } else if let Some((relay, endpoint)) = self.get_tunnel_endpoint_internal(relay_constraints) + } else if let Some((relay, endpoint)) = + self.get_tunnel_endpoint_internal(&relay_constraints) { debug!( "Relay matched on second preference for retry attempt {}", @@ -242,7 +259,7 @@ impl RelaySelector { ); Ok((relay, endpoint)) } else { - warn!("No relays matching {}", relay_constraints); + warn!("No relays matching {}", &relay_constraints); Err(Error::NoRelay) } } @@ -310,7 +327,7 @@ impl RelaySelector { } Constraint::Only(TunnelType::Wireguard) => { relay_constraints.wireguard_constraints = - original_constraints.wireguard_constraints; + original_constraints.wireguard_constraints.clone(); // This ensures that if after the first 2 failed attempts the daemon does not // connect, then afterwards 2 of each 4 successive attempts will try to connect on // port 53. @@ -323,6 +340,66 @@ impl RelaySelector { relay_constraints } + pub fn get_tunnel_entry_endpoint( + &mut self, + exit_peer: &wireguard::PeerConfig, + relay_constraints: &RelayConstraints, + retry_attempt: u32, + ) -> Option<(Relay, MullvadEndpoint)> { + let entry_location = relay_constraints + .wireguard_constraints + .entry_location + .clone()?; + let entry_constraints = RelayConstraints { + location: entry_location, + tunnel_protocol: Constraint::Only(TunnelType::Wireguard), + ..relay_constraints.clone() + }; + let entry_constraints = + self.preferred_constraints(&entry_constraints, BridgeState::Off, retry_attempt, true); + + let exit_peer_ip = exit_peer.endpoint.ip(); + let matching_relays: Vec<Relay> = self + .parsed_relays + .lock() + .relays() + .iter() + .filter(|relay| { + relay.active + && exit_peer_ip != IpAddr::V4(relay.ipv4_addr_in) + && Some(exit_peer_ip) != relay.ipv6_addr_in.map(IpAddr::V6) + }) + .filter_map(|relay| Self::matching_relay(relay, &entry_constraints)) + .collect(); + + let mut endpoint = self + .pick_random_relay(&matching_relays) + .and_then(|selected_relay| { + let endpoint = self.get_random_tunnel(&selected_relay, &entry_constraints); + let addr_in = endpoint + .as_ref() + .map(|endpoint| endpoint.to_endpoint().address.ip()) + .unwrap_or(IpAddr::from(selected_relay.ipv4_addr_in)); + info!( + "Selected entry relay {} at {}", + selected_relay.hostname, addr_in + ); + endpoint.map(|endpoint| (selected_relay.clone(), endpoint)) + })?; + + match endpoint.1 { + MullvadEndpoint::Wireguard { ref mut peer, .. } => { + peer.allowed_ips = vec![IpNetwork::from(exit_peer.endpoint.ip())]; + } + _ => { + log::error!("BUG: Endpoint must be WireGuard endpoint"); + return None; + } + } + + Some(endpoint) + } + pub fn get_auto_proxy_settings( &mut self, bridge_constraints: &InternalBridgeConstraints, @@ -493,7 +570,7 @@ impl RelaySelector { relay.tunnels = RelayTunnels { wireguard: Self::matching_wireguard_tunnels( &relay.tunnels, - constraints.wireguard_constraints, + &constraints.wireguard_constraints, ), openvpn: Self::matching_openvpn_tunnels( &relay.tunnels, @@ -507,7 +584,7 @@ impl RelaySelector { relay.tunnels = RelayTunnels { wireguard: Self::matching_wireguard_tunnels( &relay.tunnels, - constraints.wireguard_constraints, + &constraints.wireguard_constraints, ), openvpn: vec![], }; @@ -580,7 +657,7 @@ impl RelaySelector { fn matching_wireguard_tunnels( tunnels: &RelayTunnels, - constraints: WireguardConstraints, + constraints: &WireguardConstraints, ) -> Vec<WireguardEndpointData> { tunnels .wireguard @@ -660,7 +737,7 @@ impl RelaySelector { .choose(&mut self.rng) .cloned() .and_then(|wg_tunnel| { - self.wg_data_to_endpoint(relay, wg_tunnel, constraints.wireguard_constraints) + self.wg_data_to_endpoint(relay, wg_tunnel, &constraints.wireguard_constraints) }) }; @@ -689,7 +766,7 @@ impl RelaySelector { &mut self, relay: &Relay, data: WireguardEndpointData, - constraints: WireguardConstraints, + constraints: &WireguardConstraints, ) -> Option<MullvadEndpoint> { let host = self.get_address_for_wireguard_relay(relay, constraints)?; let port = self.get_port_for_wireguard_relay(&data, constraints)?; @@ -709,7 +786,7 @@ impl RelaySelector { fn get_address_for_wireguard_relay( &mut self, relay: &Relay, - constraints: WireguardConstraints, + constraints: &WireguardConstraints, ) -> Option<IpAddr> { match constraints.ip_version { Constraint::Any | Constraint::Only(IpVersion::V4) => Some(relay.ipv4_addr_in.into()), @@ -720,7 +797,7 @@ impl RelaySelector { fn get_port_for_wireguard_relay( &mut self, data: &WireguardEndpointData, - constraints: WireguardConstraints, + constraints: &WireguardConstraints, ) -> Option<u16> { match constraints.port { Constraint::Any => { diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 41ba8c96a9..faa5a73ffd 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -170,6 +170,7 @@ message TunnelEndpoint { TransportProtocol protocol = 2; TunnelType tunnel_type = 3; ProxyEndpoint proxy = 4; + Endpoint entry_endpoint = 5; } enum ProxyType { @@ -177,6 +178,11 @@ enum ProxyType { CUSTOM = 1; } +message Endpoint { + string address = 1; + TransportProtocol protocol = 2; +} + message ProxyEndpoint { string address = 1; TransportProtocol protocol = 2; @@ -317,6 +323,7 @@ message WireguardConstraints { // NOTE: optional uint32 port = 1; IpVersionConstraint ip_version = 2; + RelayLocation entry_location = 3; } message CustomRelaySettings { diff --git a/mullvad-management-interface/src/types.rs b/mullvad-management-interface/src/types.rs index aff3a30789..7d219e0b4a 100644 --- a/mullvad-management-interface/src/types.rs +++ b/mullvad-management-interface/src/types.rs @@ -40,6 +40,10 @@ impl From<talpid_types::net::TunnelEndpoint> for TunnelEndpoint { net::proxy::ProxyType::Custom => i32::from(ProxyType::Custom), }, }), + entry_endpoint: endpoint.entry_endpoint.map(|entry| Endpoint { + address: entry.address.to_string(), + protocol: i32::from(TransportProtocol::from(entry.protocol)), + }), } } } @@ -311,6 +315,25 @@ impl From<IpVersion> for IpVersionConstraint { } } +impl + From< + mullvad_types::relay_constraints::Constraint< + mullvad_types::relay_constraints::LocationConstraint, + >, + > for RelayLocation +{ + fn from( + location: mullvad_types::relay_constraints::Constraint< + mullvad_types::relay_constraints::LocationConstraint, + >, + ) -> Self { + location + .option() + .map(RelayLocation::from) + .unwrap_or_default() + } +} + impl From<mullvad_types::relay_constraints::LocationConstraint> for RelayLocation { fn from(location: mullvad_types::relay_constraints::LocationConstraint) -> Self { use mullvad_types::relay_constraints::LocationConstraint; @@ -450,6 +473,10 @@ impl From<mullvad_types::relay_constraints::RelaySettings> for RelaySettings { .option() .map(IpVersion::from) .map(IpVersionConstraint::from), + entry_location: constraints + .wireguard_constraints + .entry_location + .map(RelayLocation::from), }), openvpn_constraints: Some(OpenvpnConstraints { @@ -772,6 +799,7 @@ impl TryFrom<RelaySettingsUpdate> for mullvad_types::relay_constraints::RelaySet ))? .into(), }, + exit_peer: None, ipv4_gateway, ipv6_gateway, }) @@ -881,6 +909,11 @@ impl TryFrom<RelaySettingsUpdate> for mullvad_types::relay_constraints::RelaySet Constraint::Any }, ip_version: Constraint::from(ip_version), + entry_location: constraints.entry_location.map( + Constraint::< + mullvad_types::relay_constraints::LocationConstraint, + >::from, + ), } }), openvpn_constraints: settings.openvpn_constraints.map(|constraints| { diff --git a/mullvad-types/src/relay_constraints.rs b/mullvad-types/src/relay_constraints.rs index d32608124d..075e3b816e 100644 --- a/mullvad-types/src/relay_constraints.rs +++ b/mullvad-types/src/relay_constraints.rs @@ -434,11 +434,12 @@ impl Match<OpenVpnEndpointData> for OpenVpnConstraints { } /// [`Constraint`]s applicable to WireGuard relay servers. -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(default)] pub struct WireguardConstraints { pub port: Constraint<u16>, pub ip_version: Constraint<IpVersion>, + pub entry_location: Option<Constraint<LocationConstraint>>, } impl fmt::Display for WireguardConstraints { @@ -449,8 +450,13 @@ impl fmt::Display for WireguardConstraints { } write!(f, " over ")?; match self.ip_version { - Constraint::Any => write!(f, "IPv4 or IPv6"), - Constraint::Only(protocol) => write!(f, "{}", protocol), + Constraint::Any => write!(f, "IPv4 or IPv6")?, + Constraint::Only(protocol) => write!(f, "{}", protocol)?, + } + if let Some(Constraint::Only(ref entry)) = self.entry_location { + write!(f, " (via {})", entry) + } else { + Ok(()) } } } diff --git a/talpid-core/src/firewall/linux.rs b/talpid-core/src/firewall/linux.rs index a0c767d926..755111a31b 100644 --- a/talpid-core/src/firewall/linux.rs +++ b/talpid-core/src/firewall/linux.rs @@ -577,7 +577,7 @@ impl<'a> PolicyBatch<'a> { let allow_lan = match policy { FirewallPolicy::Connecting { peer_endpoint, - tunnel_interface: _, + tunnel_interface, pingable_hosts, allow_lan, allowed_endpoint, @@ -589,6 +589,10 @@ impl<'a> PolicyBatch<'a> { // Important to block DNS after allow relay rule (so the relay can operate // over port 53) but before allow LAN (so DNS does not leak to the LAN) self.add_drop_dns_rule(); + + if let Some(tunnel_interface) = tunnel_interface { + self.add_allow_tunnel_rules(tunnel_interface)?; + } *allow_lan } FirewallPolicy::Connected { @@ -603,7 +607,7 @@ impl<'a> PolicyBatch<'a> { // 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(); - self.add_allow_tunnel_rules(tunnel)?; + self.add_allow_tunnel_rules(&tunnel.interface)?; if *allow_lan { self.add_block_cve_2019_14899(tunnel); } @@ -820,22 +824,22 @@ impl<'a> PolicyBatch<'a> { } } - fn add_allow_tunnel_rules(&mut self, tunnel: &tunnel::TunnelMetadata) -> Result<()> { + fn add_allow_tunnel_rules(&mut self, tunnel_interface: &str) -> Result<()> { self.batch.add( - &allow_interface_rule(&self.out_chain, Direction::Out, &tunnel.interface[..])?, + &allow_interface_rule(&self.out_chain, Direction::Out, tunnel_interface)?, nftnl::MsgType::Add, ); self.batch.add( - &allow_interface_rule(&self.forward_chain, Direction::Out, &tunnel.interface[..])?, + &allow_interface_rule(&self.forward_chain, Direction::Out, tunnel_interface)?, nftnl::MsgType::Add, ); self.batch.add( - &allow_interface_rule(&self.in_chain, Direction::In, &tunnel.interface[..])?, + &allow_interface_rule(&self.in_chain, Direction::In, tunnel_interface)?, nftnl::MsgType::Add, ); let mut interface_rule = Rule::new(&self.forward_chain); - check_iface(&mut interface_rule, Direction::In, &tunnel.interface)?; + check_iface(&mut interface_rule, Direction::In, tunnel_interface)?; interface_rule.add_expr(&nft_expr!(ct state)); let allowed_states = nftnl::expr::ct::States::ESTABLISHED.bits(); interface_rule.add_expr(&nft_expr!(bitwise mask allowed_states, xor 0u32)); diff --git a/talpid-core/src/firewall/macos.rs b/talpid-core/src/firewall/macos.rs index ddc6815990..f3048d61a8 100644 --- a/talpid-core/src/firewall/macos.rs +++ b/talpid-core/src/firewall/macos.rs @@ -97,7 +97,7 @@ impl Firewall { match policy { FirewallPolicy::Connecting { peer_endpoint, - tunnel_interface: _, + tunnel_interface, allow_lan, allowed_endpoint, pingable_hosts, @@ -105,10 +105,16 @@ impl Firewall { let mut rules = vec![self.get_allow_relay_rule(peer_endpoint)?]; rules.push(self.get_allowed_endpoint_rule(allowed_endpoint)?); rules.extend(self.get_allow_pingable_hosts(&pingable_hosts)?); + + // Important to block DNS after allow relay rule (so the relay can operate + // over port 53) but before allow LAN (so DNS does not leak to the LAN) + rules.append(&mut self.get_block_dns_rules()?); + + if let Some(tunnel_interface) = tunnel_interface { + rules.push(self.get_allow_tunnel_rule(tunnel_interface.as_str())?); + } + if allow_lan { - // Important to block DNS after allow relay rule (so the relay can operate - // over port 53) but before allow LAN (so DNS does not leak to the LAN) - rules.append(&mut self.get_block_dns_rules()?); rules.append(&mut self.get_allow_lan_rules()?); } Ok(rules) diff --git a/talpid-core/src/firewall/windows.rs b/talpid-core/src/firewall/windows.rs index 34e0481610..6a4fdad186 100644 --- a/talpid-core/src/firewall/windows.rs +++ b/talpid-core/src/firewall/windows.rs @@ -154,7 +154,7 @@ impl Firewall { &mut self, endpoint: &Endpoint, winfw_settings: &WinFwSettings, - _tunnel_iface_alias: &Option<String>, + tunnel_interface: &Option<String>, allowed_endpoint: &Endpoint, pingable_hosts: &Vec<IpAddr>, relay_client: &Path, @@ -196,11 +196,21 @@ impl Firewall { protocol: WinFwProt::from(allowed_endpoint.protocol), }); + let interface_wstr = tunnel_interface + .as_ref() + .map(|alias| WideCString::new(alias.encode_utf16().collect::<Vec<_>>()).unwrap()); + let interface_wstr_ptr = if let Some(ref wstr) = interface_wstr { + wstr.as_ptr() + } else { + ptr::null() + }; + unsafe { WinFw_ApplyPolicyConnecting( winfw_settings, &winfw_relay, relay_client.as_ptr(), + interface_wstr_ptr, pingable_hosts.as_ptr(), winfw_allowed_endpoint.as_ptr(), ) @@ -436,6 +446,7 @@ mod winfw { settings: &WinFwSettings, relay: &WinFwEndpoint, relayClient: *const libc::wchar_t, + tunnelIfaceAlias: *const libc::wchar_t, pingable_hosts: *const WinFwPingableHosts, allowed_endpoint: *const WinFwEndpoint, ) -> WinFwPolicyStatus; diff --git a/talpid-core/src/tunnel/wireguard/config.rs b/talpid-core/src/tunnel/wireguard/config.rs index eddc51b25b..ae82483c66 100644 --- a/talpid-core/src/tunnel/wireguard/config.rs +++ b/talpid-core/src/tunnel/wireguard/config.rs @@ -47,10 +47,13 @@ impl Config { /// Constructs a Config from parameters pub fn from_parameters(params: &wireguard::TunnelParameters) -> Result<Config, Error> { let tunnel = params.connection.tunnel.clone(); - let peer = vec![params.connection.peer.clone()]; + let mut peers = vec![params.connection.peer.clone()]; + if let Some(exit_peer) = ¶ms.connection.exit_peer { + peers.push(exit_peer.clone()); + } Self::new( tunnel, - peer, + peers, ¶ms.connection, ¶ms.options, ¶ms.generic_options, diff --git a/talpid-core/src/tunnel/wireguard/mod.rs b/talpid-core/src/tunnel/wireguard/mod.rs index c51fdf57f5..7585793a01 100644 --- a/talpid-core/src/tunnel/wireguard/mod.rs +++ b/talpid-core/src/tunnel/wireguard/mod.rs @@ -3,6 +3,7 @@ use self::config::Config; use super::tun_provider; use super::{tun_provider::TunProvider, TunnelEvent, TunnelMetadata}; use crate::routing::{self, RequiredRoute}; +use cfg_if::cfg_if; use futures::future::abortable; #[cfg(target_os = "linux")] use lazy_static::lazy_static; @@ -342,9 +343,25 @@ impl WireguardMonitor { } fn get_routes(iface_name: &str, config: &Config) -> HashSet<RequiredRoute> { + #[cfg(target_os = "linux")] + use netlink_packet_route::rtnl::constants::RT_TABLE_MAIN; + let node = routing::Node::device(iface_name.to_string()); let mut routes: HashSet<RequiredRoute> = Self::get_tunnel_routes(config) - .map(|network| RequiredRoute::new(network, node.clone())) + .map(|network| { + cfg_if! { + if #[cfg(target_os = "linux")] { + if network.prefix() == 0 { + RequiredRoute::new(network, node.clone()) + } else { + RequiredRoute::new(network, node.clone()) + .table(u32::from(RT_TABLE_MAIN)) + } + } else { + RequiredRoute::new(network, node.clone()) + } + } + }) .collect(); // route endpoints with specific routes @@ -360,8 +377,6 @@ impl WireguardMonitor { // using `mullvad-exclude` #[cfg(target_os = "linux")] { - use netlink_packet_route::rtnl::constants::RT_TABLE_MAIN; - routes.insert( RequiredRoute::new( ipnetwork::Ipv4Network::from(config.ipv4_gateway).into(), diff --git a/talpid-types/src/net/mod.rs b/talpid-types/src/net/mod.rs index adea4fd512..c168a58f4e 100644 --- a/talpid-types/src/net/mod.rs +++ b/talpid-types/src/net/mod.rs @@ -28,11 +28,19 @@ impl TunnelParameters { tunnel_type: TunnelType::OpenVpn, endpoint: params.config.endpoint, proxy: params.proxy.as_ref().map(|proxy| proxy.get_endpoint()), + entry_endpoint: None, }, TunnelParameters::Wireguard(params) => TunnelEndpoint { tunnel_type: TunnelType::Wireguard, - endpoint: params.connection.get_endpoint(), + endpoint: params + .connection + .get_exit_endpoint() + .unwrap_or(params.connection.get_endpoint()), proxy: None, + entry_endpoint: params + .connection + .get_exit_endpoint() + .map(|_| params.connection.get_endpoint()), }, } } @@ -49,10 +57,11 @@ impl TunnelParameters { } } - pub fn get_proxy_endpoint(&self) -> Option<openvpn::ProxySettings> { + // Returns the exit endpoint, if it differs from the next hop endpoint + pub fn get_exit_hop_endpoint(&self) -> Option<Endpoint> { match self { - TunnelParameters::OpenVpn(params) => params.proxy.clone(), - _ => None, + TunnelParameters::OpenVpn(_params) => None, + TunnelParameters::Wireguard(params) => params.connection.get_exit_endpoint(), } } @@ -108,17 +117,28 @@ pub struct TunnelEndpoint { pub tunnel_type: TunnelType, #[cfg_attr(target_os = "android", jnix(skip))] pub proxy: Option<proxy::ProxyEndpoint>, + #[cfg_attr(target_os = "android", jnix(skip))] + pub entry_endpoint: Option<Endpoint>, } impl fmt::Display for TunnelEndpoint { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!(f, "{} - {}", self.tunnel_type, self.endpoint)?; - if let Some(ref proxy) = self.proxy { - write!( - f, - " via {} {} over {}", - proxy.proxy_type, proxy.endpoint.address, proxy.endpoint.protocol - )?; + match self.tunnel_type { + TunnelType::OpenVpn => { + if let Some(ref proxy) = self.proxy { + write!( + f, + " via {} {} over {}", + proxy.proxy_type, proxy.endpoint.address, proxy.endpoint.protocol + )?; + } + } + TunnelType::Wireguard => { + if let Some(ref entry_endpoint) = self.entry_endpoint { + write!(f, " via {}", entry_endpoint)?; + } + } } Ok(()) } diff --git a/talpid-types/src/net/wireguard.rs b/talpid-types/src/net/wireguard.rs index 60a0a29a6d..1c92052762 100644 --- a/talpid-types/src/net/wireguard.rs +++ b/talpid-types/src/net/wireguard.rs @@ -25,6 +25,7 @@ pub struct TunnelParameters { pub struct ConnectionConfig { pub tunnel: TunnelConfig, pub peer: PeerConfig, + pub exit_peer: Option<PeerConfig>, /// Gateway used by the tunnel (a private address). pub ipv4_gateway: Ipv4Addr, pub ipv6_gateway: Option<Ipv6Addr>, @@ -37,11 +38,18 @@ impl ConnectionConfig { protocol: self.peer.protocol, } } + + pub fn get_exit_endpoint(&self) -> Option<Endpoint> { + self.exit_peer.as_ref().map(|peer| Endpoint { + address: peer.endpoint, + protocol: peer.protocol, + }) + } } #[derive(Clone, Eq, PartialEq, Deserialize, Serialize, Debug, Hash)] pub struct PeerConfig { - /// Public key corresponding to the private key in [`TunnelConfig`]. + /// Peer's public key. pub public_key: PublicKey, /// Addresses that may be routed to the peer. Use `0.0.0.0/0` to route everything. pub allowed_ips: Vec<IpNetwork>, diff --git a/windows/winfw/src/winfw/fwcontext.cpp b/windows/winfw/src/winfw/fwcontext.cpp index 793f8c917d..d89437d699 100644 --- a/windows/winfw/src/winfw/fwcontext.cpp +++ b/windows/winfw/src/winfw/fwcontext.cpp @@ -178,6 +178,7 @@ bool FwContext::applyPolicyConnecting const WinFwSettings &settings, const WinFwEndpoint &relay, const std::wstring &relayClient, + const std::optional<std::wstring> &tunnelInterfaceAlias, const std::optional<PingableHosts> &pingableHosts, const std::optional<WinFwEndpoint> &allowedEndpoint ) @@ -193,6 +194,17 @@ bool FwContext::applyPolicyConnecting AppendAllowedEndpointRules(ruleset, allowedEndpoint.value()); } + if (tunnelInterfaceAlias.has_value()) + { + ruleset.emplace_back(std::make_unique<baseline::PermitVpnTunnel>( + *tunnelInterfaceAlias + )); + + ruleset.emplace_back(std::make_unique<baseline::PermitVpnTunnelService>( + *tunnelInterfaceAlias + )); + } + // // Permit pinging the gateway inside the tunnel. // diff --git a/windows/winfw/src/winfw/fwcontext.h b/windows/winfw/src/winfw/fwcontext.h index bbbb1de485..cff3e3c823 100644 --- a/windows/winfw/src/winfw/fwcontext.h +++ b/windows/winfw/src/winfw/fwcontext.h @@ -35,6 +35,7 @@ public: const WinFwSettings &settings, const WinFwEndpoint &relay, const std::wstring &relayClient, + const std::optional<std::wstring> &tunnelInterfaceAlias, const std::optional<PingableHosts> &pingableHosts, const std::optional<WinFwEndpoint> &allowedEndpoint ); diff --git a/windows/winfw/src/winfw/winfw.cpp b/windows/winfw/src/winfw/winfw.cpp index a3ad1737ac..119edc4ca6 100644 --- a/windows/winfw/src/winfw/winfw.cpp +++ b/windows/winfw/src/winfw/winfw.cpp @@ -260,6 +260,7 @@ WinFw_ApplyPolicyConnecting( const WinFwSettings *settings, const WinFwEndpoint *relay, const wchar_t *relayClient, + const wchar_t *tunnelInterfaceAlias, const PingableHosts *pingableHosts, const WinFwEndpoint *allowedEndpoint ) @@ -290,6 +291,7 @@ WinFw_ApplyPolicyConnecting( *settings, *relay, relayClient, + tunnelInterfaceAlias != nullptr ? std::make_optional(tunnelInterfaceAlias) : std::nullopt, ConvertPingableHosts(pingableHosts), MakeOptional(allowedEndpoint) ) ? WINFW_POLICY_STATUS_SUCCESS : WINFW_POLICY_STATUS_GENERAL_FAILURE; diff --git a/windows/winfw/src/winfw/winfw.h b/windows/winfw/src/winfw/winfw.h index 308bb32645..5065582e29 100644 --- a/windows/winfw/src/winfw/winfw.h +++ b/windows/winfw/src/winfw/winfw.h @@ -158,6 +158,7 @@ WinFw_ApplyPolicyConnecting( const WinFwSettings *settings, const WinFwEndpoint *relay, const wchar_t *relayClient, + const wchar_t *tunnelInterfaceAlias, const PingableHosts *pingableHosts, const WinFwEndpoint *allowedEndpoint ); diff --git a/windows/winfw/src/winfw/winfw.vcxproj.filters b/windows/winfw/src/winfw/winfw.vcxproj.filters index 7a2aa85487..bb266aa8ff 100644 --- a/windows/winfw/src/winfw/winfw.vcxproj.filters +++ b/windows/winfw/src/winfw/winfw.vcxproj.filters @@ -55,15 +55,15 @@ <ClCompile Include="rules\shared.cpp"> <Filter>rules</Filter> </ClCompile> - <ClCompile Include="rules\multi\permitvpnrelay.cpp"> - <Filter>rules\multi</Filter> - </ClCompile> <ClCompile Include="rules\persistent\blockall.cpp"> <Filter>rules\persistent</Filter> </ClCompile> <ClCompile Include="rules\baseline\permitendpoint.cpp"> <Filter>rules\baseline</Filter> </ClCompile> + <ClCompile Include="rules\multi\permitvpnrelay.cpp"> + <Filter>rules\multi</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="stdafx.h" /> @@ -129,15 +129,15 @@ <ClInclude Include="rules\shared.h"> <Filter>rules</Filter> </ClInclude> - <ClInclude Include="rules\multi\permitvpnrelay.h"> - <Filter>rules\multi</Filter> - </ClInclude> <ClInclude Include="rules\persistent\blockall.h"> <Filter>rules\persistent</Filter> </ClInclude> <ClInclude Include="rules\baseline\permitendpoint.h"> <Filter>rules\baseline</Filter> </ClInclude> + <ClInclude Include="rules\multi\permitvpnrelay.h"> + <Filter>rules\multi</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <Filter Include="rules"> |
