diff options
21 files changed, 762 insertions, 287 deletions
diff --git a/Cargo.lock b/Cargo.lock index 63abc2bbab..2b11029e7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1890,19 +1890,6 @@ dependencies = [ [[package]] name = "nix" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e06129fb611568ef4e868c14b326274959aa70ff7776e9d55323531c374945" -dependencies = [ - "bitflags", - "cc", - "cfg-if 1.0.0", - "libc", - "memoffset", -] - -[[package]] -name = "nix" version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" @@ -3130,7 +3117,7 @@ dependencies = [ "triggered", "trust-dns-server", "tun", - "udp-over-tcp", + "tunnel-obfuscation", "uuid", "which", "widestring 0.5.1", @@ -3632,6 +3619,17 @@ dependencies = [ ] [[package]] +name = "tunnel-obfuscation" +version = "0.1.0" +dependencies = [ + "async-trait", + "err-derive", + "futures", + "tokio", + "udp-over-tcp", +] + +[[package]] name = "typenum" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3646,14 +3644,14 @@ checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" [[package]] name = "udp-over-tcp" version = "0.1.0" -source = "git+https://github.com/mullvad/udp-over-tcp?rev=1e27324362ed123b61fa2062b1599e5f9d569796#1e27324362ed123b61fa2062b1599e5f9d569796" +source = "git+https://github.com/mullvad/udp-over-tcp?rev=27b9519b63244736b6f3c7c4af60976c88dc6b95#27b9519b63244736b6f3c7c4af60976c88dc6b95" dependencies = [ "env_logger 0.8.4", "err-context", "futures", "lazy_static", "log", - "nix 0.20.2", + "nix 0.23.1", "structopt", "tokio", ] diff --git a/mullvad-api/src/relay_list.rs b/mullvad-api/src/relay_list.rs index 5a8a01836f..7f72767a1b 100644 --- a/mullvad-api/src/relay_list.rs +++ b/mullvad-api/src/relay_list.rs @@ -4,7 +4,7 @@ use crate::rest; use hyper::{header, Method, StatusCode}; use mullvad_types::{location, relay_list}; -use talpid_types::net::{wireguard, TransportProtocol}; +use talpid_types::net::wireguard; use std::{ collections::BTreeMap, @@ -182,7 +182,6 @@ impl ServerRelayList { ipv4_gateway, ipv6_gateway, public_key, - protocol: TransportProtocol::Udp, }; for mut wireguard_relay in relays { @@ -315,6 +314,7 @@ fn relay(relay: Relay, location: location::Location) -> relay_list::Relay { weight: relay.weight, tunnels: Default::default(), bridges: Default::default(), + obfuscators: Default::default(), location: Some(location), } } diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 27aea9b79a..50bea189ea 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -41,8 +41,8 @@ use mullvad_types::{ endpoint::MullvadEndpoint, location::{Coordinates, GeoIpLocation}, relay_constraints::{ - BridgeSettings, BridgeState, Constraint, InternalBridgeConstraints, RelaySettings, - RelaySettingsUpdate, + BridgeSettings, BridgeState, Constraint, InternalBridgeConstraints, ObfuscationSettings, + RelaySettings, RelaySettingsUpdate, SelectedObfuscation, }, relay_list::{Relay, RelayList}, settings::{DnsOptions, DnsState, Settings}, @@ -155,6 +155,9 @@ pub enum Error { #[error(display = "No bridge available")] NoBridgeAvailable, + #[error(display = "Failed to select a compatible obfuscator")] + NoObfuscator, + #[error(display = "No matching entry relay was found")] NoEntryRelayAvailable, @@ -318,6 +321,8 @@ pub enum DaemonCommand { /// Notify the split tunnel monitor that a volume was mounted or dismounted #[cfg(target_os = "windows")] CheckVolumes(ResponseTx<(), Error>), + /// Register settings for WireGuard obfuscator + SetObfuscationSettings(ResponseTx<(), settings::Error>, ObfuscationSettings), /// Makes the daemon exit the main loop and quit. Shutdown, /// Saves the target tunnel state and enters a blocking state. The state is restored @@ -1043,6 +1048,7 @@ where let result = self .create_tunnel_parameters( &exit_relay, + &entry_relay, endpoint, device.token, retry_attempt, @@ -1084,6 +1090,7 @@ where async fn create_tunnel_parameters( &mut self, relay: &Relay, + entry_relay: &Option<Relay>, endpoint: MullvadEndpoint, account_token: String, retry_attempt: u32, @@ -1169,6 +1176,29 @@ where wg_data.addresses.ipv6_address.ip().into(), ], }; + + let obfuscation = match self.settings.obfuscation_settings.selected_obfuscation { + SelectedObfuscation::Off | SelectedObfuscation::Auto + if !self + .relay_selector + .should_use_auto_obfuscator(retry_attempt) => + { + None + } + _ => { + let obfuscator = self + .relay_selector + .get_obfuscator( + &self.settings.obfuscation_settings, + entry_relay.as_ref().unwrap_or(relay), + &endpoint, + retry_attempt, + ) + .ok_or(Error::NoObfuscator)?; + Some(obfuscator) + } + }; + Ok(wireguard::TunnelParameters { connection: wireguard::ConnectionConfig { tunnel, @@ -1179,6 +1209,7 @@ where }, options: tunnel_options.wireguard.options, generic_options: tunnel_options.generic, + obfuscation, } .into()) } @@ -1280,6 +1311,9 @@ where UseWireGuardNt(tx, state) => self.on_use_wireguard_nt(tx, state).await, #[cfg(target_os = "windows")] CheckVolumes(tx) => self.on_check_volumes(tx).await, + SetObfuscationSettings(tx, settings) => { + self.on_set_obfuscation_settings(tx, settings).await + } Shutdown => self.trigger_shutdown_event(), PrepareRestart => self.on_prepare_restart(), #[cfg(target_os = "android")] @@ -2215,6 +2249,30 @@ where } } + async fn on_set_obfuscation_settings( + &mut self, + tx: ResponseTx<(), settings::Error>, + new_settings: ObfuscationSettings, + ) { + match self.settings.set_obfuscation_settings(new_settings).await { + Ok(settings_changed) => { + if settings_changed { + self.event_listener + .notify_settings(self.settings.to_settings()); + self.reconnect_tunnel(); + } + Self::oneshot_send(tx, Ok(()), "set_obfuscation_settings"); + } + Err(err) => { + log::error!( + "{}", + err.display_chain_with_msg("Failed to set obfuscation settings") + ); + Self::oneshot_send(tx, Err(err), "set_obfuscation_settings"); + } + } + } + async fn on_set_bridge_state( &mut self, tx: ResponseTx<(), settings::Error>, diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index 4005ff7df0..c361fa763f 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -13,7 +13,7 @@ use mullvad_paths; use mullvad_types::settings::DnsOptions; use mullvad_types::{ account::AccountToken, - relay_constraints::{BridgeSettings, BridgeState, RelaySettingsUpdate}, + relay_constraints::{BridgeSettings, BridgeState, ObfuscationSettings, RelaySettingsUpdate}, relay_list::RelayList, settings::Settings, states::{TargetState, TunnelState}, @@ -171,7 +171,8 @@ impl ManagementService for ManagementServiceImpl { ) -> ServiceResult<()> { log::debug!("update_relay_settings"); let (tx, rx) = oneshot::channel(); - let constraints_update = RelaySettingsUpdate::try_from(request.into_inner())?; + let constraints_update = + RelaySettingsUpdate::try_from(request.into_inner()).map_err(map_protobuf_type_err)?; let message = DaemonCommand::UpdateRelaySettings(tx, constraints_update); self.send_command_to_daemon(message)?; @@ -226,7 +227,8 @@ impl ManagementService for ManagementServiceImpl { &self, request: Request<types::BridgeSettings>, ) -> ServiceResult<()> { - let settings = BridgeSettings::try_from(request.into_inner())?; + let settings = + BridgeSettings::try_from(request.into_inner()).map_err(map_protobuf_type_err)?; log::debug!("set_bridge_settings({:?})", settings); @@ -238,8 +240,24 @@ impl ManagementService for ManagementServiceImpl { .map_err(map_settings_error) } + async fn set_obfuscation_settings( + &self, + request: Request<types::ObfuscationSettings>, + ) -> ServiceResult<()> { + let settings = + ObfuscationSettings::try_from(request.into_inner()).map_err(map_protobuf_type_err)?; + log::debug!("set_obfuscation_settings({:?})", settings); + let (tx, rx) = oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::SetObfuscationSettings(tx, settings))?; + let settings_result = self.wait_for_result(rx).await?; + settings_result + .map(Response::new) + .map_err(map_settings_error) + } + async fn set_bridge_state(&self, request: Request<types::BridgeState>) -> ServiceResult<()> { - let bridge_state = BridgeState::try_from(request.into_inner())?; + let bridge_state = + BridgeState::try_from(request.into_inner()).map_err(map_protobuf_type_err)?; log::debug!("set_bridge_state({:?})", bridge_state); let (tx, rx) = oneshot::channel(); @@ -350,7 +368,7 @@ impl ManagementService for ManagementServiceImpl { #[cfg(not(target_os = "android"))] async fn set_dns_options(&self, request: Request<types::DnsOptions>) -> ServiceResult<()> { - let options = DnsOptions::try_from(request.into_inner())?; + let options = DnsOptions::try_from(request.into_inner()).map_err(map_protobuf_type_err)?; log::debug!("set_dns_options({:?})", options); let (tx, rx) = oneshot::channel(); @@ -1003,3 +1021,9 @@ fn map_account_history_error(error: account_history::Error) -> Status { } } } + +fn map_protobuf_type_err(err: types::FromProtobufTypeError) -> Status { + match err { + types::FromProtobufTypeError::InvalidArgument(err) => Status::invalid_argument(err), + } +} diff --git a/mullvad-daemon/src/relays/matcher.rs b/mullvad-daemon/src/relays/matcher.rs index fb708d05fb..7d75141b16 100644 --- a/mullvad-daemon/src/relays/matcher.rs +++ b/mullvad-daemon/src/relays/matcher.rs @@ -2,7 +2,7 @@ use mullvad_types::{ endpoint::{MullvadEndpoint, MullvadWireguardEndpoint}, relay_constraints::{ Constraint, LocationConstraint, Match, OpenVpnConstraints, Providers, RelayConstraints, - TransportPort, WireguardConstraints, + WireguardConstraints, }, relay_list::{Relay, RelayTunnels, WireguardEndpointData}, }; @@ -167,7 +167,7 @@ pub struct WireguardMatcher { /// The peer is an already selected peer relay to be used with multihop. /// It's stored here so we can exclude it from further selections being made. pub peer: Option<Relay>, - pub port: Constraint<TransportPort>, + pub port: Constraint<u16>, pub ip_version: Constraint<IpVersion>, } @@ -183,7 +183,6 @@ impl WireguardMatcher { public_key: data.public_key, endpoint: SocketAddr::new(host, port), allowed_ips: all_of_the_internet(), - protocol: data.protocol, }; Some(MullvadEndpoint::Wireguard(MullvadWireguardEndpoint { peer: peer_config, @@ -201,12 +200,7 @@ impl WireguardMatcher { } fn get_port_for_wireguard_relay(&self, data: &WireguardEndpointData) -> Option<u16> { - match self - .port - .as_ref() - .map(|port| port.port) - .unwrap_or(Constraint::Any) - { + match self.port { Constraint::Any => { let get_port_amount = |range: &(u16, u16)| -> u64 { (1 + range.1 - range.0) as u64 }; @@ -257,18 +251,10 @@ impl Match<WireguardEndpointData> for WireguardMatcher { fn matches(&self, endpoint: &WireguardEndpointData) -> bool { match self.port { Constraint::Any => true, - Constraint::Only(TransportPort { port, protocol }) => { - if protocol != endpoint.protocol { - return false; - } - match port { - Constraint::Any => true, - Constraint::Only(port) => endpoint - .port_ranges - .iter() - .any(|range| (port >= range.0 && port <= range.1)), - } - } + Constraint::Only(port) => endpoint + .port_ranges + .iter() + .any(|range| (port >= range.0 && port <= range.1)), } } } @@ -303,16 +289,9 @@ impl TunnelMatcher for WireguardMatcher { } fn mullvad_endpoint(&self, relay: &Relay) -> Option<MullvadEndpoint> { - let valid_relays = relay + relay .tunnels .wireguard - .iter() - .filter(|tunnel| match self.port { - Constraint::Any => true, - Constraint::Only(port) => port.protocol == tunnel.protocol, - }) - .collect::<Vec<_>>(); - valid_relays .choose(&mut rand::thread_rng()) .and_then(|wg_tunnel| self.wg_data_to_endpoint(relay, (*wg_tunnel).clone())) } diff --git a/mullvad-daemon/src/relays/mod.rs b/mullvad-daemon/src/relays/mod.rs index 6f6a0279ba..31a27b6e97 100644 --- a/mullvad-daemon/src/relays/mod.rs +++ b/mullvad-daemon/src/relays/mod.rs @@ -9,21 +9,25 @@ use mullvad_types::{ location::{Coordinates, Location}, relay_constraints::{ BridgeState, Constraint, InternalBridgeConstraints, LocationConstraint, Match, - OpenVpnConstraints, Providers, RelayConstraints, Set, TransportPort, WireguardConstraints, + ObfuscationSettings, OpenVpnConstraints, Providers, RelayConstraints, SelectedObfuscation, + Set, TransportPort, Udp2TcpObfuscationSettings, WireguardConstraints, }, - relay_list::{Relay, RelayList, WireguardEndpointData}, + relay_list::{Relay, RelayList, Udp2TcpEndpointData}, }; use parking_lot::Mutex; use rand::{self, seq::SliceRandom, Rng}; use std::{ io, - net::IpAddr, + net::{IpAddr, SocketAddr}, path::Path, sync::Arc, time::{self, SystemTime}, }; use talpid_types::{ - net::{openvpn::ProxySettings, wireguard, IpVersion, TransportProtocol, TunnelType}, + net::{ + obfuscation::ObfuscatorConfig, openvpn::ProxySettings, wireguard, IpVersion, + TransportProtocol, TunnelType, + }, ErrorExt, }; @@ -43,13 +47,11 @@ const RELAYS_FILENAME: &str = "relays.json"; const DEFAULT_WIREGUARD_PORT: u16 = 51820; const WIREGUARD_EXIT_CONSTRAINTS: WireguardMatcher = WireguardMatcher { peer: None, - port: Constraint::Only(TransportPort { - protocol: TransportProtocol::Udp, - port: Constraint::Only(DEFAULT_WIREGUARD_PORT), - }), + port: Constraint::Only(DEFAULT_WIREGUARD_PORT), ip_version: Constraint::Only(IpVersion::V4), }; -const WIREGUARD_TCP_PORTS: [(u16, u16); 3] = [(80, 80), (443, 443), (5001, 5001)]; + +const UDP2TCP_PORTS: [u16; 3] = [80, 443, 5001]; #[derive(err_derive::Error, Debug)] #[error(no_from)] @@ -105,8 +107,25 @@ impl ParsedRelays { latitude, longitude, }); + Self::filter_invalid_relays(&mut relay_with_location); - Self::tack_on_wireguard_tcp_relays(&mut relay_with_location.tunnels.wireguard); + + // TODO: The WireGuard data is incorrectly modelled. + // Using a vector here suggests that a relay may use multiple key pairs at a + // time. This is incorrect and will never be the case. + // + // Currently, the `wireguard` vector will have 0 or 1 entries. + // This should be changed into e.g. using an Option<_> instead. + // + + if !relay.tunnels.wireguard.is_empty() { + for port in UDP2TCP_PORTS { + relay_with_location + .obfuscators + .udp2tcp + .push(Udp2TcpEndpointData { port }); + } + } relays.push(relay_with_location); } @@ -150,25 +169,6 @@ impl ParsedRelays { } } - /// Add synthesized WireGuard TCP endpoints to a relay - fn tack_on_wireguard_tcp_relays(endpoints: &mut Vec<WireguardEndpointData>) { - *endpoints = endpoints - .iter() - .cloned() - .map(|udp_endpoint| { - [ - WireguardEndpointData { - protocol: TransportProtocol::Tcp, - port_ranges: WIREGUARD_TCP_PORTS.to_vec(), - ..udp_endpoint.clone() - }, - udp_endpoint, - ] - }) - .flatten() - .collect(); - } - pub fn from_file(path: impl AsRef<Path>) -> Result<Self, Error> { log::debug!("Reading relays from {}", path.as_ref().display()); let (last_modified, file) = @@ -570,11 +570,7 @@ impl RelaySelector { } if relay_constraints.wireguard_constraints.port.is_any() { - relay_constraints.wireguard_constraints.port = - Constraint::Only(TransportPort { - protocol: preferred_protocol, - port: preferred_port, - }); + relay_constraints.wireguard_constraints.port = preferred_port; } relay_constraints.tunnel_protocol = Constraint::Only(preferred_tunnel); @@ -607,10 +603,7 @@ impl RelaySelector { }; if relay_constraints.wireguard_constraints.port.is_any() { - relay_constraints.wireguard_constraints.port = Constraint::Only(TransportPort { - port: preferred_port, - protocol: TransportProtocol::Udp, - }); + relay_constraints.wireguard_constraints.port = preferred_port; } relay_constraints.tunnel_protocol = Constraint::Only(preferred_tunnel); @@ -713,6 +706,85 @@ impl RelaySelector { }) } + pub fn get_obfuscator( + &self, + obfuscation_settings: &ObfuscationSettings, + relay: &Relay, + endpoint: &MullvadWireguardEndpoint, + retry_attempt: u32, + ) -> Option<ObfuscatorConfig> { + match obfuscation_settings.selected_obfuscation { + SelectedObfuscation::Auto => { + self.get_auto_obfuscator(obfuscation_settings, relay, endpoint, retry_attempt) + } + SelectedObfuscation::Off => None, + SelectedObfuscation::Udp2Tcp => self.get_udp2tcp_obfuscator( + &obfuscation_settings.udp2tcp, + relay, + endpoint, + retry_attempt, + ), + } + } + + fn get_auto_obfuscator( + &self, + obfuscation_settings: &ObfuscationSettings, + relay: &Relay, + endpoint: &MullvadWireguardEndpoint, + retry_attempt: u32, + ) -> Option<ObfuscatorConfig> { + if !self.should_use_auto_obfuscator(retry_attempt) { + return None; + } + // TODO FIX: The third obfuscator entry will never be chosen + // Because get_auto_obfuscator_retry_attempt() returns [0, 1] + // And the udp2tcp endpoints are defined in a vector with entries [0, 1, 2] + self.get_udp2tcp_obfuscator( + &obfuscation_settings.udp2tcp, + relay, + endpoint, + self.get_auto_obfuscator_retry_attempt(retry_attempt) + .unwrap(), + ) + } + + pub fn should_use_auto_obfuscator(&self, retry_attempt: u32) -> bool { + self.get_auto_obfuscator_retry_attempt(retry_attempt) + .is_some() + } + + fn get_auto_obfuscator_retry_attempt(&self, retry_attempt: u32) -> Option<u32> { + match retry_attempt % 4 { + 0 | 1 => None, + filtered_retry => Some(filtered_retry - 2), + } + } + + fn get_udp2tcp_obfuscator( + &self, + obfuscation_settings: &Udp2TcpObfuscationSettings, + relay: &Relay, + _endpoint: &MullvadWireguardEndpoint, + retry_attempt: u32, + ) -> Option<ObfuscatorConfig> { + let udp2tcp_endpoint = if obfuscation_settings.port.is_only() { + relay + .obfuscators + .udp2tcp + .iter() + .find(|&candidate| obfuscation_settings.port.matches_eq(&candidate.port)) + } else { + relay + .obfuscators + .udp2tcp + .get(retry_attempt as usize % relay.obfuscators.udp2tcp.len()) + }; + udp2tcp_endpoint.map(|udp2tcp_endpoint| ObfuscatorConfig::Udp2Tcp { + endpoint: SocketAddr::new(relay.ipv4_addr_in.into(), udp2tcp_endpoint.port), + }) + } + /// Returns preferred constraints #[allow(unused_variables)] fn preferred_tunnel_constraints( @@ -773,18 +845,14 @@ impl RelaySelector { } } - fn preferred_wireguard_port(retry_attempt: u32) -> Constraint<TransportPort> { + fn preferred_wireguard_port(retry_attempt: u32) -> Constraint<u16> { // 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. - let port = match retry_attempt % 4 { + match retry_attempt % 4 { 0 | 1 => Constraint::Any, _ => Constraint::Only(53), - }; - Constraint::Only(TransportPort { - port, - protocol: TransportProtocol::Udp, - }) + } } fn preferred_openvpn_constraints(retry_attempt: u32) -> (Constraint<u16>, TransportProtocol) { @@ -963,7 +1031,7 @@ mod test { relay_constraints::RelayConstraints, relay_list::{ OpenVpnEndpointData, Relay, RelayBridges, RelayListCity, RelayListCountry, - RelayTunnels, WireguardEndpointData, + RelayObfuscators, RelayTunnels, WireguardEndpointData, }, }; use talpid_types::net::wireguard::PublicKey; @@ -999,13 +1067,15 @@ mod test { ipv4_gateway: "10.64.0.1".parse().unwrap(), ipv6_gateway: "fc00:bbbb:bbbb:bb01::1".parse().unwrap(), public_key: PublicKey::from_base64("BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=").unwrap(), - protocol: TransportProtocol::Udp, }, ], }, bridges: RelayBridges { shadowsocks: vec![], }, + obfuscators: RelayObfuscators { + udp2tcp: vec![], + }, location: None, }, Relay { @@ -1025,13 +1095,15 @@ mod test { ipv4_gateway: "10.64.0.1".parse().unwrap(), ipv6_gateway: "fc00:bbbb:bbbb:bb01::1".parse().unwrap(), public_key: PublicKey::from_base64("veGD6/aEY6sMfN3Ls7YWPmNgu3AheO7nQqsFT47YSws=").unwrap(), - protocol: TransportProtocol::Udp, }, ], }, bridges: RelayBridges { shadowsocks: vec![], }, + obfuscators: RelayObfuscators { + udp2tcp: vec![], + }, location: None, }, Relay { @@ -1063,6 +1135,9 @@ mod test { bridges: RelayBridges { shadowsocks: vec![], }, + obfuscators: RelayObfuscators { + udp2tcp: vec![], + }, location: None, }, Relay { @@ -1082,13 +1157,15 @@ mod test { ipv4_gateway: "10.64.0.1".parse().unwrap(), ipv6_gateway: "fc00:bbbb:bbbb:bb01::1".parse().unwrap(), public_key: PublicKey::from_base64("veGD6/aEY6sMfN3Ls7YWPmNgu3AheO7nQqsFT47YSws=").unwrap(), - protocol: TransportProtocol::Udp, }, ], }, bridges: RelayBridges { shadowsocks: vec![], }, + obfuscators: RelayObfuscators { + udp2tcp: vec![], + }, location: None, }, Relay { @@ -1110,6 +1187,9 @@ mod test { bridges: RelayBridges { shadowsocks: vec![], }, + obfuscators: RelayObfuscators { + udp2tcp: vec![], + }, location: None, } ], @@ -1436,102 +1516,134 @@ mod test { }, }; + const WIREGUARD_SINGLEHOP_CONSTRAINTS: RelayConstraints = RelayConstraints { + location: Constraint::Any, + providers: Constraint::Any, + wireguard_constraints: WireguardConstraints { + use_multihop: false, + port: Constraint::Any, + ip_version: Constraint::Any, + entry_location: Constraint::Any, + }, + tunnel_protocol: Constraint::Only(TunnelType::Wireguard), + openvpn_constraints: OpenVpnConstraints { + port: Constraint::Any, + }, + }; + #[test] fn test_selecting_wireguard_location_will_consider_multihop() { let relay_selector = new_relay_selector(); let result = relay_selector.get_tunnel_endpoint(&WIREGUARD_MULTIHOP_CONSTRAINTS, BridgeState::Off, 0) - - .expect("Failed to get relay when tunnel constraints are set to Any and retrying the selection"); + .expect("Failed to get relay when tunnel constraints are set to default WireGuard multihop constraints"); assert!(result.entry_relay.is_some()); - let endpoint = result.endpoint.unwrap_wireguard(); - assert!(matches!(endpoint.peer.protocol, TransportProtocol::Udp)); - assert!(matches!( - endpoint.exit_peer.as_ref().unwrap().protocol, - TransportProtocol::Udp - )); + // TODO: Verify that neither endpoint is using obfuscation for retry attempt 0 } #[test] - fn test_selecting_wg_multihop_tcp() { - let mut relay_constraints = WIREGUARD_MULTIHOP_CONSTRAINTS.clone(); - relay_constraints.wireguard_constraints.port = Constraint::Only(TransportPort { - port: Constraint::Any, - protocol: TransportProtocol::Tcp, - }); - + fn test_selecting_wg_endpoint_with_udp2tcp_obfuscation() { let relay_selector = new_relay_selector(); - let result = relay_selector - .get_tunnel_endpoint(&relay_constraints, BridgeState::Off, 0) - .expect("Failed to get WireGuard TCP multihop relay"); + let result = relay_selector.get_tunnel_endpoint(&WIREGUARD_SINGLEHOP_CONSTRAINTS, BridgeState::Off, 0) + .expect("Failed to get relay when tunnel constraints are set to default WireGuard constraints"); + + assert!(result.entry_relay.is_none()); + assert!(matches!(result.endpoint, MullvadEndpoint::Wireguard { .. })); + + let obfs_settings = ObfuscationSettings { + selected_obfuscation: SelectedObfuscation::Udp2Tcp, + ..ObfuscationSettings::default() + }; + + let obfs_config = relay_selector.get_obfuscator( + &obfs_settings, + &result.exit_relay, + result.endpoint.unwrap_wireguard(), + 0, + ); - assert!(result.entry_relay.is_some()); - let endpoint = result.endpoint.unwrap_wireguard(); - assert!(matches!(endpoint.peer.protocol, TransportProtocol::Tcp)); assert!(matches!( - endpoint.exit_peer.as_ref().unwrap().protocol, - TransportProtocol::Udp + obfs_config.unwrap(), + ObfuscatorConfig::Udp2Tcp { .. } )); } #[test] - fn test_selecting_wg_tcp() { - let relay_constraints = RelayConstraints { - wireguard_constraints: WireguardConstraints { - port: Constraint::Only(TransportPort { - port: Constraint::Any, - protocol: TransportProtocol::Tcp, - }), - ..WireguardConstraints::default() - }, - tunnel_protocol: Constraint::Only(TunnelType::Wireguard), - ..RelayConstraints::default() + fn test_selecting_wg_endpoint_with_auto_obfuscation() { + let relay_selector = new_relay_selector(); + + let result = relay_selector.get_tunnel_endpoint(&WIREGUARD_SINGLEHOP_CONSTRAINTS, BridgeState::Off, 0) + .expect("Failed to get relay when tunnel constraints are set to default WireGuard constraints"); + + assert!(result.entry_relay.is_none()); + assert!(matches!(result.endpoint, MullvadEndpoint::Wireguard { .. })); + + let obfs_settings = ObfuscationSettings { + selected_obfuscation: SelectedObfuscation::Auto, + ..ObfuscationSettings::default() }; - let relay_selector = new_relay_selector(); + assert!(relay_selector + .get_obfuscator( + &obfs_settings, + &result.exit_relay, + result.endpoint.unwrap_wireguard(), + 0, + ) + .is_none()); - let result = relay_selector - .get_tunnel_endpoint(&relay_constraints, BridgeState::Off, 0) - .expect("Failed to get WireGuard TCP relay"); - let endpoint = result.endpoint.unwrap_wireguard(); - assert!(matches!(endpoint.peer.protocol, TransportProtocol::Tcp)); - assert!(endpoint.exit_peer.is_none()); + assert!(relay_selector + .get_obfuscator( + &obfs_settings, + &result.exit_relay, + result.endpoint.unwrap_wireguard(), + 1, + ) + .is_none()); + + assert!(relay_selector + .get_obfuscator( + &obfs_settings, + &result.exit_relay, + result.endpoint.unwrap_wireguard(), + 2, + ) + .is_some()); } #[test] - fn test_selecting_wg_multihop_ports() { - let mut relay_constraints = WIREGUARD_MULTIHOP_CONSTRAINTS.clone(); + fn test_selected_endpoints_use_correct_port_ranges() { let relay_selector = new_relay_selector(); - const INVALID_UDP_PORTS: [u16; 2] = [80, 443]; - for attempt in 0..1000 { - let result = relay_selector - .get_tunnel_endpoint(&relay_constraints, BridgeState::Off, attempt) - .expect("Failed to get WireGuard TCP multihop relay"); - assert!(!INVALID_UDP_PORTS.contains(&result.endpoint.to_endpoint().address.port())); - assert_eq!( - result.endpoint.unwrap_wireguard().peer.protocol, - TransportProtocol::Udp - ); - } + const TCP2UDP_PORTS: [u16; 3] = [80, 443, 5001]; - relay_constraints.wireguard_constraints.port = Constraint::Only(TransportPort { - port: Constraint::Any, - protocol: TransportProtocol::Tcp, - }); + let obfs_settings = ObfuscationSettings { + selected_obfuscation: SelectedObfuscation::Udp2Tcp, + ..ObfuscationSettings::default() + }; - const VALID_TCP_PORTS: [u16; 3] = [80, 443, 5001]; for attempt in 0..1000 { let result = relay_selector - .get_tunnel_endpoint(&relay_constraints, BridgeState::Off, attempt) - .expect("Failed to get WireGuard TCP multihop relay"); - assert!(VALID_TCP_PORTS.contains(&result.endpoint.to_endpoint().address.port())); - assert_eq!( - result.endpoint.unwrap_wireguard().peer.protocol, - TransportProtocol::Tcp - ); + .get_tunnel_endpoint(&WIREGUARD_SINGLEHOP_CONSTRAINTS, BridgeState::Off, attempt) + .expect("Failed to select a WireGuard relay"); + assert!(result.entry_relay.is_none()); + assert!(!TCP2UDP_PORTS.contains(&result.endpoint.to_endpoint().address.port())); + + let obfs_config = relay_selector + .get_obfuscator( + &obfs_settings, + &result.exit_relay, + result.endpoint.unwrap_wireguard(), + attempt, + ) + .expect("Failed to get Tcp2Udp endpoint"); + + assert!(matches!(obfs_config, ObfuscatorConfig::Udp2Tcp { .. })); + + let ObfuscatorConfig::Udp2Tcp { endpoint } = obfs_config; + assert!(TCP2UDP_PORTS.contains(&endpoint.port())); } } diff --git a/mullvad-daemon/src/settings.rs b/mullvad-daemon/src/settings.rs index bf3fe710c8..52800028cc 100644 --- a/mullvad-daemon/src/settings.rs +++ b/mullvad-daemon/src/settings.rs @@ -1,7 +1,7 @@ #[cfg(not(target_os = "android"))] use futures::TryFutureExt; use mullvad_types::{ - relay_constraints::{BridgeSettings, BridgeState, RelaySettingsUpdate}, + relay_constraints::{BridgeSettings, BridgeState, ObfuscationSettings, RelaySettingsUpdate}, settings::{DnsOptions, Settings}, wireguard::RotationInterval, }; @@ -320,6 +320,18 @@ impl SettingsPersister { } } + pub async fn set_obfuscation_settings( + &mut self, + obfuscation_settings: ObfuscationSettings, + ) -> Result<bool, Error> { + let should_save = Self::update_field( + &mut self.settings.obfuscation_settings, + obfuscation_settings, + ); + + self.update(should_save).await + } + async fn update(&mut self, should_save: bool) -> Result<bool, Error> { if should_save { self.save().await.map(|_| true) diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 21eb6ab512..07e2d4e8c7 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -30,6 +30,7 @@ service ManagementService { rpc GetCurrentLocation(google.protobuf.Empty) returns (GeoIpLocation) {} rpc SetBridgeSettings(BridgeSettings) returns (google.protobuf.Empty) {} rpc SetBridgeState(BridgeState) returns (google.protobuf.Empty) {} + rpc SetObfuscationSettings(ObfuscationSettings) returns (google.protobuf.Empty) {} // Settings rpc GetSettings(google.protobuf.Empty) returns (Settings) {} @@ -190,7 +191,19 @@ message TunnelEndpoint { TransportProtocol protocol = 2; TunnelType tunnel_type = 3; ProxyEndpoint proxy = 4; - Endpoint entry_endpoint = 5; + ObfuscationEndpoint obfuscation = 5; + Endpoint entry_endpoint = 6; +} + +enum ObfuscationType { + UDP2TCP = 0; +} + +message ObfuscationEndpoint { + string address = 1; + uint32 port = 2; + TransportProtocol protocol = 3; + ObfuscationType obfuscation_type = 4; } enum ProxyType { @@ -269,6 +282,20 @@ message BridgeState { State state = 1; } +message Udp2TcpObfuscationSettings { + uint32 port = 1; +} + +message ObfuscationSettings { + enum SelectedObfuscation { + AUTO = 0; + OFF = 1; + UDP2TCP = 2; + } + SelectedObfuscation selected_obfuscation = 1; + Udp2TcpObfuscationSettings udp2tcp = 2; +} + message Settings { RelaySettings relay_settings = 1; BridgeSettings bridge_settings = 2; @@ -279,6 +306,7 @@ message Settings { TunnelOptions tunnel_options = 7; bool show_beta_releases = 8; SplitTunnelSettings split_tunnel = 9; + ObfuscationSettings obfuscation_settings = 10; } message SplitTunnelSettings { @@ -341,7 +369,7 @@ message IpVersionConstraint { } message WireguardConstraints { - TransportPort port = 1; + uint32 port = 1; IpVersionConstraint ip_version = 2; bool use_multihop = 3; RelayLocation entry_location = 4; @@ -368,7 +396,6 @@ message ConnectionConfig { bytes public_key = 1; repeated string allowed_ips = 2; string endpoint = 3; - TransportProtocol protocol = 4; } TunnelConfig tunnel = 1; diff --git a/mullvad-management-interface/src/types.rs b/mullvad-management-interface/src/types.rs index c76ada98d1..d62958e78d 100644 --- a/mullvad-management-interface/src/types.rs +++ b/mullvad-management-interface/src/types.rs @@ -42,6 +42,18 @@ impl From<talpid_types::net::TunnelEndpoint> for TunnelEndpoint { net::proxy::ProxyType::Custom => i32::from(ProxyType::Custom), }, }), + obfuscation: endpoint + .obfuscation + .map(|obfuscation_endpoint| ObfuscationEndpoint { + address: obfuscation_endpoint.endpoint.address.ip().to_string(), + port: u32::from(obfuscation_endpoint.endpoint.address.port()), + protocol: i32::from(TransportProtocol::from( + obfuscation_endpoint.endpoint.protocol, + )), + obfuscation_type: match obfuscation_endpoint.obfuscation_type { + net::ObfuscationType::Udp2Tcp => i32::from(ObfuscationType::Udp2tcp), + }, + }), entry_endpoint: endpoint.entry_endpoint.map(|entry| Endpoint { address: entry.address.to_string(), protocol: i32::from(TransportProtocol::from(entry.protocol)), @@ -308,7 +320,6 @@ impl From<mullvad_types::ConnectionConfig> for ConnectionConfig { .map(|address| address.to_string()) .collect(), endpoint: config.peer.endpoint.to_string(), - protocol: i32::from(TransportProtocol::from(config.peer.protocol)), }), ipv4_gateway: config.ipv4_gateway.to_string(), ipv6_gateway: config @@ -431,6 +442,7 @@ impl From<&mullvad_types::settings::Settings> for Settings { auto_connect: settings.auto_connect, tunnel_options: Some(TunnelOptions::from(&settings.tunnel_options)), show_beta_releases: settings.show_beta_releases, + obfuscation_settings: Some(ObfuscationSettings::from(&settings.obfuscation_settings)), split_tunnel, } } @@ -449,6 +461,31 @@ impl From<mullvad_types::relay_constraints::BridgeState> for BridgeState { } } +impl From<&mullvad_types::relay_constraints::ObfuscationSettings> for ObfuscationSettings { + fn from(settings: &mullvad_types::relay_constraints::ObfuscationSettings) -> Self { + use mullvad_types::relay_constraints::SelectedObfuscation; + let selected_obfuscation = i32::from(match settings.selected_obfuscation { + SelectedObfuscation::Auto => obfuscation_settings::SelectedObfuscation::Auto, + SelectedObfuscation::Off => obfuscation_settings::SelectedObfuscation::Off, + SelectedObfuscation::Udp2Tcp => obfuscation_settings::SelectedObfuscation::Udp2tcp, + }); + Self { + selected_obfuscation, + udp2tcp: Some(Udp2TcpObfuscationSettings::from(&settings.udp2tcp)), + } + } +} + +impl From<&mullvad_types::relay_constraints::Udp2TcpObfuscationSettings> + for Udp2TcpObfuscationSettings +{ + fn from(settings: &mullvad_types::relay_constraints::Udp2TcpObfuscationSettings) -> Self { + Self { + port: u32::from(settings.port.unwrap_or(0)), + } + } +} + impl From<mullvad_types::relay_constraints::BridgeSettings> for BridgeSettings { fn from(settings: mullvad_types::relay_constraints::BridgeSettings) -> Self { use mullvad_types::relay_constraints::BridgeSettings as MullvadBridgeSettings; @@ -529,11 +566,7 @@ impl From<mullvad_types::relay_constraints::RelaySettings> for RelaySettings { }), wireguard_constraints: Some(WireguardConstraints { - port: constraints - .wireguard_constraints - .port - .option() - .map(TransportPort::from), + port: u32::from(constraints.wireguard_constraints.port.unwrap_or(0)), ip_version: constraints .wireguard_constraints .ip_version @@ -756,10 +789,6 @@ impl TryFrom<&WireguardConstraints> for mullvad_types::relay_constraints::Wiregu use mullvad_types::relay_constraints as mullvad_constraints; use talpid_types::net; - let wireguard_transport_port = match &constraints.port { - Some(port) => Some(mullvad_constraints::TransportPort::try_from(port.clone())?), - None => None, - }; let ip_version = match &constraints.ip_version { Some(constraint) => match IpVersion::from_i32(constraint.protocol) { Some(IpVersion::V4) => Some(net::IpVersion::V4), @@ -774,7 +803,11 @@ impl TryFrom<&WireguardConstraints> for mullvad_types::relay_constraints::Wiregu }; Ok(mullvad_constraints::WireguardConstraints { - port: Constraint::from(wireguard_transport_port), + port: if constraints.port == 0 { + Constraint::Any + } else { + Constraint::Only(constraints.port as u16) + }, ip_version: Constraint::from(ip_version), use_multihop: constraints.use_multihop, entry_location: constraints @@ -1091,7 +1124,6 @@ impl TryFrom<ConnectionConfig> for mullvad_types::ConnectionConfig { public_key, allowed_ips, endpoint, - protocol: try_transport_protocol_from_i32(peer.protocol)?, }, exit_peer: None, ipv4_gateway, @@ -1204,6 +1236,58 @@ impl TryFrom<BridgeSettings> for mullvad_types::relay_constraints::BridgeSetting } } +impl TryFrom<ObfuscationSettings> for mullvad_types::relay_constraints::ObfuscationSettings { + type Error = FromProtobufTypeError; + + fn try_from(settings: ObfuscationSettings) -> Result<Self, Self::Error> { + use mullvad_types::relay_constraints::SelectedObfuscation; + use obfuscation_settings::SelectedObfuscation as IpcSelectedObfuscation; + let selected_obfuscation = + match IpcSelectedObfuscation::from_i32(settings.selected_obfuscation) { + Some(IpcSelectedObfuscation::Auto) => SelectedObfuscation::Auto, + Some(IpcSelectedObfuscation::Off) => SelectedObfuscation::Off, + Some(IpcSelectedObfuscation::Udp2tcp) => SelectedObfuscation::Udp2Tcp, + None => { + return Err(FromProtobufTypeError::InvalidArgument( + "invalid selected obfuscator", + )); + } + }; + + let udp2tcp = match settings.udp2tcp { + Some(settings) => { + mullvad_types::relay_constraints::Udp2TcpObfuscationSettings::try_from(&settings)? + } + None => { + return Err(FromProtobufTypeError::InvalidArgument( + "invalid selected obfuscator", + )); + } + }; + + Ok(Self { + selected_obfuscation, + udp2tcp, + }) + } +} + +impl TryFrom<&Udp2TcpObfuscationSettings> + for mullvad_types::relay_constraints::Udp2TcpObfuscationSettings +{ + type Error = FromProtobufTypeError; + + fn try_from(settings: &Udp2TcpObfuscationSettings) -> Result<Self, Self::Error> { + Ok(Self { + port: if settings.port == 0 { + Constraint::Any + } else { + Constraint::Only(settings.port as u16) + }, + }) + } +} + impl TryFrom<BridgeState> for mullvad_types::relay_constraints::BridgeState { type Error = FromProtobufTypeError; diff --git a/mullvad-types/src/custom_tunnel.rs b/mullvad-types/src/custom_tunnel.rs index cded541018..7cbfcc609f 100644 --- a/mullvad-types/src/custom_tunnel.rs +++ b/mullvad-types/src/custom_tunnel.rs @@ -60,6 +60,7 @@ impl CustomTunnelEndpoint { connection, options: tunnel_options.wireguard.options.clone(), generic_options: tunnel_options.generic.clone(), + obfuscation: None, } .into(), }; diff --git a/mullvad-types/src/relay_constraints.rs b/mullvad-types/src/relay_constraints.rs index 6962951eb1..0ee7452c60 100644 --- a/mullvad-types/src/relay_constraints.rs +++ b/mullvad-types/src/relay_constraints.rs @@ -426,7 +426,7 @@ impl Match<OpenVpnEndpointData> for OpenVpnConstraints { #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(default)] pub struct WireguardConstraints { - pub port: Constraint<TransportPort>, + pub port: Constraint<u16>, pub ip_version: Constraint<IpVersion>, pub use_multihop: bool, pub entry_location: Constraint<LocationConstraint>, @@ -436,13 +436,7 @@ impl fmt::Display for WireguardConstraints { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { match self.port { Constraint::Any => write!(f, "any port")?, - Constraint::Only(port) => { - match port.port { - Constraint::Any => write!(f, "any port")?, - Constraint::Only(port) => write!(f, "port {}", port)?, - } - write!(f, " over {}", port.protocol)?; - } + Constraint::Only(port) => write!(f, "port {}", port)?, } write!(f, " over ")?; match self.ip_version { @@ -470,6 +464,75 @@ pub enum BridgeSettings { Custom(ProxySettings), } +#[derive(Debug, Clone, Copy, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum SelectedObfuscation { + Auto, + Off, + Udp2Tcp, +} + +impl Default for SelectedObfuscation { + fn default() -> Self { + SelectedObfuscation::Off + } +} + +impl fmt::Display for SelectedObfuscation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match self { + SelectedObfuscation::Auto => "auto", + SelectedObfuscation::Off => "off", + SelectedObfuscation::Udp2Tcp => "udp2tcp", + } + ) + } +} + +#[derive(Default, Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct Udp2TcpObfuscationSettings { + pub port: Constraint<u16>, +} + +impl fmt::Display for Udp2TcpObfuscationSettings { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "port: {}", + match self.port { + Constraint::Any => "any".to_string(), + Constraint::Only(port) => port.to_string(), + } + )?; + Ok(()) + } +} + +/// Contains obfuscation settings +#[derive(Default, Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +#[serde(default)] +pub struct ObfuscationSettings { + pub selected_obfuscation: SelectedObfuscation, + pub udp2tcp: Udp2TcpObfuscationSettings, +} + +impl fmt::Display for ObfuscationSettings { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "selected obfuscation: ")?; + match self.selected_obfuscation { + SelectedObfuscation::Auto => write!(f, "auto")?, + SelectedObfuscation::Off => write!(f, "off")?, + SelectedObfuscation::Udp2Tcp => write!(f, "Udp2Tcp ({})", self.udp2tcp)?, + }; + Ok(()) + } +} + /// Limits the set of bridge servers to use in `mullvad-daemon`. #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(default)] diff --git a/mullvad-types/src/relay_list.rs b/mullvad-types/src/relay_list.rs index e638749fa5..e5a40a8f4e 100644 --- a/mullvad-types/src/relay_list.rs +++ b/mullvad-types/src/relay_list.rs @@ -83,6 +83,9 @@ pub struct Relay { #[serde(skip_serializing_if = "RelayBridges::is_empty", default)] #[cfg_attr(target_os = "android", jnix(skip))] pub bridges: RelayBridges, + #[serde(skip_serializing_if = "RelayObfuscators::is_empty", default)] + #[cfg_attr(target_os = "android", jnix(skip))] + pub obfuscators: RelayObfuscators, #[cfg_attr(target_os = "android", jnix(skip))] pub location: Option<Location>, } @@ -141,23 +144,15 @@ pub struct WireguardEndpointData { pub ipv6_gateway: Ipv6Addr, /// The peer's public key pub public_key: wireguard::PublicKey, - #[serde(default = "default_wg_transport")] - #[serde(skip)] - pub protocol: TransportProtocol, -} - -fn default_wg_transport() -> TransportProtocol { - TransportProtocol::Udp } impl fmt::Display for WireguardEndpointData { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!( f, - "gateways {} - {} {} port_ranges {{ {} }} public_key {}", + "gateways {} - {} port_ranges {{ {} }} public_key {}", self.ipv4_gateway, self.ipv6_gateway, - self.protocol, self.port_ranges .iter() .map(|range| format!("[{} - {}]", range.0, range.1)) @@ -203,3 +198,24 @@ impl ShadowsocksEndpointData { }) } } + +#[derive(Debug, Default, Clone, Deserialize, Serialize)] +#[serde(default)] +pub struct RelayObfuscators { + pub udp2tcp: Vec<Udp2TcpEndpointData>, +} + +impl RelayObfuscators { + pub fn is_empty(&self) -> bool { + self.udp2tcp.is_empty() + } + + pub fn clear(&mut self) { + self.udp2tcp.clear(); + } +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)] +pub struct Udp2TcpEndpointData { + pub port: u16, +} diff --git a/mullvad-types/src/settings/mod.rs b/mullvad-types/src/settings/mod.rs index 63ccb480a2..c2f0a17781 100644 --- a/mullvad-types/src/settings/mod.rs +++ b/mullvad-types/src/settings/mod.rs @@ -1,7 +1,8 @@ use crate::{ relay_constraints::{ BridgeConstraints, BridgeSettings, BridgeState, Constraint, LocationConstraint, - RelayConstraints, RelaySettings, RelaySettingsUpdate, + ObfuscationSettings, RelayConstraints, RelaySettings, RelaySettingsUpdate, + SelectedObfuscation, }, wireguard, }; @@ -65,6 +66,8 @@ pub struct Settings { #[cfg_attr(target_os = "android", jnix(skip))] pub bridge_settings: BridgeSettings, #[cfg_attr(target_os = "android", jnix(skip))] + pub obfuscation_settings: ObfuscationSettings, + #[cfg_attr(target_os = "android", jnix(skip))] bridge_state: BridgeState, /// If the daemon should allow communication with private (LAN) networks. pub allow_lan: bool, @@ -104,6 +107,10 @@ impl Default for Settings { ..Default::default() }), bridge_settings: BridgeSettings::Normal(BridgeConstraints::default()), + obfuscation_settings: ObfuscationSettings { + selected_obfuscation: SelectedObfuscation::Off, + ..Default::default() + }, bridge_state: BridgeState::Auto, allow_lan: false, block_when_disconnected: false, diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml index 9dd56ac5c0..d0cf685753 100644 --- a/talpid-core/Cargo.toml +++ b/talpid-core/Cargo.toml @@ -31,7 +31,7 @@ chrono = "0.4.19" tokio = { version = "1.8", features = ["process", "rt-multi-thread", "fs"] } tokio-stream = { version = "0.1", features = ["io-util"] } rand = "0.7" -udp-over-tcp = { git = "https://github.com/mullvad/udp-over-tcp", rev = "1e27324362ed123b61fa2062b1599e5f9d569796" } +tunnel-obfuscation = { path = "../tunnel-obfuscation" } socket2 = { version = "0.4.2", features = ["all"] } [target.'cfg(not(target_os="android"))'.dependencies] diff --git a/talpid-core/src/tunnel/wireguard/config.rs b/talpid-core/src/tunnel/wireguard/config.rs index 307a713272..7566d5c141 100644 --- a/talpid-core/src/tunnel/wireguard/config.rs +++ b/talpid-core/src/tunnel/wireguard/config.rs @@ -3,7 +3,7 @@ use std::{ ffi::CString, net::{Ipv4Addr, Ipv6Addr}, }; -use talpid_types::net::{wireguard, GenericTunnelOptions}; +use talpid_types::net::{obfuscation::ObfuscatorConfig, wireguard, GenericTunnelOptions}; /// Config required to set up a single WireGuard tunnel pub struct Config { @@ -26,6 +26,8 @@ pub struct Config { /// Temporary switch for wireguard-nt #[cfg(target_os = "windows")] pub use_wireguard_nt: bool, + /// Obfuscator config to be used for reaching the relay. + pub obfuscator_config: Option<ObfuscatorConfig>, } const DEFAULT_MTU: u16 = 1380; @@ -60,6 +62,7 @@ impl Config { ¶ms.connection, ¶ms.options, ¶ms.generic_options, + params.obfuscation.clone(), ) } @@ -70,6 +73,7 @@ impl Config { connection_config: &wireguard::ConnectionConfig, wg_options: &wireguard::TunnelOptions, generic_options: &GenericTunnelOptions, + obfuscator_config: Option<ObfuscatorConfig>, ) -> Result<Config, Error> { if peers.is_empty() { return Err(Error::NoPeersSuppliedError); @@ -114,6 +118,7 @@ impl Config { enable_ipv6: generic_options.enable_ipv6, #[cfg(target_os = "windows")] use_wireguard_nt: wg_options.use_wireguard_nt, + obfuscator_config, }) } diff --git a/talpid-core/src/tunnel/wireguard/mod.rs b/talpid-core/src/tunnel/wireguard/mod.rs index 263bbaeabb..59098a00ca 100644 --- a/talpid-core/src/tunnel/wireguard/mod.rs +++ b/talpid-core/src/tunnel/wireguard/mod.rs @@ -5,7 +5,10 @@ use super::{tun_provider::TunProvider, TunnelEvent, TunnelMetadata}; use crate::routing::{self, RequiredRoute, RouteManagerHandle}; #[cfg(windows)] use futures::{channel::mpsc, StreamExt}; -use futures::{channel::oneshot, future::abortable}; +use futures::{ + channel::oneshot, + future::{abortable, AbortHandle as FutureAbortHandle}, +}; #[cfg(target_os = "linux")] use lazy_static::lazy_static; #[cfg(target_os = "linux")] @@ -16,14 +19,16 @@ use std::env; use std::io; use std::{ convert::Infallible, - net::{IpAddr, SocketAddr}, + net::IpAddr, path::Path, sync::{mpsc as sync_mpsc, Arc, Mutex}, }; #[cfg(windows)] use talpid_types::BoxedError; -use talpid_types::{net::TransportProtocol, ErrorExt}; -use udp_over_tcp::{TcpOptions, Udp2Tcp}; +use talpid_types::{net::obfuscation::ObfuscatorConfig, ErrorExt}; +use tunnel_obfuscation::{ + create_obfuscator, Error as ObfuscationError, Settings as ObfuscationSettings, Udp2TcpSettings, +}; /// WireGuard config data-types pub mod config; @@ -56,13 +61,13 @@ pub enum Error { #[error(display = "Tunnel failed")] TunnelError(#[error(source)] TunnelError), - /// Failed to set up Udp2Tcp - #[error(display = "Failed to start UDP-over-TCP proxy")] - Udp2TcpError(#[error(source)] udp_over_tcp::udp2tcp::ConnectError), + /// Failed to create tunnel obfuscator + #[error(display = "Failed to create tunnel obfuscator")] + CreateObfuscatorError(#[error(source)] ObfuscationError), - /// Failed to obtain the local UDP socket address - #[error(display = "Failed obtain local address for the UDP socket in Udp2Tcp")] - GetLocalUdpAddress(#[error(source)] std::io::Error), + /// Failed to run tunnel obfuscator + #[error(display = "Tunnel obfuscator failed")] + ObfuscatorError(#[error(source)] ObfuscationError), /// Failed to set up connectivity monitor #[error(display = "Connectivity monitor failed")] @@ -93,7 +98,24 @@ pub struct WireguardMonitor { >, close_msg_receiver: sync_mpsc::Receiver<CloseMsg>, pinger_stop_sender: sync_mpsc::Sender<()>, - _tcp_proxies: Vec<TcpProxy>, + _obfuscator: Option<ObfuscatorHandle>, +} + +/// Simple wrapper that automatically cancels the future which runs an obfuscator. +struct ObfuscatorHandle { + abort_handle: FutureAbortHandle, +} + +impl ObfuscatorHandle { + pub fn new(abort_handle: FutureAbortHandle) -> Self { + Self { abort_handle } + } +} + +impl Drop for ObfuscatorHandle { + fn drop(&mut self) { + self.abort_handle.abort(); + } } #[cfg(target_os = "linux")] @@ -108,52 +130,51 @@ lazy_static! { .unwrap_or(false); } -struct TcpProxy { - local_addr: SocketAddr, - abort_handle: futures::future::AbortHandle, -} - -impl TcpProxy { - pub fn new(runtime: &tokio::runtime::Handle, endpoint: SocketAddr) -> Result<Self> { - let listen_addr = if endpoint.is_ipv4() { - SocketAddr::new("127.0.0.1".parse().unwrap(), 0) - } else { - SocketAddr::new("::1".parse().unwrap(), 0) - }; +fn maybe_create_obfuscator( + runtime: &tokio::runtime::Handle, + config: &mut Config, + close_msg_sender: sync_mpsc::Sender<CloseMsg>, +) -> Result<Option<ObfuscatorHandle>> { + // There are one or two peers. + // The first one is always the entry relay. + let mut first_peer = config.peers.get_mut(0).expect("missing peer"); - let udp2tcp = runtime - .block_on(Udp2Tcp::new( - listen_addr, - endpoint, - TcpOptions { + if let Some(ref obfuscator_config) = config.obfuscator_config { + match obfuscator_config { + ObfuscatorConfig::Udp2Tcp { endpoint } => { + log::trace!("Connecting to Udp2Tcp endpoint {:?}", *endpoint); + let settings = Udp2TcpSettings { + peer: *endpoint, #[cfg(target_os = "linux")] fwmark: Some(crate::linux::TUNNEL_FW_MARK), - ..TcpOptions::default() - }, - )) - .map_err(Error::Udp2TcpError)?; - let local_addr = udp2tcp - .local_udp_addr() - .map_err(Error::GetLocalUdpAddress)?; - - let (udp2tcp_future, abort_handle) = abortable(udp2tcp.run()); - runtime.spawn(udp2tcp_future); - - Ok(Self { - local_addr, - abort_handle, - }) - } - - pub fn local_udp_addr(&self) -> SocketAddr { - self.local_addr - } -} - -impl Drop for TcpProxy { - fn drop(&mut self) { - self.abort_handle.abort(); + }; + let obfuscator = runtime + .block_on(create_obfuscator(&ObfuscationSettings::Udp2Tcp(settings))) + .map_err(Error::CreateObfuscatorError)?; + let endpoint = obfuscator.endpoint(); + log::trace!("Patching first WireGuard peer to become {:?}", endpoint); + first_peer.endpoint = endpoint; + let (runner, abort_handle) = abortable(async move { + match obfuscator.run().await { + Ok(_) => { + let _ = close_msg_sender.send(CloseMsg::ObfuscatorExpired); + } + Err(error) => { + log::error!( + "{}", + error.display_chain_with_msg("Obfuscation controller failed") + ); + let _ = close_msg_sender + .send(CloseMsg::ObfuscatorFailed(Error::ObfuscatorError(error))); + } + } + }); + runtime.spawn(runner); + return Ok(Some(ObfuscatorHandle::new(abort_handle))); + } + } } + Ok(None) } impl WireguardMonitor { @@ -175,19 +196,11 @@ impl WireguardMonitor { retry_attempt: u32, tunnel_close_rx: oneshot::Receiver<()>, ) -> Result<WireguardMonitor> { - let mut tcp_proxies = vec![]; - let mut endpoint_addrs = vec![]; - - for peer in &mut config.peers { - endpoint_addrs.push(peer.endpoint.ip()); - if peer.protocol == TransportProtocol::Tcp { - let udp2tcp = TcpProxy::new(&runtime, peer.endpoint.clone())?; + let endpoint_addrs: Vec<IpAddr> = + config.peers.iter().map(|peer| peer.endpoint.ip()).collect(); + let (close_msg_sender, close_msg_receiver) = sync_mpsc::channel(); - // Replace remote peer with proxy - peer.endpoint = udp2tcp.local_udp_addr(); - tcp_proxies.push(udp2tcp); - } - } + let obfuscator = maybe_create_obfuscator(&runtime, &mut config, close_msg_sender.clone())?; #[cfg(target_os = "windows")] let (setup_done_tx, mut setup_done_rx) = mpsc::channel(0); @@ -203,7 +216,6 @@ impl WireguardMonitor { let iface_name = tunnel.get_interface_name().to_string(); let event_callback = Box::new(on_event.clone()); - let (close_msg_sender, close_msg_receiver) = sync_mpsc::channel(); let (pinger_tx, pinger_rx) = sync_mpsc::channel(); let monitor = WireguardMonitor { runtime: runtime.clone(), @@ -211,7 +223,7 @@ impl WireguardMonitor { event_callback, close_msg_receiver, pinger_stop_sender: pinger_tx, - _tcp_proxies: tcp_proxies, + _obfuscator: obfuscator, }; let gateway = config.ipv4_gateway; @@ -413,8 +425,9 @@ impl WireguardMonitor { pub fn wait(mut self) -> Result<()> { let wait_result = match self.close_msg_receiver.recv() { Ok(CloseMsg::PingErr) => Err(Error::TimeoutError), - Ok(CloseMsg::Stop) => Ok(()), + Ok(CloseMsg::Stop) | Ok(CloseMsg::ObfuscatorExpired) => Ok(()), Ok(CloseMsg::SetupError(error)) => Err(error), + Ok(CloseMsg::ObfuscatorFailed(error)) => Err(error), Err(_) => Ok(()), }; @@ -570,6 +583,8 @@ enum CloseMsg { Stop, PingErr, SetupError(Error), + ObfuscatorExpired, + ObfuscatorFailed(Error), } pub(crate) trait Tunnel: Send { diff --git a/talpid-core/src/tunnel/wireguard/wireguard_nt.rs b/talpid-core/src/tunnel/wireguard/wireguard_nt.rs index 705d09892d..21c9b705ce 100644 --- a/talpid-core/src/tunnel/wireguard/wireguard_nt.rs +++ b/talpid-core/src/tunnel/wireguard/wireguard_nt.rs @@ -982,7 +982,7 @@ impl Tunnel for WgNtTunnel { mod tests { use super::*; use lazy_static::lazy_static; - use talpid_types::net::{wireguard, TransportProtocol}; + use talpid_types::net::wireguard; #[derive(Debug, Eq, PartialEq, Clone, Copy)] #[repr(C)] @@ -1006,12 +1006,12 @@ mod tests { public_key: WG_PUBLIC_KEY.clone(), allowed_ips: vec!["1.3.3.0/24".parse().unwrap()], endpoint: "1.2.3.4:1234".parse().unwrap(), - protocol: TransportProtocol::Udp, }], ipv4_gateway: "0.0.0.0".parse().unwrap(), ipv6_gateway: None, mtu: 0, use_wireguard_nt: true, + obfuscator_config: None, } }; static ref WG_STRUCT_CONFIG: Interface = Interface { diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs index 950ee1a43f..88319a737e 100644 --- a/talpid-core/src/tunnel_state_machine/connecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs @@ -479,7 +479,7 @@ fn should_retry(error: &tunnel::Error, retry_attempt: u32) -> bool { use tunnel::wireguard::{Error, TunnelError}; match error { - tunnel::Error::WireguardTunnelMonitoringError(Error::Udp2TcpError(_)) => true, + tunnel::Error::WireguardTunnelMonitoringError(Error::CreateObfuscatorError(_)) => true, #[cfg(not(windows))] tunnel::Error::WireguardTunnelMonitoringError(Error::TunnelError( diff --git a/talpid-types/src/net/mod.rs b/talpid-types/src/net/mod.rs index e758d0e02f..fdfeeb8b33 100644 --- a/talpid-types/src/net/mod.rs +++ b/talpid-types/src/net/mod.rs @@ -1,5 +1,6 @@ #[cfg(target_os = "android")] use jnix::IntoJava; +use obfuscation::ObfuscatorConfig; use serde::{Deserialize, Serialize}; #[cfg(windows)] use std::path::PathBuf; @@ -9,6 +10,7 @@ use std::{ str::FromStr, }; +pub mod obfuscation; pub mod openvpn; pub mod proxy; pub mod wireguard; @@ -29,6 +31,7 @@ impl TunnelParameters { tunnel_type: TunnelType::OpenVpn, endpoint: params.config.endpoint, proxy: params.proxy.as_ref().map(|proxy| proxy.get_endpoint()), + obfuscation: None, entry_endpoint: None, }, TunnelParameters::Wireguard(params) => TunnelEndpoint { @@ -38,6 +41,10 @@ impl TunnelParameters { .get_exit_endpoint() .unwrap_or(params.connection.get_endpoint()), proxy: None, + obfuscation: params + .obfuscation + .as_ref() + .map(|obfs| ObfuscationEndpoint::from(obfs)), entry_endpoint: params .connection .get_exit_endpoint() @@ -54,7 +61,20 @@ impl TunnelParameters { .as_ref() .map(|proxy| proxy.get_endpoint().endpoint) .unwrap_or(params.config.endpoint), - TunnelParameters::Wireguard(params) => params.connection.get_endpoint(), + TunnelParameters::Wireguard(params) => params + .obfuscation + .as_ref() + .map(|obfuscator| Self::get_obfuscator_endpoint(obfuscator)) + .unwrap_or(params.connection.get_endpoint()), + } + } + + fn get_obfuscator_endpoint(obfuscator: &ObfuscatorConfig) -> Endpoint { + match obfuscator { + ObfuscatorConfig::Udp2Tcp { endpoint } => Endpoint { + address: *endpoint, + protocol: TransportProtocol::Tcp, + }, } } @@ -119,6 +139,8 @@ pub struct TunnelEndpoint { #[cfg_attr(target_os = "android", jnix(skip))] pub proxy: Option<proxy::ProxyEndpoint>, #[cfg_attr(target_os = "android", jnix(skip))] + pub obfuscation: Option<ObfuscationEndpoint>, + #[cfg_attr(target_os = "android", jnix(skip))] pub entry_endpoint: Option<Endpoint>, } @@ -139,12 +161,64 @@ impl fmt::Display for TunnelEndpoint { if let Some(ref entry_endpoint) = self.entry_endpoint { write!(f, " via {}", entry_endpoint)?; } + if let Some(ref obfuscation) = self.obfuscation { + write!(f, " via {}", obfuscation)?; + } } } Ok(()) } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename = "obfuscation_type")] +pub enum ObfuscationType { + #[serde(rename = "udp2tcp")] + Udp2Tcp, +} + +impl fmt::Display for ObfuscationType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + let obfuscation = match self { + ObfuscationType::Udp2Tcp => "Udp2Tcp", + }; + write!(f, "{}", obfuscation) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename = "obfuscation_endpoint")] +pub struct ObfuscationEndpoint { + pub endpoint: Endpoint, + pub obfuscation_type: ObfuscationType, +} + +impl From<&ObfuscatorConfig> for ObfuscationEndpoint { + fn from(config: &ObfuscatorConfig) -> ObfuscationEndpoint { + let (endpoint, obfuscation_type) = match config { + ObfuscatorConfig::Udp2Tcp { endpoint } => ( + Endpoint { + address: *endpoint, + protocol: TransportProtocol::Tcp, + }, + ObfuscationType::Udp2Tcp, + ), + }; + + ObfuscationEndpoint { + endpoint, + obfuscation_type, + } + } +} + +impl fmt::Display for ObfuscationEndpoint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "{} {}", self.obfuscation_type, self.endpoint)?; + Ok(()) + } +} + /// Represents a network layer IP address together with the transport layer protocol and port. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[cfg_attr(target_os = "android", derive(IntoJava))] diff --git a/talpid-types/src/net/obfuscation.rs b/talpid-types/src/net/obfuscation.rs new file mode 100644 index 0000000000..a39e9bf919 --- /dev/null +++ b/talpid-types/src/net/obfuscation.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; +use std::net::SocketAddr; + +#[derive(Clone, Eq, PartialEq, Deserialize, Serialize, Debug)] +pub enum ObfuscatorConfig { + Udp2Tcp { endpoint: SocketAddr }, +} diff --git a/talpid-types/src/net/wireguard.rs b/talpid-types/src/net/wireguard.rs index 8219d772ec..2b3a9054b5 100644 --- a/talpid-types/src/net/wireguard.rs +++ b/talpid-types/src/net/wireguard.rs @@ -17,6 +17,7 @@ pub struct TunnelParameters { pub connection: ConnectionConfig, pub options: TunnelOptions, pub generic_options: GenericTunnelOptions, + pub obfuscation: Option<super::obfuscation::ObfuscatorConfig>, } /// Connection-specific configuration in [`TunnelParameters`]. @@ -34,14 +35,14 @@ impl ConnectionConfig { pub fn get_endpoint(&self) -> Endpoint { Endpoint { address: self.peer.endpoint, - protocol: self.peer.protocol, + protocol: TransportProtocol::Udp, } } pub fn get_exit_endpoint(&self) -> Option<Endpoint> { self.exit_peer.as_ref().map(|peer| Endpoint { address: peer.endpoint, - protocol: peer.protocol, + protocol: TransportProtocol::Udp, }) } } @@ -54,14 +55,6 @@ pub struct PeerConfig { pub allowed_ips: Vec<IpNetwork>, /// IP address of the WireGuard server. pub endpoint: SocketAddr, - /// Transport protocol. WireGuard only supports UDP directly. - /// If this is set to TCP, then traffic is proxied using [udp_over_tcp](https://github.com/mullvad/udp-over-tcp). - #[serde(default = "default_peer_transport")] - pub protocol: TransportProtocol, -} - -fn default_peer_transport() -> TransportProtocol { - TransportProtocol::Udp } #[derive(Clone, Eq, PartialEq, Deserialize, Serialize, Debug)] |
