summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2024-05-30 17:20:46 +0200
committerDavid Lönnhager <david.l@mullvad.net>2024-08-16 09:13:30 +0200
commit0945cdb9ac304ff4c46144e290198cdae810e17d (patch)
tree983464d7ff281a8af9d9cf519b40cd6db40e0d39
parent39c0a267382e51831652ff0966df8fb180b989d8 (diff)
downloadmullvadvpn-0945cdb9ac304ff4c46144e290198cdae810e17d.tar.xz
mullvadvpn-0945cdb9ac304ff4c46144e290198cdae810e17d.zip
Add Shadowsocks obfuscation support to mullvad-daemon
-rw-r--r--android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt10
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationType.kt3
-rw-r--r--gui/src/main/daemon-rpc.ts14
-rw-r--r--mullvad-management-interface/proto/management_interface.proto13
-rw-r--r--mullvad-management-interface/src/types/conversions/net.rs35
-rw-r--r--mullvad-management-interface/src/types/conversions/relay_constraints.rs40
-rw-r--r--mullvad-relay-selector/src/relay_selector/detailer.rs55
-rw-r--r--mullvad-relay-selector/src/relay_selector/helpers.rs80
-rw-r--r--mullvad-relay-selector/src/relay_selector/mod.rs27
-rw-r--r--mullvad-relay-selector/src/relay_selector/query.rs67
-rw-r--r--mullvad-relay-selector/tests/relay_selector.rs15
-rw-r--r--mullvad-types/src/relay_constraints.rs18
-rw-r--r--mullvad-types/src/relay_list.rs1
-rw-r--r--talpid-types/src/net/mod.rs13
-rw-r--r--talpid-types/src/net/obfuscation.rs1
-rw-r--r--talpid-wireguard/src/lib.rs100
-rw-r--r--test/test-manager/src/tests/tunnel.rs2
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");