diff options
| author | Odd Stranne <odd@mullvad.net> | 2022-03-24 10:55:57 +0100 |
|---|---|---|
| committer | Odd Stranne <odd@mullvad.net> | 2022-03-24 10:55:57 +0100 |
| commit | e4c2f269ef6fb6c39001f7fb0f963c713ea8dc5b (patch) | |
| tree | f715103d5cb043c7d34bf632012c45b8ad6059d3 | |
| parent | e53c4552cf57b9263f88cdce1ff55419efa9daa4 (diff) | |
| parent | 097d28ecf89a3acdaa854c469f0f79438b0aa67f (diff) | |
| download | mullvadvpn-e4c2f269ef6fb6c39001f7fb0f963c713ea8dc5b.tar.xz mullvadvpn-e4c2f269ef6fb6c39001f7fb0f963c713ea8dc5b.zip | |
Merge branch 'restructure-wg-obfuscation'
34 files changed, 1233 insertions, 354 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index d23d4b1c99..137be0e6d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,8 @@ Line wrap the file at 100 chars. Th - Add toggle for split tunneling state. ### Changed +- Update settings format to `v6`. +- Move WireGuard TCP obfuscation settings into `mullvad obfuscation` command in CLI. - Decrease the size of fonts, some icons and other design elements in the desktop app. This makes it possible to fit more into the same area and makes text easier to read. - Don't block the tunnel state machine while starting the tunnel monitor. This also means that 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/Cargo.toml b/Cargo.toml index 074445b867..51a87a03d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ members = [ "talpid-dbus", "talpid-platform-metadata", "mullvad-management-interface", + "tunnel-obfuscation", ] exclude = ["dist-assets/binaries/shadowsocks-rust"] diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts index 2497bceaeb..6e3fde5ea0 100644 --- a/gui/src/main/daemon-rpc.ts +++ b/gui/src/main/daemon-rpc.ts @@ -1206,7 +1206,7 @@ function convertFromWireguardConstraints( entryLocation: 'any', }; - const port = constraints.getPort()?.getPort(); + const port = constraints.getPort(); if (port) { result.port = { only: port }; } @@ -1332,10 +1332,7 @@ function convertToWireguardConstraints( const port = liftConstraint(constraint.port); if (port) { - const portConstraints = new grpcTypes.TransportPort(); - portConstraints.setPort(port); - portConstraints.setProtocol(grpcTypes.TransportProtocol.UDP); - wireguardConstraints.setPort(portConstraints); + wireguardConstraints.setPort(port); } const ipVersion = liftConstraint(constraint.ipVersion); 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-cli/src/cmds/mod.rs b/mullvad-cli/src/cmds/mod.rs index 2ceb3bfdcf..4c374d64ee 100644 --- a/mullvad-cli/src/cmds/mod.rs +++ b/mullvad-cli/src/cmds/mod.rs @@ -28,6 +28,9 @@ pub use self::dns::Dns; mod lan; pub use self::lan::Lan; +mod obfuscation; +pub use self::obfuscation::Obfuscation; + mod reconnect; pub use self::reconnect::Reconnect; @@ -64,6 +67,7 @@ pub fn get_commands() -> HashMap<&'static str, Box<dyn Command>> { Box::new(Dns), Box::new(Reconnect), Box::new(Lan), + Box::new(Obfuscation), Box::new(Relay), Box::new(Reset), #[cfg(any(target_os = "linux", windows))] diff --git a/mullvad-cli/src/cmds/obfuscation.rs b/mullvad-cli/src/cmds/obfuscation.rs new file mode 100644 index 0000000000..99887db555 --- /dev/null +++ b/mullvad-cli/src/cmds/obfuscation.rs @@ -0,0 +1,134 @@ +use crate::{new_rpc_client, Command, Result}; + +use mullvad_management_interface::{types as grpc_types, ManagementServiceClient}; + +use mullvad_types::relay_constraints::{ObfuscationSettings, SelectedObfuscation}; + +use std::convert::TryFrom; + +pub struct Obfuscation; + +#[mullvad_management_interface::async_trait] +impl Command for Obfuscation { + fn name(&self) -> &'static str { + "obfuscation" + } + + fn clap_subcommand(&self) -> clap::App<'static> { + clap::App::new(self.name()) + .about("Manage use of obfuscators") + .setting(clap::AppSettings::SubcommandRequiredElseHelp) + .subcommand(create_obfuscation_set_subcommand()) + .subcommand(create_obfuscation_get_subcommand()) + } + + async fn run(&self, matches: &clap::ArgMatches) -> Result<()> { + match matches.subcommand() { + Some(("set", set_matches)) => Self::handle_set(set_matches).await, + Some(("get", get_matches)) => Self::handle_get(get_matches).await, + _ => unreachable!("unhandled command"), + } + } +} + +impl Obfuscation { + async fn handle_set(matches: &clap::ArgMatches) -> Result<()> { + match matches.subcommand() { + Some(("mode", mode_matches)) => { + let obfuscator_type = mode_matches.value_of("mode").unwrap(); + let mut rpc = new_rpc_client().await?; + let mut settings = Self::get_obfuscation_settings(&mut rpc).await?; + settings.selected_obfuscation = match obfuscator_type { + "auto" => SelectedObfuscation::Auto, + "off" => SelectedObfuscation::Off, + "udp2tcp" => SelectedObfuscation::Udp2Tcp, + _ => unreachable!("Unhandled obfuscator mode"), + }; + Self::set_obfuscation_settings(&mut rpc, &settings).await?; + } + Some(("udp2tcp-settings", settings_matches)) => { + let port: String = settings_matches.value_of_t_or_exit("port"); + let mut rpc = new_rpc_client().await?; + let mut settings = Self::get_obfuscation_settings(&mut rpc).await?; + settings.udp2tcp.port = if port == "any" { + mullvad_types::relay_constraints::Constraint::Any + } else { + mullvad_types::relay_constraints::Constraint::Only( + port.parse::<u16>().expect("Invalid port number"), + ) + }; + Self::set_obfuscation_settings(&mut rpc, &settings).await?; + } + _ => unreachable!("unhandled command"), + } + Ok(()) + } + + async fn handle_get(matches: &clap::ArgMatches) -> Result<()> { + let mut rpc = new_rpc_client().await?; + let settings = Self::get_obfuscation_settings(&mut rpc).await?; + match matches.subcommand() { + Some(("udp2tcp-settings", _)) => println!("Udp2Tcp: {}", settings.udp2tcp), + _ => println!("Current settings: {}", settings), + } + Ok(()) + } + + async fn get_obfuscation_settings( + rpc: &mut ManagementServiceClient, + ) -> Result<ObfuscationSettings> { + let settings = rpc.get_settings(()).await?.into_inner(); + + let obfuscation_settings = ObfuscationSettings::try_from( + settings + .obfuscation_settings + .expect("No obfuscation settings"), + ) + .expect("failed to parse obfuscation settings"); + Ok(obfuscation_settings) + } + + async fn set_obfuscation_settings( + rpc: &mut ManagementServiceClient, + settings: &ObfuscationSettings, + ) -> Result<()> { + let grpc_settings: grpc_types::ObfuscationSettings = settings.into(); + let _ = rpc.set_obfuscation_settings(grpc_settings).await?; + Ok(()) + } +} + +fn create_obfuscation_set_subcommand() -> clap::App<'static> { + clap::App::new("set") + .about("Set obfuscation settings") + .setting(clap::AppSettings::SubcommandRequiredElseHelp) + .subcommand( + clap::App::new("mode").about("Set obfuscation mode").arg( + clap::Arg::new("mode") + .help("Specifies what kind of obfuscation should be used, if any") + .required(true) + .index(1) + .possible_values(&["auto", "off", "udp2tcp"]), + ), + ) + .subcommand( + clap::App::new("udp2tcp-settings") + .about("Specifies the config for the udp2tcp obfuscator") + .setting(clap::AppSettings::ArgRequiredElseHelp) + .arg( + clap::Arg::new("port") + .help("TCP port of remote endpoint. Either 'any' or a specific port") + .long("port") + .takes_value(true), + ), + ) +} + +fn create_obfuscation_get_subcommand() -> clap::App<'static> { + clap::App::new("get") + .about("Get obfuscation settings") + .subcommand( + clap::App::new("udp2tcp-settings") + .about("Specifies the config for the udp2tcp obfuscator"), + ) +} diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs index 8cb9fa111b..c11ee82158 100644 --- a/mullvad-cli/src/cmds/relay.rs +++ b/mullvad-cli/src/cmds/relay.rs @@ -61,14 +61,6 @@ impl Command for Relay { .multiple_values(true), ) .arg( - clap::Arg::new("protocol") - .help("Transport protocol. If TCP is selected, traffic is \ - sent over TCP using a udp-over-tcp proxy") - .long("protocol") - .default_value("udp") - .possible_values(&["udp", "tcp"]), - ) - .arg( clap::Arg::new("v6-gateway") .help("IPv6 gateway address") .long("v6-gateway") @@ -164,14 +156,6 @@ impl Command for Relay { .takes_value(true), ) .arg( - clap::Arg::new("transport protocol") - .help("Transport protocol. If TCP is selected, traffic is \ - sent over TCP using a udp-over-tcp proxy") - .long("protocol") - .possible_values(&["any", "udp", "tcp"]) - .takes_value(true), - ) - .arg( clap::Arg::new("ip version") .long("ipv") .possible_values(&["any", "4", "6"]) @@ -310,8 +294,6 @@ impl Relay { _ => e.exit(), }, }; - let protocol: String = matches.value_of_t_or_exit("protocol"); - let protocol = Self::validate_transport_protocol(&protocol); let mut private_key_str = String::new(); println!("Reading private key from standard input"); let _ = io::stdin().lock().read_line(&mut private_key_str); @@ -341,7 +323,6 @@ impl Relay { .collect(), endpoint: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port) .to_string(), - protocol: protocol as i32, }), ipv4_gateway: ipv4_gateway.to_string(), ipv6_gateway: ipv6_gateway @@ -547,8 +528,12 @@ impl Relay { let mut rpc = new_rpc_client().await?; let mut wireguard_constraints = self.get_wireguard_constraints(&mut rpc).await?; - wireguard_constraints.port = - parse_transport_port(matches, &mut wireguard_constraints.port)?; + if let Some(port) = matches.value_of("port") { + wireguard_constraints.port = match parse_port_constraint(port)? { + Constraint::Any => 0, + Constraint::Only(specific_port) => u32::from(specific_port), + } + } if let Some(ipv) = matches.value_of("ip version") { wireguard_constraints.ip_version = diff --git a/mullvad-cli/src/format.rs b/mullvad-cli/src/format.rs index eb91ffcca8..76ffcf7fb1 100644 --- a/mullvad-cli/src/format.rs +++ b/mullvad-cli/src/format.rs @@ -5,7 +5,8 @@ use mullvad_management_interface::types::{ }, tunnel_state, tunnel_state::State::*, - ErrorState, ProxyType, TransportProtocol, TunnelEndpoint, TunnelState, TunnelType, + ErrorState, ObfuscationType, ProxyType, TransportProtocol, TunnelEndpoint, TunnelState, + TunnelType, }; use mullvad_types::auth_failed::AuthFailed; use std::fmt::Write; @@ -83,6 +84,24 @@ fn format_endpoint(endpoint: &TunnelEndpoint) -> String { ) .unwrap(); } + if let Some(ref obfuscation) = endpoint.obfuscation { + write!( + &mut out, + " via {} {}:{} over {}", + match ObfuscationType::from_i32(obfuscation.obfuscation_type) + .expect("invalid obfuscation type") + { + ObfuscationType::Udp2tcp => "Udp2Tcp", + }, + obfuscation.address, + obfuscation.port, + format_protocol( + TransportProtocol::from_i32(obfuscation.protocol) + .expect("invalid transport protocol") + ) + ) + .unwrap(); + } } } 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/migrations/mod.rs b/mullvad-daemon/src/migrations/mod.rs index 98f3781061..5392f9828c 100644 --- a/mullvad-daemon/src/migrations/mod.rs +++ b/mullvad-daemon/src/migrations/mod.rs @@ -43,7 +43,6 @@ mod v1; mod v2; mod v3; mod v4; -// Not yet done. Add to this instead of creating v6 for now. mod v5; const SETTINGS_FILE: &str = "settings.json"; diff --git a/mullvad-daemon/src/migrations/v5.rs b/mullvad-daemon/src/migrations/v5.rs index 4231e52a23..ba12c5ae91 100644 --- a/mullvad-daemon/src/migrations/v5.rs +++ b/mullvad-daemon/src/migrations/v5.rs @@ -1,5 +1,5 @@ use super::{Error, Result}; -use mullvad_types::settings::SettingsVersion; +use mullvad_types::{relay_constraints::Constraint, settings::SettingsVersion}; // ====================================================== // Section for vendoring types and values that @@ -7,6 +7,45 @@ use mullvad_types::settings::SettingsVersion; pub type AccountToken = String; +/// Representation of a transport protocol, either UDP or TCP. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum TransportProtocol { + /// Represents the UDP transport protocol. + Udp, + /// Represents the TCP transport protocol. + Tcp, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct TransportPort { + pub protocol: TransportProtocol, + pub port: Constraint<u16>, +} + +/// Contains obfuscation settings +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct ObfuscationSettings { + pub selected_obfuscation: SelectedObfuscation, + pub udp2tcp: Udp2TcpObfuscationSettings, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct Udp2TcpObfuscationSettings { + pub port: Constraint<u16>, +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum SelectedObfuscation { + Auto, + Off, + Udp2Tcp, +} + // ====================================================== pub(crate) struct MigrationData { @@ -14,13 +53,6 @@ pub(crate) struct MigrationData { pub wg_data: Option<serde_json::Value>, } -/// This is an open ended migration. There is no v6 yet! -/// The migrations performed by this function are still backwards compatible. -/// The JSON coming out of this migration can be read by any v5 compatible daemon. -/// -/// When further migrations are needed, add them here and if they are not backwards -/// compatible then create v6 and "close" this migration for further modification. -/// /// # Changes to the format /// /// The ability to disable WireGuard multihop while preserving the entry location was added. @@ -32,61 +64,105 @@ pub(crate) struct MigrationData { /// is null in order to make it default back to the default location. /// /// This also removes the account token and WireGuard key from the settings. +/// +/// Additionally, the WireGuard protocol constraint, if set to be using TCP, is migrated into +/// having an active Udp2Tcp obfuscator. The protocol constraint is then removed from WireGuard +/// settings since all WireGuard traffic is UDP. pub(crate) async fn migrate(settings: &mut serde_json::Value) -> Result<Option<MigrationData>> { if !version_matches(settings) { return Ok(None); } - let wireguard_constraints = || -> Option<&serde_json::Value> { - settings - .get("relay_settings")? - .get("normal")? - .get("wireguard_constraints") - }(); - if let Some(constraints) = wireguard_constraints { - if let Some(location) = constraints.get("entry_location") { - if constraints.get("use_multihop").is_none() { + if let Some(wireguard_constraints) = get_wireguard_constraints(settings) { + if let Some(location) = wireguard_constraints.get("entry_location") { + if wireguard_constraints.get("use_multihop").is_none() { if location.is_null() { // "Null" is no longer valid. It is not an option. - settings["relay_settings"]["normal"]["wireguard_constraints"] + wireguard_constraints .as_object_mut() .ok_or(Error::NoMatchingVersion)? .remove("entry_location"); } else { - settings["relay_settings"]["normal"]["wireguard_constraints"]["use_multihop"] = - serde_json::json!(true); + wireguard_constraints["use_multihop"] = serde_json::json!(true); + } + } + } + // The field `pub port: Constraint<TransportPort>` is now `pub port: Constraint<u16>`. + // Data is migrated as follows: + // If the existing field has `protocol == Tcp` configured, then we need to create a + // corresponding setting to enable the Udp2Tcp obfuscator. In this case, the port + // constraint is moved as well. Otherwise the existing port constraint is moved into + // the new field. + // + if let Some(port) = wireguard_constraints.get("port") { + let port_constraint: Constraint<TransportPort> = + serde_json::from_value(port.clone()).map_err(Error::ParseError)?; + if let Some(transport_port) = port_constraint.option() { + let (port, obfuscation_settings) = match transport_port.protocol { + TransportProtocol::Udp => (serde_json::json!(transport_port.port), None), + TransportProtocol::Tcp => ( + serde_json::json!(Constraint::<u16>::Any), + Some(serde_json::json!(create_migrated_obfuscation_settings( + transport_port.port + ))), + ), + }; + wireguard_constraints["port"] = port; + if let Some(obfuscation_settings) = obfuscation_settings { + settings["obfuscation_settings"] = obfuscation_settings; } } } } - if let Some(token) = settings.get("account_token").filter(|t| !t.is_null()) { + let migration_data = if let Some(token) = settings.get("account_token").filter(|t| !t.is_null()) + { let token: AccountToken = serde_json::from_value(token.clone()).map_err(Error::ParseError)?; let migration_data = if let Some(wg_data) = settings.get("wireguard").filter(|wg| !wg.is_null()) { - Ok(Some(MigrationData { + Some(MigrationData { token, wg_data: Some(wg_data.clone()), - })) + }) } else { - Ok(Some(MigrationData { + Some(MigrationData { token, wg_data: None, - })) + }) }; let settings_map = settings.as_object_mut().ok_or(Error::NoMatchingVersion)?; settings_map.remove("account_token"); settings_map.remove("wireguard"); - return migration_data; - } + migration_data + } else { + None + }; - // Note: Not incrementing the version number yet, since this migration is still open - // for future modification. - // settings["settings_version"] = serde_json::json!(SettingsVersion::V6); + settings["settings_version"] = serde_json::json!(SettingsVersion::V6); + + Ok(migration_data) +} - Ok(None) +fn get_wireguard_constraints(settings: &mut serde_json::Value) -> Option<&mut serde_json::Value> { + if let Some(relay_settings) = settings.get_mut("relay_settings") { + if let Some(normal) = relay_settings.get_mut("normal") { + return normal.get_mut("wireguard_constraints"); + } + } + None +} + +// Create an ObfuscationSettings struct that replaces the `protocol == TCP` setting +// that was previously used on the wireguard constraints. +// If a port is specified, this is the remote port to be used for Udp2Tcp. +// +fn create_migrated_obfuscation_settings(port: Constraint<u16>) -> ObfuscationSettings { + ObfuscationSettings { + selected_obfuscation: SelectedObfuscation::Udp2Tcp, + udp2tcp: Udp2TcpObfuscationSettings { port }, + } } fn version_matches(settings: &mut serde_json::Value) -> bool { @@ -101,7 +177,7 @@ mod test { use super::{migrate, version_matches}; use serde_json; - pub const V5_SETTINGS_V1: &str = r#" + pub const V5_SETTINGS: &str = r#" { "account_token": "1234", "relay_settings": { @@ -113,7 +189,12 @@ mod test { }, "tunnel_protocol": "any", "wireguard_constraints": { - "port": "any", + "port": { + "only": { + "protocol": "tcp", + "port": "any" + } + }, "ip_version": "any", "entry_location": "any" }, @@ -170,7 +251,7 @@ mod test { } "#; - pub const V5_SETTINGS_V2: &str = r#" + pub const V6_SETTINGS: &str = r#" { "relay_settings": { "normal": { @@ -203,6 +284,12 @@ mod test { "location": "any" } }, + "obfuscation_settings": { + "selected_obfuscation": "udp2_tcp", + "udp2tcp": { + "port": "any" + } + }, "bridge_state": "auto", "allow_lan": true, "block_when_disconnected": false, @@ -235,17 +322,17 @@ mod test { } } }, - "settings_version": 5 + "settings_version": 6 } "#; #[tokio::test] - async fn test_v5_v1_migration() { - let mut old_settings = serde_json::from_str(V5_SETTINGS_V1).unwrap(); + async fn test_v5_to_v6_migration() { + let mut old_settings = serde_json::from_str(V5_SETTINGS).unwrap(); assert!(version_matches(&mut old_settings)); migrate(&mut old_settings).await.unwrap(); - let new_settings: serde_json::Value = serde_json::from_str(V5_SETTINGS_V2).unwrap(); + let new_settings: serde_json::Value = serde_json::from_str(V6_SETTINGS).unwrap(); assert_eq!(&old_settings, &new_settings); } 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..638a0d01e4 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, }; @@ -17,7 +18,7 @@ use talpid_types::net::{self, openvpn, GenericTunnelOptions}; /// latest version that exists in `SettingsVersion`. /// This should be bumped when a new version is introduced along with a migration /// being added to `mullvad-daemon`. -pub const CURRENT_SETTINGS_VERSION: SettingsVersion = SettingsVersion::V5; +pub const CURRENT_SETTINGS_VERSION: SettingsVersion = SettingsVersion::V6; #[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] #[repr(u32)] @@ -26,6 +27,7 @@ pub enum SettingsVersion { V3 = 3, V4 = 4, V5 = 5, + V6 = 6, } impl<'de> Deserialize<'de> for SettingsVersion { @@ -38,6 +40,7 @@ impl<'de> Deserialize<'de> for SettingsVersion { v if v == SettingsVersion::V3 as u32 => Ok(SettingsVersion::V3), v if v == SettingsVersion::V4 as u32 => Ok(SettingsVersion::V4), v if v == SettingsVersion::V5 as u32 => Ok(SettingsVersion::V5), + v if v == SettingsVersion::V6 as u32 => Ok(SettingsVersion::V6), v => Err(serde::de::Error::custom(format!( "{} is not a valid SettingsVersion", v @@ -65,6 +68,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 +109,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)] diff --git a/tunnel-obfuscation/Cargo.toml b/tunnel-obfuscation/Cargo.toml new file mode 100644 index 0000000000..02b7f9370e --- /dev/null +++ b/tunnel-obfuscation/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "tunnel-obfuscation" +version = "0.1.0" +authors = ["Mullvad VPN"] +description = "Provides different types of obfuscation layers for WireGuard" +license = "GPL-3.0" +edition = "2021" +publish = false + +[dependencies] +async-trait = "0.1" +err-derive = "0.3.0" +futures = "0.3.5" +tokio = { version = "1.8", features = ["rt-multi-thread", "macros", "net", "io-util"] } +udp-over-tcp = { git = "https://github.com/mullvad/udp-over-tcp", rev = "27b9519b63244736b6f3c7c4af60976c88dc6b95" } diff --git a/tunnel-obfuscation/src/lib.rs b/tunnel-obfuscation/src/lib.rs new file mode 100644 index 0000000000..c59fa284fd --- /dev/null +++ b/tunnel-obfuscation/src/lib.rs @@ -0,0 +1,35 @@ +use async_trait::async_trait; +use std::net::SocketAddr; + +mod udp2tcp; +pub use udp2tcp::Udp2TcpSettings; + +pub type Result<T> = std::result::Result<T, Error>; + +#[derive(err_derive::Error, Debug)] +#[error(no_from)] +pub enum Error { + #[error(display = "Failed to create Udp2Tcp obfuscator")] + CreateUdp2TcpObfuscator(#[error(source)] udp2tcp::Error), + + #[error(display = "Failed to run Udp2Tcp obfuscator")] + RunUdp2TcpObfuscator(#[error(source)] udp2tcp::Error), +} + +#[async_trait] +pub trait Obfuscator: Send { + fn endpoint(&self) -> SocketAddr; + async fn run(self: Box<Self>) -> Result<()>; +} + +pub enum Settings { + Udp2Tcp(Udp2TcpSettings), +} + +pub async fn create_obfuscator(settings: &Settings) -> Result<Box<dyn Obfuscator>> { + match settings { + Settings::Udp2Tcp(s) => udp2tcp::create_obfuscator(s) + .await + .map_err(Error::CreateUdp2TcpObfuscator), + } +} diff --git a/tunnel-obfuscation/src/main.rs b/tunnel-obfuscation/src/main.rs new file mode 100644 index 0000000000..db613f773b --- /dev/null +++ b/tunnel-obfuscation/src/main.rs @@ -0,0 +1,36 @@ +use std::{env::args, net::SocketAddr}; +use tunnel_obfuscation::{create_obfuscator, Obfuscator, Settings, Udp2TcpSettings}; + +#[tokio::main] +async fn main() { + if args().len() != 2 { + println!("Missing arguments"); + } + + let obfuscator = instantiate_requested(&args().last().unwrap()).await; + + println!("endpoint() returns {:?}", obfuscator.endpoint()); + + if let Err(err) = obfuscator.run().await { + println!("obfuscator.run() failed: {:?}", err); + } +} + +async fn instantiate_requested(obfuscator_type: &String) -> Box<dyn Obfuscator> { + match obfuscator_type.as_str() { + "udp2tcp" => { + let settings = Udp2TcpSettings { + peer: SocketAddr::new("127.0.0.1".parse().unwrap(), 3030), + #[cfg(target_os = "linux")] + fwmark: Some(1337), + }; + + create_obfuscator(&Settings::Udp2Tcp(settings)) + .await + .expect("Creating obfuscator failed") + } + _ => { + unimplemented!() + } + } +} diff --git a/tunnel-obfuscation/src/udp2tcp.rs b/tunnel-obfuscation/src/udp2tcp.rs new file mode 100644 index 0000000000..877b53bd2f --- /dev/null +++ b/tunnel-obfuscation/src/udp2tcp.rs @@ -0,0 +1,88 @@ +use crate::Obfuscator; +use async_trait::async_trait; +use std::net::SocketAddr; +use udp_over_tcp::{ + udp2tcp::{ConnectError, ForwardError, Udp2Tcp as Udp2TcpImpl}, + TcpOptions, +}; + +pub struct Udp2TcpSettings { + pub peer: SocketAddr, + #[cfg(target_os = "linux")] + pub fwmark: Option<u32>, +} + +pub type Result<T> = std::result::Result<T, Error>; + +#[derive(err_derive::Error, Debug)] +#[error(no_from)] +pub enum Error { + /// Failed to create obfuscator + #[error(display = "Failed to create obfuscator")] + CreateObfuscator(#[error(source)] ConnectError), + + /// Failed to determine UDP socket details + #[error(display = "Failed to determine UDP socket details")] + GetUdpSocketDetails(#[error(source)] std::io::Error), + + /// Failed to run obfuscator + #[error(display = "Failed to run obfuscator")] + RunObfuscator(#[error(source)] ForwardError), +} + +struct Udp2Tcp { + local_addr: SocketAddr, + instance: Udp2TcpImpl, +} + +impl Udp2Tcp { + pub async fn new(settings: &Udp2TcpSettings) -> Result<Self> { + let listen_addr = if settings.peer.is_ipv4() { + SocketAddr::new("127.0.0.1".parse().unwrap(), 0) + } else { + SocketAddr::new("::1".parse().unwrap(), 0) + }; + + let instance = Udp2TcpImpl::new( + listen_addr, + settings.peer, + #[cfg(target_os = "linux")] + TcpOptions { + recv_buffer_size: None, + send_buffer_size: None, + fwmark: settings.fwmark, + }, + #[cfg(not(target_os = "linux"))] + TcpOptions::default(), + ) + .await + .map_err(Error::CreateObfuscator)?; + let local_addr = instance + .local_udp_addr() + .map_err(Error::GetUdpSocketDetails)?; + + Ok(Self { + local_addr, + instance, + }) + } +} + +#[async_trait] +impl Obfuscator for Udp2Tcp { + fn endpoint(&self) -> SocketAddr { + self.local_addr + } + + async fn run(self: Box<Self>) -> crate::Result<()> { + self.instance + .run() + .await + .map_err(Error::RunObfuscator) + .map_err(crate::Error::RunUdp2TcpObfuscator) + } +} + +pub async fn create_obfuscator(settings: &Udp2TcpSettings) -> Result<Box<dyn Obfuscator>> { + Ok(Box::new(Udp2Tcp::new(settings).await?)) +} |
