diff options
| author | David Lönnhager <david.l@mullvad.net> | 2024-05-30 17:20:46 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2024-08-16 09:13:30 +0200 |
| commit | 0945cdb9ac304ff4c46144e290198cdae810e17d (patch) | |
| tree | 983464d7ff281a8af9d9cf519b40cd6db40e0d39 | |
| parent | 39c0a267382e51831652ff0966df8fb180b989d8 (diff) | |
| download | mullvadvpn-0945cdb9ac304ff4c46144e290198cdae810e17d.tar.xz mullvadvpn-0945cdb9ac304ff4c46144e290198cdae810e17d.zip | |
Add Shadowsocks obfuscation support to mullvad-daemon
17 files changed, 341 insertions, 153 deletions
diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt index 50599ad7f4..9baa426696 100644 --- a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt +++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt @@ -166,11 +166,13 @@ internal fun ManagementInterface.ObfuscationEndpoint.toDomain(): ObfuscationEndp obfuscationType = obfuscationType.toDomain() ) -internal fun ManagementInterface.ObfuscationType.toDomain(): ObfuscationType = +internal fun ManagementInterface.ObfuscationEndpoint.ObfuscationType.toDomain(): ObfuscationType = when (this) { - ManagementInterface.ObfuscationType.UDP2TCP -> ObfuscationType.Udp2Tcp - ManagementInterface.ObfuscationType.UNRECOGNIZED -> + ManagementInterface.ObfuscationEndpoint.ObfuscationType.UDP2TCP -> ObfuscationType.Udp2Tcp + ManagementInterface.ObfuscationEndpoint.ObfuscationType.UNRECOGNIZED -> throw IllegalArgumentException("Unrecognized obfuscation type") + ManagementInterface.ObfuscationEndpoint.ObfuscationType.SHADOWSOCKS -> + throw IllegalArgumentException("Shadowsocks is unsupported") } internal fun ManagementInterface.TransportProtocol.toDomain(): TransportProtocol = @@ -340,6 +342,8 @@ internal fun ManagementInterface.ObfuscationSettings.SelectedObfuscation.toDomai ManagementInterface.ObfuscationSettings.SelectedObfuscation.OFF -> SelectedObfuscation.Off ManagementInterface.ObfuscationSettings.SelectedObfuscation.UDP2TCP -> SelectedObfuscation.Udp2Tcp + ManagementInterface.ObfuscationSettings.SelectedObfuscation.SHADOWSOCKS -> + throw IllegalArgumentException("Shadowsocks is unsupported") ManagementInterface.ObfuscationSettings.SelectedObfuscation.UNRECOGNIZED -> throw IllegalArgumentException("Unrecognized selected obfuscation") } diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationType.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationType.kt index cd71d645af..4e7cb1f5a8 100644 --- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationType.kt +++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationType.kt @@ -1,5 +1,6 @@ package net.mullvad.mullvadvpn.lib.model enum class ObfuscationType { - Udp2Tcp + Udp2Tcp, + Shadowsocks } diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts index 3461a85ac5..1adfe4c2ae 100644 --- a/gui/src/main/daemon-rpc.ts +++ b/gui/src/main/daemon-rpc.ts @@ -29,7 +29,6 @@ import { DeviceEvent, DeviceState, DirectMethod, - EndpointObfuscationType, ErrorState, ErrorStateCause, FirewallPolicyError, @@ -403,6 +402,8 @@ export class DaemonRpc { grpcObfuscationSettings.setUdp2tcp(grpcUdp2tcpSettings); } + grpcObfuscationSettings.setShadowsocks(new grpcTypes.ShadowsocksSettings()); + await this.call<grpcTypes.ObfuscationSettings, Empty>( this.client.setObfuscationSettings, grpcObfuscationSettings, @@ -1151,14 +1152,17 @@ function convertFromProxyEndpoint(proxyEndpoint: grpcTypes.ProxyEndpoint.AsObjec function convertFromObfuscationEndpoint( obfuscationEndpoint: grpcTypes.ObfuscationEndpoint.AsObject, ): IObfuscationEndpoint { - const obfuscationTypes: Record<grpcTypes.ObfuscationType, EndpointObfuscationType> = { - [grpcTypes.ObfuscationType.UDP2TCP]: 'udp2tcp', - }; + // TODO: Handle Shadowsocks (and other implemented protocols) + if ( + obfuscationEndpoint.obfuscationType !== grpcTypes.ObfuscationEndpoint.ObfuscationType.UDP2TCP + ) { + throw new Error('unsupported obfuscation protocol'); + } return { ...obfuscationEndpoint, protocol: convertFromTransportProtocol(obfuscationEndpoint.protocol), - obfuscationType: obfuscationTypes[obfuscationEndpoint.obfuscationType], + obfuscationType: 'udp2tcp', }; } diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index e2881dbf90..12a0e25900 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -263,11 +263,12 @@ enum FeatureIndicator { DAITA = 12; } -enum ObfuscationType { - UDP2TCP = 0; -} - message ObfuscationEndpoint { + enum ObfuscationType { + UDP2TCP = 0; + SHADOWSOCKS = 1; + } + string address = 1; uint32 port = 2; TransportProtocol protocol = 3; @@ -352,14 +353,18 @@ message BridgeState { message Udp2TcpObfuscationSettings { optional uint32 port = 1; } +message ShadowsocksSettings { optional uint32 port = 1; } + message ObfuscationSettings { enum SelectedObfuscation { AUTO = 0; OFF = 1; UDP2TCP = 2; + SHADOWSOCKS = 3; } SelectedObfuscation selected_obfuscation = 1; Udp2TcpObfuscationSettings udp2tcp = 2; + ShadowsocksSettings shadowsocks = 3; } message CustomList { diff --git a/mullvad-management-interface/src/types/conversions/net.rs b/mullvad-management-interface/src/types/conversions/net.rs index e9c8a94838..93af637e37 100644 --- a/mullvad-management-interface/src/types/conversions/net.rs +++ b/mullvad-management-interface/src/types/conversions/net.rs @@ -29,7 +29,12 @@ impl From<talpid_types::net::TunnelEndpoint> for proto::TunnelEndpoint { obfuscation_endpoint.endpoint.protocol, )), obfuscation_type: match obfuscation_endpoint.obfuscation_type { - net::ObfuscationType::Udp2Tcp => i32::from(proto::ObfuscationType::Udp2tcp), + net::ObfuscationType::Udp2Tcp => { + i32::from(proto::obfuscation_endpoint::ObfuscationType::Udp2tcp) + } + net::ObfuscationType::Shadowsocks => { + i32::from(proto::obfuscation_endpoint::ObfuscationType::Shadowsocks) + } }, } }), @@ -100,18 +105,22 @@ impl TryFrom<proto::TunnelEndpoint> for talpid_types::net::TunnelEndpoint { ), protocol: try_transport_protocol_from_i32(obfs_ep.protocol)?, }, - obfuscation_type: match proto::ObfuscationType::try_from( - obfs_ep.obfuscation_type, - ) { - Ok(proto::ObfuscationType::Udp2tcp) => { - talpid_net::ObfuscationType::Udp2Tcp - } - Err(_) => { - return Err(FromProtobufTypeError::InvalidArgument( - "unknown obfuscation type", - )) - } - }, + obfuscation_type: + match proto::obfuscation_endpoint::ObfuscationType::try_from( + obfs_ep.obfuscation_type, + ) { + Ok(proto::obfuscation_endpoint::ObfuscationType::Udp2tcp) => { + talpid_net::ObfuscationType::Udp2Tcp + } + Ok(proto::obfuscation_endpoint::ObfuscationType::Shadowsocks) => { + talpid_net::ObfuscationType::Shadowsocks + } + Err(_) => { + return Err(FromProtobufTypeError::InvalidArgument( + "unknown obfuscation type", + )) + } + }, }) }) .transpose()?, diff --git a/mullvad-management-interface/src/types/conversions/relay_constraints.rs b/mullvad-management-interface/src/types/conversions/relay_constraints.rs index 47d097abe9..ef0afd02b4 100644 --- a/mullvad-management-interface/src/types/conversions/relay_constraints.rs +++ b/mullvad-management-interface/src/types/conversions/relay_constraints.rs @@ -152,10 +152,14 @@ impl From<&mullvad_types::relay_constraints::ObfuscationSettings> for proto::Obf SelectedObfuscation::Udp2Tcp => { proto::obfuscation_settings::SelectedObfuscation::Udp2tcp } + SelectedObfuscation::Shadowsocks => { + proto::obfuscation_settings::SelectedObfuscation::Shadowsocks + } }); Self { selected_obfuscation, udp2tcp: Some(proto::Udp2TcpObfuscationSettings::from(&settings.udp2tcp)), + shadowsocks: Some(proto::ShadowsocksSettings::from(&settings.shadowsocks)), } } } @@ -176,6 +180,14 @@ impl From<&mullvad_types::relay_constraints::Udp2TcpObfuscationSettings> } } +impl From<&mullvad_types::relay_constraints::ShadowsocksSettings> for proto::ShadowsocksSettings { + fn from(settings: &mullvad_types::relay_constraints::ShadowsocksSettings) -> Self { + Self { + port: settings.port.map(u32::from).option(), + } + } +} + impl From<mullvad_types::relay_constraints::BridgeSettings> for proto::BridgeSettings { fn from(settings: mullvad_types::relay_constraints::BridgeSettings) -> Self { use proto::bridge_settings; @@ -438,9 +450,10 @@ impl TryFrom<proto::ObfuscationSettings> for mullvad_types::relay_constraints::O Ok(IpcSelectedObfuscation::Auto) => SelectedObfuscation::Auto, Ok(IpcSelectedObfuscation::Off) => SelectedObfuscation::Off, Ok(IpcSelectedObfuscation::Udp2tcp) => SelectedObfuscation::Udp2Tcp, + Ok(IpcSelectedObfuscation::Shadowsocks) => SelectedObfuscation::Shadowsocks, Err(_) => { return Err(FromProtobufTypeError::InvalidArgument( - "invalid selected obfuscator", + "invalid obfuscation settings", )); } }; @@ -451,7 +464,17 @@ impl TryFrom<proto::ObfuscationSettings> for mullvad_types::relay_constraints::O } None => { return Err(FromProtobufTypeError::InvalidArgument( - "invalid selected obfuscator", + "invalid udp2tcp settings", + )); + } + }; + let shadowsocks = match settings.shadowsocks { + Some(settings) => { + mullvad_types::relay_constraints::ShadowsocksSettings::try_from(&settings)? + } + None => { + return Err(FromProtobufTypeError::InvalidArgument( + "invalid shadowsocks settings", )); } }; @@ -459,6 +482,7 @@ impl TryFrom<proto::ObfuscationSettings> for mullvad_types::relay_constraints::O Ok(Self { selected_obfuscation, udp2tcp, + shadowsocks, }) } } @@ -475,6 +499,18 @@ impl TryFrom<&proto::Udp2TcpObfuscationSettings> } } +impl TryFrom<&proto::ShadowsocksSettings> + for mullvad_types::relay_constraints::ShadowsocksSettings +{ + type Error = FromProtobufTypeError; + + fn try_from(settings: &proto::ShadowsocksSettings) -> Result<Self, Self::Error> { + Ok(Self { + port: Constraint::from(settings.port.map(|port| port as u16)), + }) + } +} + impl TryFrom<proto::BridgeState> for mullvad_types::relay_constraints::BridgeState { type Error = FromProtobufTypeError; diff --git a/mullvad-relay-selector/src/relay_selector/detailer.rs b/mullvad-relay-selector/src/relay_selector/detailer.rs index 5c387c41f5..ca28de36ec 100644 --- a/mullvad-relay-selector/src/relay_selector/detailer.rs +++ b/mullvad-relay-selector/src/relay_selector/detailer.rs @@ -41,10 +41,8 @@ pub enum Error { MissingPublicKey, #[error("The selected relay does not support IPv6")] NoIPv6(Box<Relay>), - #[error("Invalid port argument: port {0} is not in any valid Wireguard port range")] - PortNotInRange(u16), - #[error("Port selection algorithm is broken")] - PortSelectionAlgorithm, + #[error("Failed to select port")] + PortSelectionError(#[source] super::helpers::Error), } /// Constructs a [`MullvadWireguardEndpoint`] with details for how to connect to a Wireguard relay. @@ -175,20 +173,8 @@ fn get_port_for_wireguard_relay( query: &WireguardRelayQuery, data: &WireguardEndpointData, ) -> Result<u16, Error> { - match query.port { - Constraint::Any => select_random_port(&data.port_ranges), - Constraint::Only(port) => { - if data - .port_ranges - .iter() - .any(|range| (range.0 <= port && port <= range.1)) - { - Ok(port) - } else { - Err(Error::PortNotInRange(port)) - } - } - } + super::helpers::select_random_port(query.port, &data.port_ranges) + .map_err(Error::PortSelectionError) } /// Read the [`PublicKey`] of a relay. This will only succeed if [relay][`Relay`] is a @@ -200,39 +186,6 @@ const fn get_public_key(relay: &Relay) -> Result<&PublicKey, Error> { } } -/// Selects a random port number from a list of provided port ranges. -/// -/// This function iterates over a list of port ranges, each represented as a tuple (u16, u16) -/// where the first element is the start of the range and the second is the end (inclusive), -/// and selects a random port from the set of all ranges. -/// -/// # Parameters -/// - `port_ranges`: A slice of tuples, each representing a range of valid port numbers. -/// -/// # Returns -/// - `Option<u16>`: A randomly selected port number within the given ranges, or `None` if the input -/// is empty or the total number of available ports is zero. -fn select_random_port(port_ranges: &[(u16, u16)]) -> Result<u16, Error> { - use rand::Rng; - let get_port_amount = |range: &(u16, u16)| -> u64 { (1 + range.1 - range.0) as u64 }; - let port_amount: u64 = port_ranges.iter().map(get_port_amount).sum(); - - if port_amount < 1 { - return Err(Error::PortSelectionAlgorithm); - } - - let mut port_index = rand::thread_rng().gen_range(0..port_amount); - - for range in port_ranges.iter() { - let ports_in_range = get_port_amount(range); - if port_index < ports_in_range { - return Ok(port_index as u16 + range.0); - } - port_index -= ports_in_range; - } - Err(Error::PortSelectionAlgorithm) -} - /// Constructs an [`Endpoint`] with details for how to connect to an OpenVPN relay. /// /// If this endpoint is to be used in conjunction with a bridge, the resulting endpoint is diff --git a/mullvad-relay-selector/src/relay_selector/helpers.rs b/mullvad-relay-selector/src/relay_selector/helpers.rs index 442b1f596f..7964bef71f 100644 --- a/mullvad-relay-selector/src/relay_selector/helpers.rs +++ b/mullvad-relay-selector/src/relay_selector/helpers.rs @@ -3,14 +3,24 @@ use std::net::SocketAddr; use mullvad_types::{ - constraints::Constraint, endpoint::MullvadWireguardEndpoint, - relay_constraints::Udp2TcpObfuscationSettings, relay_list::Relay, + constraints::Constraint, + endpoint::MullvadWireguardEndpoint, + relay_constraints::{ShadowsocksSettings, Udp2TcpObfuscationSettings}, + relay_list::Relay, }; use rand::{seq::SliceRandom, thread_rng, Rng}; use talpid_types::net::obfuscation::ObfuscatorConfig; use crate::SelectedObfuscator; +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Port selection algorithm is broken")] + PortSelectionAlgorithm, + #[error("Invalid port argument: port {0} is not in any valid Wireguard port range")] + PortNotInRange(u16), +} + /// Picks a relay using [pick_random_relay_weighted], using the `weight` member of each relay /// as the weight function. pub fn pick_random_relay(relays: &[Relay]) -> Option<&Relay> { @@ -86,3 +96,69 @@ pub fn get_udp2tcp_obfuscator_port( udp2tcp_ports.choose(&mut thread_rng()).copied() } } + +pub fn get_shadowsocks_obfuscator( + settings: &ShadowsocksSettings, + port_ranges: &[(u16, u16)], + relay: Relay, + endpoint: &MullvadWireguardEndpoint, +) -> Option<SelectedObfuscator> { + let port = select_random_port(settings.port, port_ranges).ok()?; + + let config = ObfuscatorConfig::Shadowsocks { + endpoint: SocketAddr::new(endpoint.peer.endpoint.ip(), port), + }; + + Some(SelectedObfuscator { config, relay }) +} + +/// Selects a random port number from a list of provided port ranges. +/// +/// This function iterates over a list of port ranges, each represented as a tuple (u16, u16) +/// where the first element is the start of the range and the second is the end (inclusive), +/// and selects a random port from the set of all ranges. +/// +/// # Parameters +/// - `port`: Constraint to apply to the port selection +/// - `port_ranges`: A slice of tuples, each representing a range of valid port numbers. +/// +/// # Returns +/// - A randomly selected port number within the given ranges. +/// +/// # Panic +/// - If port ranges contains no ports, this function panics. +pub fn select_random_port(port: Constraint<u16>, port_ranges: &[(u16, u16)]) -> Result<u16, Error> { + match port { + Constraint::Any => select_random_port_inner(port_ranges), + Constraint::Only(port) => { + if port_ranges + .iter() + .any(|range| (range.0 <= port && port <= range.1)) + { + Ok(port) + } else { + Err(Error::PortNotInRange(port)) + } + } + } +} + +fn select_random_port_inner(port_ranges: &[(u16, u16)]) -> Result<u16, Error> { + let get_port_amount = |range: &(u16, u16)| -> u64 { (1 + range.1 - range.0) as u64 }; + let port_amount: u64 = port_ranges.iter().map(get_port_amount).sum(); + + if port_amount < 1 { + return Err(Error::PortSelectionAlgorithm); + } + + let mut port_index = rand::thread_rng().gen_range(0..port_amount); + + for range in port_ranges.iter() { + let ports_in_range = get_port_amount(range); + if port_index < ports_in_range { + return Ok(port_index as u16 + range.0); + } + port_index -= ports_in_range; + } + Err(Error::PortSelectionAlgorithm) +} diff --git a/mullvad-relay-selector/src/relay_selector/mod.rs b/mullvad-relay-selector/src/relay_selector/mod.rs index 7e6acab43a..5c5b324081 100644 --- a/mullvad-relay-selector/src/relay_selector/mod.rs +++ b/mullvad-relay-selector/src/relay_selector/mod.rs @@ -790,19 +790,34 @@ impl RelaySelector { endpoint: &MullvadWireguardEndpoint, parsed_relays: &ParsedRelays, ) -> Result<Option<SelectedObfuscator>, Error> { + let obfuscator_relay = match relay { + WireguardConfig::Singlehop { exit } => exit, + WireguardConfig::Multihop { entry, .. } => entry, + }; match &query.wireguard_constraints.obfuscation { ObfuscationQuery::Off | ObfuscationQuery::Auto => Ok(None), - ObfuscationQuery::Udp2tcp { port } => { - let obfuscator_relay = match relay { - WireguardConfig::Singlehop { exit } => exit, - WireguardConfig::Multihop { entry, .. } => entry, - }; + ObfuscationQuery::Udp2tcp(settings) => { let udp2tcp_ports = &parsed_relays.parsed_list().wireguard.udp2tcp_ports; - helpers::get_udp2tcp_obfuscator(port, udp2tcp_ports, obfuscator_relay, endpoint) + helpers::get_udp2tcp_obfuscator(settings, udp2tcp_ports, obfuscator_relay, endpoint) .map(Some) .ok_or(Error::NoObfuscator) } + ObfuscationQuery::Shadowsocks(settings) => { + let port_ranges = &parsed_relays + .parsed_list() + .wireguard + .shadowsocks_port_ranges; + let obfuscation = helpers::get_shadowsocks_obfuscator( + &settings, + port_ranges, + obfuscator_relay, + endpoint, + ) + .ok_or(Error::NoObfuscator)?; + + Ok(Some(obfuscation)) + } } } diff --git a/mullvad-relay-selector/src/relay_selector/query.rs b/mullvad-relay-selector/src/relay_selector/query.rs index 48ad5bd1dc..0faa9c1ca9 100644 --- a/mullvad-relay-selector/src/relay_selector/query.rs +++ b/mullvad-relay-selector/src/relay_selector/query.rs @@ -33,8 +33,8 @@ use mullvad_types::{ constraints::Constraint, relay_constraints::{ BridgeConstraints, LocationConstraint, ObfuscationSettings, OpenVpnConstraints, Ownership, - Providers, RelayConstraints, RelaySettings, SelectedObfuscation, TransportPort, - Udp2TcpObfuscationSettings, WireguardConstraints, + Providers, RelayConstraints, RelaySettings, SelectedObfuscation, ShadowsocksSettings, + TransportPort, Udp2TcpObfuscationSettings, WireguardConstraints, }, Intersection, }; @@ -159,9 +159,8 @@ pub enum ObfuscationQuery { Off, #[default] Auto, - Udp2tcp { - port: Udp2TcpObfuscationSettings, - }, + Udp2tcp(Udp2TcpObfuscationSettings), + Shadowsocks(ShadowsocksSettings), } impl From<ObfuscationSettings> for ObfuscationQuery { @@ -173,9 +172,10 @@ impl From<ObfuscationSettings> for ObfuscationQuery { match obfuscation.selected_obfuscation { SelectedObfuscation::Off => ObfuscationQuery::Off, SelectedObfuscation::Auto => ObfuscationQuery::Auto, - SelectedObfuscation::Udp2Tcp => ObfuscationQuery::Udp2tcp { - port: obfuscation.udp2tcp, - }, + SelectedObfuscation::Udp2Tcp => ObfuscationQuery::Udp2tcp(obfuscation.udp2tcp), + SelectedObfuscation::Shadowsocks => { + ObfuscationQuery::Shadowsocks(obfuscation.shadowsocks) + } } } } @@ -185,11 +185,13 @@ impl Intersection for ObfuscationQuery { match (self, other) { (ObfuscationQuery::Off, _) | (_, ObfuscationQuery::Off) => Some(ObfuscationQuery::Off), (ObfuscationQuery::Auto, other) | (other, ObfuscationQuery::Auto) => Some(other), - (ObfuscationQuery::Udp2tcp { port: a }, ObfuscationQuery::Udp2tcp { port: b }) => { - Some(ObfuscationQuery::Udp2tcp { - port: a.intersection(b)?, - }) + (ObfuscationQuery::Udp2tcp(a), ObfuscationQuery::Udp2tcp(b)) => { + Some(ObfuscationQuery::Udp2tcp(a.intersection(b)?)) } + (ObfuscationQuery::Shadowsocks(a), ObfuscationQuery::Shadowsocks(b)) => { + Some(ObfuscationQuery::Shadowsocks(a.intersection(b)?)) + } + _ => None, } } } @@ -343,7 +345,7 @@ pub mod builder { constraints::Constraint, relay_constraints::{ BridgeConstraints, LocationConstraint, RelayConstraints, SelectedObfuscation, - TransportPort, Udp2TcpObfuscationSettings, + ShadowsocksSettings, TransportPort, Udp2TcpObfuscationSettings, }, }; use talpid_types::net::TunnelType; @@ -543,8 +545,28 @@ pub mod builder { obfuscation: obfuscation.clone(), daita: self.protocol.daita, }; + self.query.wireguard_constraints.obfuscation = ObfuscationQuery::Udp2tcp(obfuscation); + RelayQueryBuilder { + query: self.query, + protocol, + } + } + + /// Enable Shadowsocks obufscation. This will in turn enable the option to configure the + /// port. + pub fn shadowsocks( + mut self, + ) -> RelayQueryBuilder<Wireguard<Multihop, ShadowsocksSettings, Daita>> { + let obfuscation = ShadowsocksSettings { + port: Constraint::Any, + }; + let protocol = Wireguard { + multihop: self.protocol.multihop, + obfuscation: obfuscation.clone(), + daita: self.protocol.daita, + }; self.query.wireguard_constraints.obfuscation = - ObfuscationQuery::Udp2tcp { port: obfuscation }; + ObfuscationQuery::Shadowsocks(obfuscation); RelayQueryBuilder { query: self.query, protocol, @@ -557,9 +579,8 @@ pub mod builder { /// protocol should use to connect to a relay. pub fn udp2tcp_port(mut self, port: u16) -> Self { self.protocol.obfuscation.port = Constraint::Only(port); - self.query.wireguard_constraints.obfuscation = ObfuscationQuery::Udp2tcp { - port: self.protocol.obfuscation.clone(), - }; + self.query.wireguard_constraints.obfuscation = + ObfuscationQuery::Udp2tcp(self.protocol.obfuscation.clone()); self } } @@ -680,7 +701,10 @@ pub mod builder { mod test { use mullvad_types::{ constraints::Constraint, - relay_constraints::{ObfuscationSettings, SelectedObfuscation, Udp2TcpObfuscationSettings}, + relay_constraints::{ + ObfuscationSettings, SelectedObfuscation, ShadowsocksSettings, + Udp2TcpObfuscationSettings, + }, }; use proptest::prelude::*; @@ -765,11 +789,14 @@ mod test { /// When obfuscation is set to automatic in [`ObfuscationSettings`], the query should not /// contain any specific obfuscation protocol settings. #[test] - fn test_auto_obfuscation_settings(port in constraint(proptest::arbitrary::any::<u16>())) { + fn test_auto_obfuscation_settings(port1 in constraint(proptest::arbitrary::any::<u16>()), port2 in constraint(proptest::arbitrary::any::<u16>())) { let query = ObfuscationQuery::from(ObfuscationSettings { selected_obfuscation: SelectedObfuscation::Auto, udp2tcp: Udp2TcpObfuscationSettings { - port, + port: port1, + }, + shadowsocks: ShadowsocksSettings { + port: port2, }, }); assert_eq!(query, ObfuscationQuery::Auto); diff --git a/mullvad-relay-selector/tests/relay_selector.rs b/mullvad-relay-selector/tests/relay_selector.rs index e2808d5ff4..9069720d2b 100644 --- a/mullvad-relay-selector/tests/relay_selector.rs +++ b/mullvad-relay-selector/tests/relay_selector.rs @@ -55,6 +55,7 @@ static RELAYS: Lazy<RelayList> = Lazy::new(|| RelayList { ) .unwrap(), daita: false, + shadowsocks_extra_addr_in: vec![], }), location: None, }, @@ -73,6 +74,7 @@ static RELAYS: Lazy<RelayList> = Lazy::new(|| RelayList { ) .unwrap(), daita: false, + shadowsocks_extra_addr_in: vec![], }), location: None, }, @@ -164,6 +166,7 @@ static RELAYS: Lazy<RelayList> = Lazy::new(|| RelayList { ipv4_gateway: "10.64.0.1".parse().unwrap(), ipv6_gateway: "fc00:bbbb:bbbb:bb01::1".parse().unwrap(), udp2tcp_ports: vec![], + shadowsocks_port_ranges: vec![(100, 200), (1000, 2000)], }, }); @@ -314,7 +317,8 @@ fn test_retry_order() { assert!(match &query.wireguard_constraints.obfuscation { ObfuscationQuery::Auto => true, ObfuscationQuery::Off => obfuscator.is_none(), - ObfuscationQuery::Udp2tcp { .. } => obfuscator.is_some(), + ObfuscationQuery::Udp2tcp(_) | ObfuscationQuery::Shadowsocks(_) => + obfuscator.is_some(), }); } GetRelay::OpenVpn { @@ -437,6 +441,7 @@ fn test_wireguard_entry() { ) .unwrap(), daita: false, + shadowsocks_extra_addr_in: vec![], }), location: None, }, @@ -455,6 +460,7 @@ fn test_wireguard_entry() { ) .unwrap(), daita: false, + shadowsocks_extra_addr_in: vec![], }), location: None, }, @@ -476,6 +482,7 @@ fn test_wireguard_entry() { ipv4_gateway: "10.64.0.1".parse().unwrap(), ipv6_gateway: "fc00:bbbb:bbbb:bb01::1".parse().unwrap(), udp2tcp_ports: vec![], + shadowsocks_port_ranges: vec![(100, 200), (1000, 2000)], }, }; @@ -778,10 +785,10 @@ fn test_selected_wireguard_endpoints_use_correct_port_ranges() { let Some(obfuscator) = obfuscator else { panic!("Relay selector should have picked an obfuscator") }; - assert!(match obfuscator.config { - ObfuscatorConfig::Udp2Tcp { endpoint } => + assert!(matches!(obfuscator.config, + ObfuscatorConfig::Udp2Tcp { endpoint } if TCP2UDP_PORTS.contains(&endpoint.port()), - }) + )) } wrong_relay => panic!( "Relay selector should have picked a Wireguard relay, instead chose {wrong_relay:?}" diff --git a/mullvad-types/src/relay_constraints.rs b/mullvad-types/src/relay_constraints.rs index 37d4708602..04e7884519 100644 --- a/mullvad-types/src/relay_constraints.rs +++ b/mullvad-types/src/relay_constraints.rs @@ -477,6 +477,7 @@ pub enum SelectedObfuscation { Off, #[cfg_attr(feature = "clap", clap(name = "udp2tcp"))] Udp2Tcp, + Shadowsocks, } impl Intersection for SelectedObfuscation { @@ -500,6 +501,7 @@ impl fmt::Display for SelectedObfuscation { SelectedObfuscation::Auto => "auto".fmt(f), SelectedObfuscation::Off => "off".fmt(f), SelectedObfuscation::Udp2Tcp => "udp2tcp".fmt(f), + SelectedObfuscation::Shadowsocks => "shadowsocks".fmt(f), } } } @@ -519,6 +521,21 @@ impl fmt::Display for Udp2TcpObfuscationSettings { } } +#[derive(Default, Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Intersection)] +#[serde(rename_all = "snake_case")] +pub struct ShadowsocksSettings { + pub port: Constraint<u16>, +} + +impl fmt::Display for ShadowsocksSettings { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.port { + Constraint::Any => write!(f, "any port"), + Constraint::Only(port) => write!(f, "port {port}"), + } + } +} + /// Contains obfuscation settings #[derive(Default, Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] @@ -526,6 +543,7 @@ impl fmt::Display for Udp2TcpObfuscationSettings { pub struct ObfuscationSettings { pub selected_obfuscation: SelectedObfuscation, pub udp2tcp: Udp2TcpObfuscationSettings, + pub shadowsocks: ShadowsocksSettings, } /// Limits the set of bridge servers to use in `mullvad-daemon`. diff --git a/mullvad-types/src/relay_list.rs b/mullvad-types/src/relay_list.rs index 6c4f9fbf6a..37d63dbf15 100644 --- a/mullvad-types/src/relay_list.rs +++ b/mullvad-types/src/relay_list.rs @@ -111,6 +111,7 @@ impl PartialEq for Relay { /// # ) /// # .unwrap(), /// # daita: false, + /// # shadowsocks_extra_addr_in: vec![], /// # }), /// # location: None, /// }; diff --git a/talpid-types/src/net/mod.rs b/talpid-types/src/net/mod.rs index 485f1d779f..3d6961f50b 100644 --- a/talpid-types/src/net/mod.rs +++ b/talpid-types/src/net/mod.rs @@ -119,6 +119,10 @@ impl TunnelParameters { address: *endpoint, protocol: TransportProtocol::Tcp, }, + ObfuscatorConfig::Shadowsocks { endpoint } => Endpoint { + address: *endpoint, + protocol: TransportProtocol::Udp, + }, } } @@ -250,12 +254,14 @@ impl fmt::Display for TunnelEndpoint { pub enum ObfuscationType { #[serde(rename = "udp2tcp")] Udp2Tcp, + Shadowsocks, } impl fmt::Display for ObfuscationType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { match self { ObfuscationType::Udp2Tcp => "Udp2Tcp".fmt(f), + ObfuscationType::Shadowsocks => "Shadowsocks".fmt(f), } } } @@ -277,6 +283,13 @@ impl From<&ObfuscatorConfig> for ObfuscationEndpoint { }, ObfuscationType::Udp2Tcp, ), + ObfuscatorConfig::Shadowsocks { endpoint } => ( + Endpoint { + address: *endpoint, + protocol: TransportProtocol::Udp, + }, + ObfuscationType::Shadowsocks, + ), }; ObfuscationEndpoint { diff --git a/talpid-types/src/net/obfuscation.rs b/talpid-types/src/net/obfuscation.rs index a39e9bf919..ddd93a4a39 100644 --- a/talpid-types/src/net/obfuscation.rs +++ b/talpid-types/src/net/obfuscation.rs @@ -4,4 +4,5 @@ use std::net::SocketAddr; #[derive(Clone, Eq, PartialEq, Deserialize, Serialize, Debug)] pub enum ObfuscatorConfig { Udp2Tcp { endpoint: SocketAddr }, + Shadowsocks { endpoint: SocketAddr }, } diff --git a/talpid-wireguard/src/lib.rs b/talpid-wireguard/src/lib.rs index e093ac2058..b9a85560ee 100644 --- a/talpid-wireguard/src/lib.rs +++ b/talpid-wireguard/src/lib.rs @@ -5,7 +5,7 @@ use self::config::Config; #[cfg(windows)] use futures::channel::mpsc; -use futures::future::{abortable, AbortHandle as FutureAbortHandle, BoxFuture, Future}; +use futures::future::{BoxFuture, Future}; #[cfg(target_os = "linux")] use once_cell::sync::Lazy; #[cfg(target_os = "android")] @@ -16,7 +16,7 @@ use std::env; use std::io; use std::{ convert::Infallible, - net::IpAddr, + net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, path::Path, pin::Pin, sync::{mpsc as sync_mpsc, Arc, Mutex}, @@ -39,7 +39,8 @@ use talpid_types::{ }; use tokio::sync::Mutex as AsyncMutex; use tunnel_obfuscation::{ - create_obfuscator, Error as ObfuscationError, Settings as ObfuscationSettings, udp2tcp, + create_obfuscator, shadowsocks, udp2tcp, Error as ObfuscationError, + Settings as ObfuscationSettings, }; /// WireGuard config data-types @@ -158,18 +159,18 @@ const PSK_EXCHANGE_TIMEOUT_MULTIPLIER: u32 = 2; /// Simple wrapper that automatically cancels the future which runs an obfuscator. struct ObfuscatorHandle { - abort_handle: FutureAbortHandle, + obfuscation_task: tokio::task::JoinHandle<()>, #[cfg(target_os = "android")] remote_socket_fd: std::os::unix::io::RawFd, } impl ObfuscatorHandle { pub fn new( - abort_handle: FutureAbortHandle, + obfuscation_task: tokio::task::JoinHandle<()>, #[cfg(target_os = "android")] remote_socket_fd: std::os::unix::io::RawFd, ) -> Self { Self { - abort_handle, + obfuscation_task, #[cfg(target_os = "android")] remote_socket_fd, } @@ -181,13 +182,13 @@ impl ObfuscatorHandle { } pub fn abort(&self) { - self.abort_handle.abort(); + self.obfuscation_task.abort(); } } impl Drop for ObfuscatorHandle { fn drop(&mut self) { - self.abort_handle.abort(); + self.obfuscation_task.abort(); } } @@ -204,48 +205,63 @@ async fn maybe_create_obfuscator( close_msg_sender: sync_mpsc::Sender<CloseMsg>, ) -> Result<Option<ObfuscatorHandle>> { if let Some(ref obfuscator_config) = config.obfuscator_config { - match obfuscator_config { + let settings = match obfuscator_config { ObfuscatorConfig::Udp2Tcp { endpoint } => { - log::trace!("Connecting to Udp2Tcp endpoint {:?}", *endpoint); - let settings = udp2tcp::Settings { + ObfuscationSettings::Udp2Tcp(udp2tcp::Settings { peer: *endpoint, #[cfg(target_os = "linux")] fwmark: config.fwmark, - }; - let obfuscator = create_obfuscator(&ObfuscationSettings::Udp2Tcp(settings)) - .await - .map_err(Error::CreateObfuscatorError)?; - let endpoint = obfuscator.endpoint(); + }) + } + ObfuscatorConfig::Shadowsocks { endpoint } => { + ObfuscationSettings::Shadowsocks(shadowsocks::Settings { + shadowsocks_endpoint: *endpoint, + // TODO: Temporary since we may different entry IPs later? + wireguard_endpoint: if config.entry_peer.endpoint.is_ipv4() { + SocketAddr::from((Ipv4Addr::LOCALHOST, 51820)) + } else { + SocketAddr::from((Ipv6Addr::LOCALHOST, 51820)) + }, + //wireguard_endpoint: config.entry_peer.endpoint, + #[cfg(target_os = "linux")] + fwmark: config.fwmark, + }) + } + }; - log::trace!("Patching first WireGuard peer to become {:?}", endpoint); - config.entry_peer.endpoint = endpoint; + log::trace!("Obfuscation settings: {settings:?}"); - #[cfg(target_os = "android")] - let remote_socket_fd = obfuscator.remote_socket_fd(); + let obfuscator = create_obfuscator(&settings) + .await + .map_err(Error::CreateObfuscatorError)?; + let endpoint = obfuscator.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))); - } - } - }); - tokio::spawn(runner); - return Ok(Some(ObfuscatorHandle::new( - abort_handle, - #[cfg(target_os = "android")] - remote_socket_fd, - ))); + log::trace!("Patching first WireGuard peer to become {endpoint}"); + config.entry_peer.endpoint = endpoint; + + #[cfg(target_os = "android")] + let remote_socket_fd = obfuscator.remote_socket_fd(); + + let obfuscation_task = tokio::spawn(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))); + } } - } + }); + return Ok(Some(ObfuscatorHandle::new( + obfuscation_task, + #[cfg(target_os = "android")] + remote_socket_fd, + ))); } Ok(None) } diff --git a/test/test-manager/src/tests/tunnel.rs b/test/test-manager/src/tests/tunnel.rs index 540f02802e..c671f28966 100644 --- a/test/test-manager/src/tests/tunnel.rs +++ b/test/test-manager/src/tests/tunnel.rs @@ -151,6 +151,7 @@ pub async fn test_udp2tcp_tunnel( udp2tcp: Udp2TcpObfuscationSettings { port: Constraint::Any, }, + ..Default::default() }) .await .expect("failed to enable udp2tcp"); @@ -578,6 +579,7 @@ pub async fn test_quantum_resistant_multihop_udp2tcp_tunnel( udp2tcp: Udp2TcpObfuscationSettings { port: Constraint::Any, }, + ..Default::default() }) .await .expect("Failed to enable obfuscation"); |
