diff options
| author | Markus Pettersson <markus.pettersson@mullvad.net> | 2025-07-17 18:24:56 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2025-07-23 09:41:54 +0200 |
| commit | 38b8388e06b0fd60c3e4e6aa351eb541329dd3dd (patch) | |
| tree | 0082708fb62cadfe9f1fd405c6ec7ac9ec75556d | |
| parent | bb4b111acc09a92fdf12e0fc6d504180f91421c8 (diff) | |
| download | mullvadvpn-38b8388e06b0fd60c3e4e6aa351eb541329dd3dd.tar.xz mullvadvpn-38b8388e06b0fd60c3e4e6aa351eb541329dd3dd.zip | |
Refactor Relay protobuf type
Remove the dependency on google/protobuf/any.proto.
12 files changed, 302 insertions, 393 deletions
diff --git a/desktop/packages/mullvad-vpn/src/main/grpc-type-convertions.ts b/desktop/packages/mullvad-vpn/src/main/grpc-type-convertions.ts index 1a49dae202..4b79b0b1d9 100644 --- a/desktop/packages/mullvad-vpn/src/main/grpc-type-convertions.ts +++ b/desktop/packages/mullvad-vpn/src/main/grpc-type-convertions.ts @@ -25,7 +25,6 @@ import { ErrorStateCause, ErrorStateDetails, FeatureIndicator, - FeaturesType, FirewallPolicyError, FirewallPolicyErrorType, IAppVersionInfo, @@ -53,7 +52,7 @@ import { ObfuscationType, Ownership, ProxyType, - RelayEndpointType, + Quic, RelayLocation, RelayLocationGeographical, RelayProtocol, @@ -119,43 +118,35 @@ function convertFromRelayListCity(city: grpcTypes.RelayListCity): IRelayListCity function convertFromRelayListRelay(relay: grpcTypes.Relay): IRelayListHostname { const relayObject = relay.toObject(); - let daita = false; - if (relayObject.endpointType === grpcTypes.Relay.RelayType.WIREGUARD) { - const endpointDataU8 = relay.getEndpointData()?.getValue_asU8(); - if (endpointDataU8) { - daita = grpcTypes.WireguardRelayEndpointData.deserializeBinary(endpointDataU8).getDaita(); - } - } + // The relay type is determined by the variant of the extra endpoint data + const wireguard = relayObject.endpointData?.wireguard; + const openvpn = relayObject.endpointData?.openvpn; + const bridge = relayObject.endpointData?.bridge; + + const endpointType = wireguard + ? 'wireguard' + : openvpn + ? 'openvpn' + : bridge + ? 'bridge' + : /*This case should never happen ..*/ 'bridge'; + + const daita = wireguard ? wireguard.daita : false; + const quic = wireguard?.quic ? quicFromRelayType(wireguard.quic) : undefined; return { ...relayObject, - endpointType: convertFromRelayType(relayObject.endpointType), - features: relayObject.features ? featuresFromRelayType(relayObject.features) : undefined, + endpointType, daita, + quic, }; } -function convertFromRelayType(relayType: grpcTypes.Relay.RelayType): RelayEndpointType { - const protocolMap: Record<grpcTypes.Relay.RelayType, RelayEndpointType> = { - [grpcTypes.Relay.RelayType.OPENVPN]: 'openvpn', - [grpcTypes.Relay.RelayType.BRIDGE]: 'bridge', - [grpcTypes.Relay.RelayType.WIREGUARD]: 'wireguard', - }; - return protocolMap[relayType]; -} - -function featuresFromRelayType(features: grpcTypes.Relay.Features.AsObject): FeaturesType { - const daita = features.daita; - const quic = features.quic - ? { - domain: features.quic.domain, - token: features.quic.token, - addr_in: features.quic.addrInList, - } - : undefined; +function quicFromRelayType(quic: grpcTypes.Relay.RelayData.Wireguard.Quic.AsObject): Quic { return { - daita, - quic, + domain: quic.domain, + token: quic.token, + addrIn: quic.addrInList, }; } diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/filter-locations.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/filter-locations.ts index fcfd3a38da..29ce00f1ff 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/filter-locations.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/filter-locations.ts @@ -38,7 +38,7 @@ export function filterLocationsByQuic( tunnelProtocol: TunnelProtocol, ): IRelayLocationCountryRedux[] { const quicFilterActive = quic && tunnelProtocol !== 'openvpn'; - const quickOnRelay = (relay: IRelayLocationRelayRedux) => relay.features?.quic !== undefined; + const quickOnRelay = (relay: IRelayLocationRelayRedux) => relay.quic !== undefined; return quicFilterActive ? filterLocationsImpl(locations, quickOnRelay) : locations; } diff --git a/desktop/packages/mullvad-vpn/src/renderer/redux/settings/reducers.ts b/desktop/packages/mullvad-vpn/src/renderer/redux/settings/reducers.ts index 9fc8a6d30e..9ac8bb3a61 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/redux/settings/reducers.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/redux/settings/reducers.ts @@ -7,7 +7,6 @@ import { BridgeType, CustomLists, CustomProxy, - FeaturesType, IDaitaSettings, IDnsOptions, IpVersion, @@ -16,6 +15,7 @@ import { ObfuscationSettings, ObfuscationType, Ownership, + Quic, RelayEndpointType, RelayLocation, RelayOverride, @@ -78,7 +78,7 @@ export interface IRelayLocationRelayRedux { weight: number; endpointType: RelayEndpointType; daita: boolean; - features?: FeaturesType; + quic?: Quic; } export interface IRelayLocationCityRedux { diff --git a/desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts b/desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts index f12c3123f8..49164a3770 100644 --- a/desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts +++ b/desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts @@ -396,15 +396,10 @@ export interface IRelayListHostname { weight: number; owned: boolean; endpointType: RelayEndpointType; - daita: boolean; // TODO: Deprecate in favor of Features 👇 - features?: FeaturesType; -} - -// The absence of a value signals that the relay does not have it enabled. -export type FeaturesType = { daita: boolean; + // The absence of this value signals that the relay does not deploy QUIC. quic?: Quic; -}; +} export type Quic = { domain: string; diff --git a/mullvad-api/src/relay_list.rs b/mullvad-api/src/relay_list.rs index abca6dd2b7..4a2d68a071 100644 --- a/mullvad-api/src/relay_list.rs +++ b/mullvad-api/src/relay_list.rs @@ -7,7 +7,7 @@ use mullvad_types::{location, relay_list}; use talpid_types::net::wireguard; use std::{ - collections::BTreeMap, + collections::{BTreeMap, HashSet}, future::Future, net::{IpAddr, Ipv4Addr, Ipv6Addr}, ops::RangeInclusive, @@ -167,7 +167,6 @@ fn into_mullvad_relay( relay: Relay, location: location::Location, endpoint_data: relay_list::RelayEndpointData, - features: relay_list::Features, ) -> relay_list::Relay { relay_list::Relay { hostname: relay.hostname, @@ -182,7 +181,6 @@ fn into_mullvad_relay( weight: relay.weight, endpoint_data, location, - features, } } @@ -247,21 +245,11 @@ struct Relay { impl Relay { fn into_openvpn_mullvad_relay(self, location: location::Location) -> relay_list::Relay { - into_mullvad_relay( - self, - location, - relay_list::RelayEndpointData::Openvpn, - relay_list::Features::empty(), - ) + into_mullvad_relay(self, location, relay_list::RelayEndpointData::Openvpn) } fn into_bridge_mullvad_relay(self, location: location::Location) -> relay_list::Relay { - into_mullvad_relay( - self, - location, - relay_list::RelayEndpointData::Bridge, - relay_list::Features::empty(), - ) + into_mullvad_relay(self, location, relay_list::RelayEndpointData::Bridge) } fn convert_to_lowercase(&mut self) { @@ -353,25 +341,61 @@ struct WireGuardRelay { #[serde(default)] shadowsocks_extra_addr_in: Vec<IpAddr>, #[serde(default)] - features: relay_list::Features, + features: Features, } impl WireGuardRelay { fn into_mullvad_relay(self, location: location::Location) -> relay_list::Relay { - // Sanity check that new 'features' key is in sync with the old Relay keys. - if self.features.daita() { + // Sanity check that new 'features' key is in sync with the old, superceded keys. + // TODO: Remove `self.daita` (and this check 👇) when `features` key has been completely + // rolled out to production. + if self.features.daita.is_some() { debug_assert!(self.daita) } - into_mullvad_relay( - self.relay, - location, + + let relay = self.relay; + let endpoint_data = relay_list::RelayEndpointData::Wireguard(relay_list::WireguardRelayEndpointData { public_key: self.public_key, daita: self.daita, - shadowsocks_extra_addr_in: self.shadowsocks_extra_addr_in, - }), - self.features, - ) + shadowsocks_extra_addr_in: HashSet::from_iter(self.shadowsocks_extra_addr_in), + quic: self.features.quic.map(relay_list::Quic::from), + }); + + into_mullvad_relay(relay, location, endpoint_data) + } +} + +/// Extra features enabled on some (Wireguard) relay, such as obfuscation daemons or Daita. +#[derive(Debug, Default, Clone, serde::Deserialize)] +struct Features { + daita: Option<Daita>, + quic: Option<Quic>, +} + +/// DAITA doesn't have any configuration options (exposed by the API). +/// +/// Note, an empty struct is not the same as an empty tuple struct according to serde_json! +#[derive(Debug, Clone, serde::Deserialize)] +struct Daita {} + +/// Parameters for setting up a QUIC obfuscator (connecting to a masque-proxy running on a relay). +#[derive(Debug, Clone, serde::Deserialize)] +struct Quic { + /// In-addresses for the QUIC obfuscator. + /// + /// There may be 0, 1 or 2 in IPs, depending on how many masque-proxy daemons running on the + /// relay. Hopefully the API will tell use the correct amount🤞. + addr_in: Vec<IpAddr>, + /// Authorization token + token: String, + /// Hostname where masque proxy is hosted + domain: String, +} + +impl From<Quic> for relay_list::Quic { + fn from(value: Quic) -> Self { + Self::new(value.addr_in, value.token, value.domain) } } diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index fc453afe9b..4d405bcde2 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -6,7 +6,6 @@ import "google/protobuf/empty.proto"; import "google/protobuf/timestamp.proto"; import "google/protobuf/wrappers.proto"; import "google/protobuf/duration.proto"; -import "google/protobuf/any.proto"; service ManagementService { // Control and get tunnel state @@ -717,20 +716,27 @@ message RelayListCity { } message Relay { - enum RelayType { - OPENVPN = 0; - BRIDGE = 1; - WIREGUARD = 2; - } - message Features { - message Quic { - string domain = 1; - string token = 2; - repeated string addr_in = 3; + message RelayData { + message OpenVPN {} + message Bridge {} + message Wireguard { + message Quic { + string domain = 1; + string token = 2; + repeated string addr_in = 3; + } + + bytes public_key = 1; + bool daita = 2; + Quic quic = 3; + repeated string shadowsocks_extra_addr_in = 4; } - bool daita = 1; - Quic quic = 2; + oneof data { + Wireguard wireguard = 1; + OpenVPN openvpn = 2; + Bridge bridge = 3; + } } string hostname = 1; @@ -741,17 +747,8 @@ message Relay { bool owned = 6; string provider = 7; fixed64 weight = 8; - RelayType endpoint_type = 9; - // XXX: wtf, why is this untyped - google.protobuf.Any endpoint_data = 10; - Location location = 11; - Features features = 12; -} - -message WireguardRelayEndpointData { - bytes public_key = 1; - bool daita = 2; - repeated string shadowsocks_extra_addr_in = 3; + RelayData endpoint_data = 9; + Location location = 10; } message Location { diff --git a/mullvad-management-interface/src/types/conversions/mod.rs b/mullvad-management-interface/src/types/conversions/mod.rs index 0654cbb641..3c610b8d1b 100644 --- a/mullvad-management-interface/src/types/conversions/mod.rs +++ b/mullvad-management-interface/src/types/conversions/mod.rs @@ -60,22 +60,3 @@ impl From<FromProtobufTypeError> for crate::Status { } } } - -/// Converts any message to `google.protobuf.Any`. -fn to_proto_any<T: prost::Message>(type_name: &str, message: T) -> prost_types::Any { - prost_types::Any { - type_url: format!("type.googleapis.com/{type_name}"), - value: message.encode_to_vec(), - } -} - -/// Tries to convert a message from `google.protobuf.Any` to `T`. -fn try_from_proto_any<T: prost::Message + Default>( - type_name: &str, - any_value: prost_types::Any, -) -> Option<T> { - if any_value.type_url != format!("type.googleapis.com/{type_name}") { - return None; - } - T::decode(any_value.value.as_slice()).ok() -} diff --git a/mullvad-management-interface/src/types/conversions/relay_list.rs b/mullvad-management-interface/src/types/conversions/relay_list.rs index 780900413c..08ec379ba9 100644 --- a/mullvad-management-interface/src/types/conversions/relay_list.rs +++ b/mullvad-management-interface/src/types/conversions/relay_list.rs @@ -1,16 +1,11 @@ use std::{ - net::{Ipv4Addr, Ipv6Addr}, + collections::HashSet, + net::{IpAddr, Ipv4Addr, Ipv6Addr}, ops::RangeInclusive, str::FromStr, }; -use mullvad_types::relay_list::Features; - -use crate::types::{ - FromProtobufTypeError, - conversions::{bytes_to_pubkey, to_proto_any, try_from_proto_any}, - proto, -}; +use crate::types::{FromProtobufTypeError, conversions::bytes_to_pubkey, proto}; use super::net::try_transport_protocol_from_i32; @@ -127,26 +122,29 @@ impl From<mullvad_types::relay_list::Relay> for proto::Relay { owned: relay.owned, provider: relay.provider, weight: relay.weight, - endpoint_type: match &relay.endpoint_data { - MullvadEndpointData::Openvpn => proto::relay::RelayType::Openvpn as i32, - MullvadEndpointData::Bridge => proto::relay::RelayType::Bridge as i32, - MullvadEndpointData::Wireguard(_) => proto::relay::RelayType::Wireguard as i32, - }, - endpoint_data: match relay.endpoint_data { - MullvadEndpointData::Wireguard(data) => Some(to_proto_any( - "mullvad_daemon.management_interface/WireguardRelayEndpointData", - proto::WireguardRelayEndpointData { - public_key: data.public_key.as_bytes().to_vec(), - // TODO: Deprecate in favor of new `features` key - daita: data.daita, - shadowsocks_extra_addr_in: data - .shadowsocks_extra_addr_in - .iter() + endpoint_data: { + use proto::relay::RelayData; + use proto::relay::relay_data::{Bridge, Data, OpenVpn, Wireguard, wireguard}; + let data = match relay.endpoint_data { + MullvadEndpointData::Wireguard(data) => { + let shadowsocks_extra_addr_in = data + .shadowsocks_extra_in_addrs() .map(|addr| addr.to_string()) - .collect(), - }, - )), - _ => None, + .collect(); + let public_key = data.public_key.as_bytes().to_vec(); + let daita = data.daita; + let quic = data.quic.map(wireguard::Quic::from); + Data::Wireguard(Wireguard { + public_key, + daita, + shadowsocks_extra_addr_in, + quic, + }) + } + MullvadEndpointData::Bridge => Data::Bridge(Bridge {}), + MullvadEndpointData::Openvpn => Data::Openvpn(OpenVpn {}), + }; + Some(RelayData { data: Some(data) }) }, location: Some(proto::Location { country: relay.location.country, @@ -156,53 +154,15 @@ impl From<mullvad_types::relay_list::Relay> for proto::Relay { latitude: relay.location.latitude, longitude: relay.location.longitude, }), - features: Some(proto::relay::Features::from(relay.features)), } } } -impl From<mullvad_types::relay_list::Features> for proto::relay::Features { - fn from(features: mullvad_types::relay_list::Features) -> Self { - Self { - daita: features.daita(), - quic: features - .quic() - .cloned() - .map(proto::relay::features::Quic::from), - } - } -} - -impl TryFrom<proto::relay::Features> for mullvad_types::relay_list::Features { - type Error = FromProtobufTypeError; - - fn try_from(value: proto::relay::Features) -> Result<Self, Self::Error> { - let features = Features::empty(); - let features = if value.daita { - features.configure_daita() - } else { - features - }; - let features = { - let quic = value - .quic - .map(mullvad_types::relay_list::Quic::try_from) - .transpose()?; - if let Some(options) = quic { - features.configure_quic(options) - } else { - features - } - }; - Ok(features) - } -} - -impl From<mullvad_types::relay_list::Quic> for proto::relay::features::Quic { - fn from(value: mullvad_types::relay_list::Quic) -> Self { - let domain = value.hostname().to_owned(); - let token = value.auth_token().to_owned(); - let addr_in = value.in_addr().iter().map(|ip| ip.to_string()).collect(); +impl From<mullvad_types::relay_list::Quic> for proto::relay::relay_data::wireguard::Quic { + fn from(quic: mullvad_types::relay_list::Quic) -> Self { + let domain = quic.hostname().to_owned(); + let token = quic.auth_token().to_owned(); + let addr_in = quic.in_addr().map(|ip| ip.to_string()).collect(); Self { domain, token, @@ -211,21 +171,21 @@ impl From<mullvad_types::relay_list::Quic> for proto::relay::features::Quic { } } -impl TryFrom<proto::relay::features::Quic> for mullvad_types::relay_list::Quic { +impl TryFrom<proto::relay::relay_data::wireguard::Quic> for mullvad_types::relay_list::Quic { type Error = FromProtobufTypeError; - fn try_from(value: proto::relay::features::Quic) -> Result<Self, Self::Error> { + fn try_from(value: proto::relay::relay_data::wireguard::Quic) -> Result<Self, Self::Error> { let domain = value.domain; let token = value.token; + fn parse_addr(addr: String) -> Result<IpAddr, FromProtobufTypeError> { + addr.parse() + .map_err(|_err| FromProtobufTypeError::InvalidArgument("Invalid IP address")) + } let addr_in = value .addr_in - .iter() - .map(|addr| { - addr.parse().map_err(|_err| { - FromProtobufTypeError::InvalidArgument("Invalid IP address: {addr}") - }) - }) - .collect::<Result<_, FromProtobufTypeError>>()?; + .into_iter() + .map(parse_addr) + .collect::<Result<Vec<IpAddr>, FromProtobufTypeError>>()?; Ok(Self::new(addr_in, token, domain)) } } @@ -309,44 +269,43 @@ impl TryFrom<proto::Relay> for mullvad_types::relay_list::Relay { relay_list::{Relay as MullvadRelay, RelayEndpointData as MullvadEndpointData}, }; - let endpoint_data = match relay.endpoint_type { - i if i == proto::relay::RelayType::Openvpn as i32 => MullvadEndpointData::Openvpn, - i if i == proto::relay::RelayType::Bridge as i32 => MullvadEndpointData::Bridge, - i if i == proto::relay::RelayType::Wireguard as i32 => { - let data = relay - .endpoint_data - .ok_or(FromProtobufTypeError::InvalidArgument( - "missing endpoint wg data", - ))?; - let data: proto::WireguardRelayEndpointData = try_from_proto_any( - "mullvad_daemon.management_interface/WireguardRelayEndpointData", - data, - ) + let endpoint_data = { + let data = relay + .endpoint_data + .and_then(|endpoint| endpoint.data) .ok_or(FromProtobufTypeError::InvalidArgument( - "invalid endpoint wg data", - ))?; - MullvadEndpointData::Wireguard( - mullvad_types::relay_list::WireguardRelayEndpointData { - public_key: bytes_to_pubkey(&data.public_key)?, - daita: data.daita, - shadowsocks_extra_addr_in: data - .shadowsocks_extra_addr_in - .iter() - .map(|addr| { - addr.parse().map_err(|_err| { - FromProtobufTypeError::InvalidArgument( - "invalid relay IPv6 address", - ) - }) - }) - .collect::<Result<_, FromProtobufTypeError>>()?, - }, - ) - } - _ => { - return Err(FromProtobufTypeError::InvalidArgument( "invalid relay endpoint type", - )); + ))?; + match data { + proto::relay::relay_data::Data::Openvpn(_openvpn) => MullvadEndpointData::Openvpn, + proto::relay::relay_data::Data::Bridge(_bridge) => MullvadEndpointData::Bridge, + proto::relay::relay_data::Data::Wireguard(wireguard) => { + fn parse_addr(addr: &str) -> Result<IpAddr, FromProtobufTypeError> { + addr.parse().map_err(|_err| { + FromProtobufTypeError::InvalidArgument("Invalid IP address") + }) + } + + let public_key = bytes_to_pubkey(&wireguard.public_key)?; + let daita = wireguard.daita; + let quic = wireguard + .quic + .map(mullvad_types::relay_list::Quic::try_from) + .transpose()?; + let shadowsocks_extra_addr_in = wireguard + .shadowsocks_extra_addr_in + .iter() + .map(String::as_ref) + .map(parse_addr) + .collect::<Result<HashSet<IpAddr>, FromProtobufTypeError>>()?; + let data = mullvad_types::relay_list::WireguardRelayEndpointData { + public_key, + daita, + quic, + shadowsocks_extra_addr_in, + }; + MullvadEndpointData::Wireguard(data) + } } }; @@ -359,12 +318,6 @@ impl TryFrom<proto::Relay> for mullvad_types::relay_list::Relay { }) .transpose()?; - let features = relay - .features - .map(mullvad_types::relay_list::Features::try_from) - .transpose()? - .unwrap_or_default(); - let relay = MullvadRelay { hostname: relay.hostname, ipv4_addr_in: relay.ipv4_addr_in.parse().map_err(|_err| { @@ -391,7 +344,6 @@ impl TryFrom<proto::Relay> for mullvad_types::relay_list::Relay { }) .ok_or("missing relay location") .map_err(FromProtobufTypeError::InvalidArgument)?, - features, }; Ok(relay) diff --git a/mullvad-relay-selector/src/relay_selector/helpers.rs b/mullvad-relay-selector/src/relay_selector/helpers.rs index 7135d41fd0..1a5a11e69c 100644 --- a/mullvad-relay-selector/src/relay_selector/helpers.rs +++ b/mullvad-relay-selector/src/relay_selector/helpers.rs @@ -124,7 +124,7 @@ pub fn get_shadowsocks_obfuscator( let port = settings.port; let extra_addrs = match &relay.endpoint_data { mullvad_types::relay_list::RelayEndpointData::Wireguard(wg) => { - &wg.shadowsocks_extra_addr_in + wg.shadowsocks_extra_in_addrs() } _ => panic!("expected wireguard relay"), }; @@ -132,7 +132,7 @@ pub fn get_shadowsocks_obfuscator( let endpoint = get_shadowsocks_obfuscator_inner( endpoint.peer.endpoint.ip(), non_extra_port_ranges, - extra_addrs, + extra_addrs.copied(), port, )?; @@ -143,7 +143,7 @@ pub fn get_shadowsocks_obfuscator( } pub fn get_quic_obfuscator(relay: Relay, ip_version: IpVersion) -> Option<SelectedObfuscator> { - let quic = relay.features.quic()?; + let quic = relay.wireguard()?.quic()?; let config = { let hostname = quic.hostname().to_string(); let endpoint = match ip_version { @@ -168,14 +168,13 @@ pub fn get_quic_obfuscator(relay: Relay, ip_version: IpVersion) -> Option<Select fn get_shadowsocks_obfuscator_inner<R: RangeBounds<u16> + Iterator<Item = u16> + Clone>( wg_in_addr: IpAddr, wg_in_addr_port_ranges: &[R], - extra_in_addrs: &[IpAddr], + extra_in_addrs: impl IntoIterator<Item = IpAddr>, desired_port: Constraint<u16>, ) -> Result<SocketAddr, Error> { // Filter out addresses for the wrong address family let extra_in_addrs: Vec<_> = extra_in_addrs - .iter() + .into_iter() .filter(|addr| addr.is_ipv4() == wg_in_addr.is_ipv4()) - .copied() .collect(); let in_ip = extra_in_addrs @@ -244,7 +243,7 @@ mod tests { SHADOWSOCKS_EXTRA_PORT_RANGES, get_shadowsocks_obfuscator_inner, port_if_in_range, }; use mullvad_types::constraints::Constraint; - use std::{net::IpAddr, ops::RangeInclusive}; + use std::{iter, net::IpAddr, ops::RangeInclusive}; /// Test whether select ports are available when relay has no extra IPs #[test] @@ -255,7 +254,7 @@ mod tests { let wg_in_ip: IpAddr = "1.2.3.4".parse().unwrap(); let selected_addr = - get_shadowsocks_obfuscator_inner(wg_in_ip, PORT_RANGES, &[], Constraint::Any) + get_shadowsocks_obfuscator_inner(wg_in_ip, PORT_RANGES, iter::empty(), Constraint::Any) .expect("should find valid port without constraint"); assert_eq!(selected_addr.ip(), wg_in_ip); @@ -267,7 +266,7 @@ mod tests { let selected_addr = get_shadowsocks_obfuscator_inner( wg_in_ip, PORT_RANGES, - &[], + iter::empty(), Constraint::Only(WITHIN_RANGE_PORT), ) .expect("should find within-range port"); @@ -281,7 +280,7 @@ mod tests { let selected_addr = get_shadowsocks_obfuscator_inner( wg_in_ip, PORT_RANGES, - &[], + iter::empty(), Constraint::Only(OUT_OF_RANGE_PORT), ); assert!( @@ -297,13 +296,13 @@ mod tests { const OUT_OF_RANGE_PORT: u16 = 1; let wg_in_ip: IpAddr = "1.2.3.4".parse().unwrap(); - let extra_in_addrs: &[IpAddr] = - &["1.3.3.7".parse().unwrap(), "192.0.2.123".parse().unwrap()]; + let extra_in_addrs: Vec<IpAddr> = + vec!["1.3.3.7".parse().unwrap(), "192.0.2.123".parse().unwrap()]; let selected_addr = get_shadowsocks_obfuscator_inner( wg_in_ip, PORT_RANGES, - extra_in_addrs, + extra_in_addrs.clone(), Constraint::Any, ) .expect("should find valid port without constraint"); @@ -317,7 +316,7 @@ mod tests { let selected_addr = get_shadowsocks_obfuscator_inner( wg_in_ip, PORT_RANGES, - extra_in_addrs, + extra_in_addrs.clone(), Constraint::Only(OUT_OF_RANGE_PORT), ) .expect("expected selected address to be returned"); @@ -340,12 +339,12 @@ mod tests { const OUT_OF_RANGE_PORT: u16 = 1; let wg_in_ip: IpAddr = "1.2.3.4".parse().unwrap(); - let extra_in_addrs: &[IpAddr] = &["::2".parse().unwrap()]; + let extra_in_addrs: Vec<IpAddr> = vec!["::2".parse().unwrap()]; let selected_addr = get_shadowsocks_obfuscator_inner( wg_in_ip, PORT_RANGES, - extra_in_addrs, + extra_in_addrs.clone(), Constraint::Any, ) .expect("should find valid port without constraint"); @@ -359,7 +358,7 @@ mod tests { let selected_addr = get_shadowsocks_obfuscator_inner( wg_in_ip, PORT_RANGES, - extra_in_addrs, + extra_in_addrs.clone(), Constraint::Only(OUT_OF_RANGE_PORT), ); assert!( diff --git a/mullvad-relay-selector/src/relay_selector/matcher.rs b/mullvad-relay-selector/src/relay_selector/matcher.rs index 8133677b87..52f0cc600a 100644 --- a/mullvad-relay-selector/src/relay_selector/matcher.rs +++ b/mullvad-relay-selector/src/relay_selector/matcher.rs @@ -145,7 +145,7 @@ fn filter_on_obfuscation( ) } // QUIC is only enabled on some relays - ObfuscationQuery::Quic => relay.features.quic().is_some(), + ObfuscationQuery::Quic => relay.wireguard().is_some_and(|wg| wg.quic().is_some()), // Other relays are compatible with this query ObfuscationQuery::Off | ObfuscationQuery::Auto | ObfuscationQuery::Udp2tcp(_) => true, } diff --git a/mullvad-relay-selector/tests/relay_selector.rs b/mullvad-relay-selector/tests/relay_selector.rs index 532e78a076..ad91c192d5 100644 --- a/mullvad-relay-selector/tests/relay_selector.rs +++ b/mullvad-relay-selector/tests/relay_selector.rs @@ -27,9 +27,9 @@ use mullvad_types::{ RelayConstraints, RelayOverride, RelaySettings, TransportPort, }, relay_list::{ - BridgeEndpointData, Features, OpenVpnEndpoint, OpenVpnEndpointData, Quic, Relay, - RelayEndpointData, RelayList, RelayListCity, RelayListCountry, ShadowsocksEndpointData, - WireguardEndpointData, WireguardRelayEndpointData, + BridgeEndpointData, OpenVpnEndpoint, OpenVpnEndpointData, Quic, Relay, RelayEndpointData, + RelayList, RelayListCity, RelayListCountry, ShadowsocksEndpointData, WireguardEndpointData, + WireguardRelayEndpointData, }, }; @@ -42,6 +42,10 @@ static DUMMY_LOCATION: LazyLock<Location> = LazyLock::new(|| Location { longitude: 11.97, }); +static WIREGUARD_PUBKEY: LazyLock<PublicKey> = LazyLock::new(|| { + PublicKey::from_base64("BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=").unwrap() +}); + static RELAYS: LazyLock<RelayList> = LazyLock::new(|| RelayList { etag: None, countries: vec![RelayListCountry { @@ -64,25 +68,19 @@ static RELAYS: LazyLock<RelayList> = LazyLock::new(|| RelayList { owned: true, provider: "provider0".to_string(), weight: 1, - endpoint_data: RelayEndpointData::Wireguard(WireguardRelayEndpointData { - public_key: PublicKey::from_base64( - "BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=", - ) - .unwrap(), - daita: true, - shadowsocks_extra_addr_in: vec![], - }), + endpoint_data: RelayEndpointData::Wireguard( + WireguardRelayEndpointData::new(WIREGUARD_PUBKEY.clone()) + .set_daita(true) + .set_quic(Quic::new( + vec![ + "185.213.154.68".parse().unwrap(), + "2a03:1b20:5:f011::a09f".parse().unwrap(), + ], + "Bearer test".to_owned(), + "se9-wireguard.blockerad.eu".to_owned(), + )), + ), location: DUMMY_LOCATION.clone(), - features: Features::default() - .configure_daita() - .configure_quic(Quic::new( - vec![ - "185.213.154.68".parse().unwrap(), - "2a03:1b20:5:f011::a09f".parse().unwrap(), - ], - "Bearer test".to_owned(), - "se9-wireguard.blockerad.eu".to_owned(), - )), }, Relay { hostname: "se10-wireguard".to_string(), @@ -95,16 +93,11 @@ static RELAYS: LazyLock<RelayList> = LazyLock::new(|| RelayList { owned: false, provider: "provider1".to_string(), weight: 1, - endpoint_data: RelayEndpointData::Wireguard(WireguardRelayEndpointData { - public_key: PublicKey::from_base64( - "BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=", - ) - .unwrap(), - daita: false, - shadowsocks_extra_addr_in: vec![], - }), + endpoint_data: RelayEndpointData::Wireguard(WireguardRelayEndpointData::new( + PublicKey::from_base64("BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=") + .unwrap(), + )), location: DUMMY_LOCATION.clone(), - features: Features::default(), }, Relay { hostname: "se11-wireguard".to_string(), @@ -117,16 +110,14 @@ static RELAYS: LazyLock<RelayList> = LazyLock::new(|| RelayList { owned: false, provider: "provider2".to_string(), weight: 1, - endpoint_data: RelayEndpointData::Wireguard(WireguardRelayEndpointData { - public_key: PublicKey::from_base64( - "BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=", + endpoint_data: RelayEndpointData::Wireguard( + WireguardRelayEndpointData::new( + PublicKey::from_base64("BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=") + .unwrap(), ) - .unwrap(), - daita: true, - shadowsocks_extra_addr_in: vec![], - }), + .set_daita(true), + ), location: DUMMY_LOCATION.clone(), - features: Features::default().configure_daita(), }, Relay { hostname: "se-got-001".to_string(), @@ -141,7 +132,6 @@ static RELAYS: LazyLock<RelayList> = LazyLock::new(|| RelayList { weight: 1, endpoint_data: RelayEndpointData::Openvpn, location: DUMMY_LOCATION.clone(), - features: Features::default(), }, Relay { hostname: "se-got-002".to_string(), @@ -156,7 +146,6 @@ static RELAYS: LazyLock<RelayList> = LazyLock::new(|| RelayList { weight: 1, endpoint_data: RelayEndpointData::Openvpn, location: DUMMY_LOCATION.clone(), - features: Features::default(), }, Relay { hostname: "se-got-br-001".to_string(), @@ -171,7 +160,6 @@ static RELAYS: LazyLock<RelayList> = LazyLock::new(|| RelayList { weight: 1, endpoint_data: RelayEndpointData::Bridge, location: DUMMY_LOCATION.clone(), - features: Features::default(), }, SHADOWSOCKS_RELAY.clone(), ], @@ -250,13 +238,13 @@ static SHADOWSOCKS_RELAY: LazyLock<Relay> = LazyLock::new(|| Relay { owned: true, provider: "provider0".to_string(), weight: 1, - endpoint_data: RelayEndpointData::Wireguard(WireguardRelayEndpointData { - public_key: PublicKey::from_base64("eaNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=").unwrap(), - daita: false, - shadowsocks_extra_addr_in: SHADOWSOCKS_RELAY_EXTRA_ADDRS.to_vec(), - }), + endpoint_data: RelayEndpointData::Wireguard( + WireguardRelayEndpointData::new( + PublicKey::from_base64("eaNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=").unwrap(), + ) + .add_shadowsocks_extra_in_addrs(SHADOWSOCKS_RELAY_EXTRA_ADDRS.iter().copied()), + ), location: DUMMY_LOCATION.clone(), - features: Features::default(), }); const SHADOWSOCKS_RELAY_IPV4: Ipv4Addr = Ipv4Addr::new(123, 123, 123, 1); const SHADOWSOCKS_RELAY_IPV6: Ipv6Addr = Ipv6Addr::new(0x123, 0, 0, 0, 0, 0, 0, 2); @@ -586,16 +574,10 @@ fn test_wireguard_entry() { owned: true, provider: "provider0".to_string(), weight: 1, - endpoint_data: RelayEndpointData::Wireguard(WireguardRelayEndpointData { - public_key: PublicKey::from_base64( - "BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=", - ) - .unwrap(), - daita: false, - shadowsocks_extra_addr_in: vec![], - }), + endpoint_data: RelayEndpointData::Wireguard( + WireguardRelayEndpointData::new(WIREGUARD_PUBKEY.clone()), + ), location: DUMMY_LOCATION.clone(), - features: Features::default(), }, Relay { hostname: "se10-wireguard".to_string(), @@ -608,16 +590,10 @@ fn test_wireguard_entry() { owned: false, provider: "provider1".to_string(), weight: 1, - endpoint_data: RelayEndpointData::Wireguard(WireguardRelayEndpointData { - public_key: PublicKey::from_base64( - "BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=", - ) - .unwrap(), - daita: false, - shadowsocks_extra_addr_in: vec![], - }), + endpoint_data: RelayEndpointData::Wireguard( + WireguardRelayEndpointData::new(WIREGUARD_PUBKEY.clone()), + ), location: DUMMY_LOCATION.clone(), - features: Features::default(), }, ], }], @@ -1280,16 +1256,10 @@ fn test_include_in_country() { owned: true, provider: "31173".to_string(), weight: 1, - endpoint_data: RelayEndpointData::Wireguard(WireguardRelayEndpointData { - public_key: PublicKey::from_base64( - "BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=", - ) - .unwrap(), - shadowsocks_extra_addr_in: vec![], - daita: false, - }), + endpoint_data: RelayEndpointData::Wireguard( + WireguardRelayEndpointData::new(WIREGUARD_PUBKEY.clone()), + ), location: DUMMY_LOCATION.clone(), - features: Features::default(), }, Relay { hostname: "se10-wireguard".to_string(), @@ -1302,16 +1272,10 @@ fn test_include_in_country() { owned: false, provider: "31173".to_string(), weight: 1, - endpoint_data: RelayEndpointData::Wireguard(WireguardRelayEndpointData { - public_key: PublicKey::from_base64( - "BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=", - ) - .unwrap(), - shadowsocks_extra_addr_in: vec![], - daita: false, - }), + endpoint_data: RelayEndpointData::Wireguard( + WireguardRelayEndpointData::new(WIREGUARD_PUBKEY.clone()), + ), location: DUMMY_LOCATION.clone(), - features: Features::default(), }, ], }], diff --git a/mullvad-types/src/relay_list.rs b/mullvad-types/src/relay_list.rs index aceff060b0..c6f1693c14 100644 --- a/mullvad-types/src/relay_list.rs +++ b/mullvad-types/src/relay_list.rs @@ -1,6 +1,7 @@ use crate::location::{CityCode, CountryCode, Location}; use serde::{Deserialize, Serialize}; use std::{ + collections::HashSet, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, ops::RangeInclusive, }; @@ -88,69 +89,28 @@ pub struct Relay { pub weight: u64, pub endpoint_data: RelayEndpointData, pub location: Location, - #[serde(default)] - pub features: Features, -} - -/// Extra features enabled on some (Wireguard) relay, such as obfuscation daemons or Daita. -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Features { - daita: Option<Daita>, - quic: Option<Quic>, } -impl Features { - /// Equivalent to a relay without any additional features. - pub fn empty() -> Features { - Features { - daita: None, - quic: None, +impl Relay { + /// If self is a Wireguard relay, we sometimes want to peek on its extra data. + pub fn wireguard(&self) -> Option<&WireguardRelayEndpointData> { + match &self.endpoint_data { + RelayEndpointData::Wireguard(wireguard_relay_endpoint_data) => { + Some(wireguard_relay_endpoint_data) + } + RelayEndpointData::Openvpn | RelayEndpointData::Bridge => None, } } - - /// Whether Daita is enabled - pub fn daita(&self) -> bool { - self.daita.is_some() - } - - /// Whether Quic is enabled and its config - pub fn quic(&self) -> Option<&Quic> { - self.quic.as_ref() - } - - /// Enable Daita for this relay - pub fn configure_daita(self) -> Self { - let daita = Some(Daita {}); - Self { daita, ..self } - } - - /// Configure QUIC for this relay - pub fn configure_quic(self, options: Quic) -> Self { - let quic = Some(options); - Self { quic, ..self } - } -} - -impl Default for Features { - fn default() -> Self { - Features::empty() - } } -/// DAITA doesn't have any configuration options (exposed by the API). -/// -/// Note, an empty struct is not the same as an empty tuple struct according to serde_json! -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Daita {} - /// Parameters for setting up a QUIC obfuscator (connecting to a masque-proxy running on a relay). -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] pub struct Quic { /// In-addresses for the QUIC obfuscator. /// /// There may be 0, 1 or 2 in IPs, depending on how many masque-proxy daemons running on the /// relay. Hopefully the API will tell use the correct amount🤞. - addr_in: Vec<IpAddr>, + addr_in: HashSet<IpAddr>, /// Authorization token token: String, /// Hostname where masque proxy is hosted @@ -158,7 +118,8 @@ pub struct Quic { } impl Quic { - pub fn new(addr_in: Vec<IpAddr>, token: String, domain: String) -> Self { + pub fn new(addr_in: impl IntoIterator<Item = IpAddr>, token: String, domain: String) -> Self { + let addr_in = HashSet::from_iter(addr_in); Self { addr_in, token, @@ -202,8 +163,8 @@ impl Quic { &self.token } - pub fn in_addr(&self) -> &[IpAddr] { - &self.addr_in + pub fn in_addr(&self) -> impl Iterator<Item = IpAddr> { + self.addr_in.iter().copied() } } @@ -234,7 +195,7 @@ impl PartialEq for Relay { /// # Example /// /// ```rust - /// # use mullvad_types::{relay_list::{Relay, Features}, relay_list::{RelayEndpointData, WireguardRelayEndpointData}}; + /// # use mullvad_types::{relay_list::Relay, relay_list::{RelayEndpointData, WireguardRelayEndpointData}}; /// # use talpid_types::net::wireguard::PublicKey; /// /// let relay = Relay { @@ -254,7 +215,8 @@ impl PartialEq for Relay { /// # ) /// # .unwrap(), /// # daita: false, - /// # shadowsocks_extra_addr_in: vec![], + /// # shadowsocks_extra_addr_in: Default::default(), + /// # quic: None, /// # }), /// # location: mullvad_types::location::Location { /// # country: "Sweden".to_string(), @@ -264,7 +226,6 @@ impl PartialEq for Relay { /// # latitude: 57.71, /// # longitude: 11.97, /// # }, - /// # features: Features::default(), /// }; /// /// let mut different_relay = relay.clone(); @@ -342,17 +303,62 @@ impl Default for WireguardEndpointData { } /// Contains data about specific WireGuard endpoints, i.e. their public keys. -#[derive(Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Debug)] +#[derive(Clone, Eq, PartialEq, Deserialize, Serialize, Debug)] pub struct WireguardRelayEndpointData { /// Public key used by the relay peer pub public_key: wireguard::PublicKey, - /// Whether the server supports DAITA - /// FIXME: This has been superceded by [Features] + [Daita]. + /// Whether the relay supports DAITA #[serde(default)] pub daita: bool, + /// Parameters for connecting to the masque-proxy running on the relay. + #[serde(default)] + pub quic: Option<Quic>, /// Optional IP addresses used by Shadowsocks #[serde(default)] - pub shadowsocks_extra_addr_in: Vec<IpAddr>, + pub shadowsocks_extra_addr_in: HashSet<IpAddr>, +} + +impl WireguardRelayEndpointData { + pub fn new(public_key: wireguard::PublicKey) -> Self { + Self { + public_key, + daita: Default::default(), + quic: Default::default(), + shadowsocks_extra_addr_in: Default::default(), + } + } + + pub fn set_daita(self, enabled: bool) -> Self { + Self { + daita: enabled, + ..self + } + } + + pub fn set_quic(self, quic: Quic) -> Self { + Self { + quic: Some(quic), + ..self + } + } + + /// Add `in_addrs` to the existing shadowsocks extra in addressess. + pub fn add_shadowsocks_extra_in_addrs(self, in_addrs: impl Iterator<Item = IpAddr>) -> Self { + let in_addrs = self.shadowsocks_extra_in_addrs().copied().chain(in_addrs); + Self { + shadowsocks_extra_addr_in: HashSet::from_iter(in_addrs), + ..self + } + } + + pub fn shadowsocks_extra_in_addrs(&self) -> impl Iterator<Item = &IpAddr> { + self.shadowsocks_extra_addr_in.iter() + } + + // Is this really needed if `self.quic` is pub? + pub fn quic(&self) -> Option<&Quic> { + self.quic.as_ref() + } } #[derive(Debug, Default, Clone, Deserialize, Serialize)] |
