diff options
| author | David Lönnhager <david.l@mullvad.net> | 2022-07-07 14:40:05 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2022-07-07 14:40:05 +0200 |
| commit | ac15ef4e14d97b3e9f8ec1ac237d544d4264f550 (patch) | |
| tree | b94d2664d37b4bbfe45ea244d7dd5ad2965fa7c2 | |
| parent | 887349f12a71a2c6a3bce49ba7d86df353be71a6 (diff) | |
| parent | 7652f299b34003721923f53f47868b14eca3ae53 (diff) | |
| download | mullvadvpn-ac15ef4e14d97b3e9f8ec1ac237d544d4264f550.tar.xz mullvadvpn-ac15ef4e14d97b3e9f8ec1ac237d544d4264f550.zip | |
Merge branch 'relays-validate-ports'
19 files changed, 590 insertions, 822 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/Relay.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/Relay.kt index 23f9d87f77..dbb74b129a 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/Relay.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/Relay.kt @@ -7,8 +7,8 @@ import kotlinx.parcelize.Parcelize data class Relay( val hostname: String, val active: Boolean, - val tunnels: RelayTunnels + val endpointData: RelayEndpointData ) : Parcelable { - val hasWireguardTunnels - get() = !tunnels.wireguard.isEmpty() + val isWireguardRelay + get() = endpointData is RelayEndpointData.Wireguard } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayEndpointData.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayEndpointData.kt new file mode 100644 index 0000000000..ecc2d4d002 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayEndpointData.kt @@ -0,0 +1,17 @@ +package net.mullvad.mullvadvpn.model + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +sealed class RelayEndpointData : Parcelable { + @Parcelize + object Openvpn : RelayEndpointData() + + @Parcelize + object Bridge : RelayEndpointData() + + @Parcelize + data class Wireguard( + val wireguardRelayEndpointData: WireguardRelayEndpointData + ) : RelayEndpointData() +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/WireguardEndpointData.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/WireguardEndpointData.kt deleted file mode 100644 index aee9b56082..0000000000 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/WireguardEndpointData.kt +++ /dev/null @@ -1,8 +0,0 @@ -package net.mullvad.mullvadvpn.model - -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - -@Suppress("PARCELABLE_PRIMARY_CONSTRUCTOR_IS_EMPTY") -@Parcelize -class WireguardEndpointData() : Parcelable diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayTunnels.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/WireguardRelayEndpointData.kt index 8856f6b4bd..b3ef17f98a 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayTunnels.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/WireguardRelayEndpointData.kt @@ -4,4 +4,4 @@ import android.os.Parcelable import kotlinx.parcelize.Parcelize @Parcelize -data class RelayTunnels(val wireguard: ArrayList<WireguardEndpointData>) : Parcelable +object WireguardRelayEndpointData : Parcelable diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayList.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayList.kt index aed15f9508..915e6ca181 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayList.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayList.kt @@ -16,7 +16,7 @@ class RelayList { val relays = mutableListOf<Relay>() val relayCity = RelayCity(relayCountry, city.name, city.code, false, relays) - val validCityRelays = city.relays.filter { relay -> relay.hasWireguardTunnels } + val validCityRelays = city.relays.filter { relay -> relay.isWireguardRelay } for (relay in validCityRelays) { relays.add(Relay(relayCity, relay.hostname, relay.active)) diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts index e704979a40..a868208ac2 100644 --- a/gui/src/main/daemon-rpc.ts +++ b/gui/src/main/daemon-rpc.ts @@ -29,24 +29,22 @@ import { ILocation, IObfuscationEndpoint, IOpenVpnConstraints, - IOpenVpnTunnelData, IProxyEndpoint, IRelayList, IRelayListCity, IRelayListCountry, IRelayListHostname, ISettings, - IShadowsocksEndpointData, ITunnelOptions, ITunnelStateRelayInfo, IWireguardConstraints, - IWireguardTunnelData, LoggedInDeviceState, LoggedOutDeviceState, ObfuscationType, Ownership, ProxySettings, ProxyType, + RelayEndpointType, RelayLocation, RelayProtocol, RelaySettings, @@ -732,34 +730,17 @@ function convertFromRelayListCity(city: grpcTypes.RelayListCity.AsObject): IRela function convertFromRelayListRelay(relay: grpcTypes.Relay.AsObject): IRelayListHostname { return { ...relay, - tunnels: relay.tunnels && { - ...relay.tunnels, - openvpn: relay.tunnels.openvpnList.map(convertFromOpenvpnList), - wireguard: relay.tunnels.wireguardList.map(convertFromWireguardList), - }, - bridges: relay.bridges && { - shadowsocks: relay.bridges.shadowsocksList.map(convertFromShadowsocksList), - }, + endpointType: convertFromRelayType(relay.endpointType), }; } -function convertFromOpenvpnList( - openvpn: grpcTypes.OpenVpnEndpointData.AsObject, -): IOpenVpnTunnelData { - return { - ...openvpn, - protocol: convertFromTransportProtocol(openvpn.protocol), - }; -} - -function convertFromWireguardList( - wireguard: grpcTypes.WireguardEndpointData.AsObject, -): IWireguardTunnelData { - return { - ...wireguard, - portRanges: wireguard.portRangesList, - publicKey: convertFromWireguardKey(wireguard.publicKey), +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 convertFromWireguardKey(publicKey: Uint8Array | string): string { @@ -769,15 +750,6 @@ function convertFromWireguardKey(publicKey: Uint8Array | string): string { return Buffer.from(publicKey).toString('base64'); } -function convertFromShadowsocksList( - shadowsocks: grpcTypes.ShadowsocksEndpointData.AsObject, -): IShadowsocksEndpointData { - return { - ...shadowsocks, - protocol: convertFromTransportProtocol(shadowsocks.protocol), - }; -} - function convertFromTransportProtocol(protocol: grpcTypes.TransportProtocol): RelayProtocol { const protocolMap: Record<grpcTypes.TransportProtocol, RelayProtocol> = { [grpcTypes.TransportProtocol.TCP]: 'tcp', diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts index 83b76292c4..b8a8a9ca28 100644 --- a/gui/src/main/index.ts +++ b/gui/src/main/index.ts @@ -1026,23 +1026,19 @@ class ApplicationMain { .map((city) => ({ ...city, relays: city.relays.filter((relay) => { - if (relay.tunnels) { + if (relay.endpointType != 'bridge') { switch (tunnelProtocol) { case 'openvpn': - return relay.tunnels.openvpn.length > 0; + return relay.endpointType == 'openvpn'; case 'wireguard': - return relay.tunnels.wireguard.length > 0; + return relay.endpointType == 'wireguard'; case 'any': { const useMultihop = 'normal' in relaySettings && relaySettings.normal.wireguardConstraints.useMultihop; - if (useMultihop) { - return relay.tunnels.wireguard.length > 0; - } else { - return relay.tunnels.openvpn.length > 0 || relay.tunnels.wireguard.length > 0; - } + return !useMultihop || relay.endpointType == 'wireguard'; } default: return false; @@ -1072,9 +1068,7 @@ class ApplicationMain { cities: country.cities .map((city) => ({ ...city, - relays: city.relays.filter( - (relay) => relay.bridges && relay.bridges.shadowsocks.length > 0, - ), + relays: city.relays.filter((relay) => relay.endpointType == 'bridge'), })) .filter((city) => city.relays.length > 0), })) diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts index 8bee8003b0..d0507cab98 100644 --- a/gui/src/shared/daemon-rpc-types.ts +++ b/gui/src/shared/daemon-rpc-types.ts @@ -250,41 +250,10 @@ export interface IRelayListHostname { active: boolean; weight: number; owned: boolean; - tunnels?: IRelayTunnels; - bridges?: IRelayBridges; + endpointType: RelayEndpointType; } -export interface IRelayTunnels { - openvpn: IOpenVpnTunnelData[]; - wireguard: IWireguardTunnelData[]; -} - -export interface IRelayBridges { - shadowsocks: IShadowsocksEndpointData[]; -} - -export interface IOpenVpnTunnelData { - port: number; - protocol: RelayProtocol; -} - -export interface IWireguardTunnelData { - portRanges: Array<IPortRange>; - // Public key of the tunnel. - publicKey: string; -} - -export interface IPortRange { - first: number; - last: number; -} - -export interface IShadowsocksEndpointData { - port: number; - cipher: string; - password: string; - protocol: RelayProtocol; -} +export type RelayEndpointType = 'wireguard' | 'openvpn' | 'bridge'; export interface ITunnelOptions { openvpn: { diff --git a/mullvad-api/src/relay_list.rs b/mullvad-api/src/relay_list.rs index a2d699e248..951493db02 100644 --- a/mullvad-api/src/relay_list.rs +++ b/mullvad-api/src/relay_list.rs @@ -106,10 +106,6 @@ impl ServerRelayList { } } - Self::add_openvpn_relays(&mut countries, openvpn); - Self::add_wireguard_relays(&mut countries, wireguard); - Self::add_bridge_relays(&mut countries, bridge); - relay_list::RelayList { etag: etag.map(|mut tag| { if tag.starts_with('"') { @@ -117,160 +113,15 @@ impl ServerRelayList { } tag }), + openvpn: openvpn.extract_relays(&mut countries), + wireguard: wireguard.extract_relays(&mut countries), + bridge: bridge.extract_relays(&mut countries), countries: countries .into_iter() .map(|(_key, country)| country) .collect(), } } - - fn add_openvpn_relays( - countries: &mut BTreeMap<String, relay_list::RelayListCountry>, - openvpn: OpenVpn, - ) { - let openvpn_endpoint_data = openvpn.ports; - for mut openvpn_relay in openvpn.relays.into_iter() { - openvpn_relay.convert_to_lowercase(); - if let Some((country_code, city_code)) = split_location_code(&openvpn_relay.location) { - if let Some(country) = countries.get_mut(country_code) { - if let Some(city) = country - .cities - .iter_mut() - .find(|city| city.code == city_code) - { - let location = location::Location { - country: country.name.clone(), - country_code: country.code.clone(), - city: city.name.clone(), - city_code: city.code.clone(), - latitude: city.latitude, - longitude: city.longitude, - }; - match city - .relays - .iter_mut() - .find(|r| r.hostname == openvpn_relay.hostname) - { - Some(relay) => relay.tunnels.openvpn = openvpn_endpoint_data.clone(), - None => { - let mut relay = relay(openvpn_relay, location); - relay.tunnels.openvpn = openvpn_endpoint_data.clone(); - city.relays.push(relay); - } - }; - } - }; - } - } - } - - fn add_wireguard_relays( - countries: &mut BTreeMap<String, relay_list::RelayListCountry>, - wireguard: Wireguard, - ) { - let Wireguard { - port_ranges, - ipv4_gateway, - ipv6_gateway, - relays, - } = wireguard; - - let wireguard_endpoint_data = - |public_key: wireguard::PublicKey| relay_list::WireguardEndpointData { - port_ranges: port_ranges.clone(), - ipv4_gateway, - ipv6_gateway, - public_key, - }; - - for mut wireguard_relay in relays { - wireguard_relay.relay.convert_to_lowercase(); - if let Some((country_code, city_code)) = - split_location_code(&wireguard_relay.relay.location) - { - if let Some(country) = countries.get_mut(country_code) { - if let Some(city) = country - .cities - .iter_mut() - .find(|city| city.code == city_code) - { - let location = location::Location { - country: country.name.clone(), - country_code: country.code.clone(), - city: city.name.clone(), - city_code: city.code.clone(), - latitude: city.latitude, - longitude: city.longitude, - }; - match city - .relays - .iter_mut() - .find(|r| r.hostname == wireguard_relay.relay.hostname) - { - Some(relay) => relay - .tunnels - .wireguard - .push(wireguard_endpoint_data(wireguard_relay.public_key)), - None => { - let mut relay = relay(wireguard_relay.relay, location); - relay.ipv6_addr_in = Some(wireguard_relay.ipv6_addr_in); - relay.tunnels.wireguard = - vec![wireguard_endpoint_data(wireguard_relay.public_key)]; - city.relays.push(relay); - } - }; - } - }; - } - } - } - - fn add_bridge_relays( - countries: &mut BTreeMap<String, relay_list::RelayListCountry>, - bridges: Bridges, - ) { - let Bridges { - relays, - shadowsocks, - } = bridges; - - for mut bridge_relay in relays { - bridge_relay.convert_to_lowercase(); - if let Some((country_code, city_code)) = split_location_code(&bridge_relay.location) { - if let Some(country) = countries.get_mut(country_code) { - if let Some(city) = country - .cities - .iter_mut() - .find(|city| city.code == city_code) - { - let location = location::Location { - country: country.name.clone(), - country_code: country.code.clone(), - city: city.name.clone(), - city_code: city.code.clone(), - latitude: city.latitude, - longitude: city.longitude, - }; - - match city - .relays - .iter_mut() - .find(|r| r.hostname == bridge_relay.hostname) - { - Some(relay) => { - relay.bridges.shadowsocks = shadowsocks.clone(); - } - None => { - let mut relay = relay(bridge_relay, location); - relay.bridges.shadowsocks = shadowsocks.clone(); - city.relays.push(relay); - } - }; - } - }; - } - } - } } /// Splits a location code into a country code and a city code. The input is expected to be in a @@ -301,19 +152,21 @@ fn location_to_city(location: &Location, code: String) -> relay_list::RelayListC } } -fn relay(relay: Relay, location: location::Location) -> relay_list::Relay { +fn into_mullvad_relay( + relay: Relay, + location: location::Location, + endpoint_data: relay_list::RelayEndpointData, +) -> relay_list::Relay { relay_list::Relay { hostname: relay.hostname, ipv4_addr_in: relay.ipv4_addr_in, - ipv6_addr_in: None, + ipv6_addr_in: relay.ipv6_addr_in, include_in_country: relay.include_in_country, active: relay.active, owned: relay.owned, provider: relay.provider, weight: relay.weight, - tunnels: Default::default(), - bridges: Default::default(), - obfuscators: Default::default(), + endpoint_data, location: Some(location), } } @@ -328,10 +181,44 @@ struct Location { #[derive(Debug, serde::Deserialize)] struct OpenVpn { - ports: Vec<relay_list::OpenVpnEndpointData>, + #[serde(flatten)] + ports: relay_list::OpenVpnEndpointData, relays: Vec<Relay>, } +impl OpenVpn { + /// Consumes `self` and appends all its relays to `countries`. + fn extract_relays( + self, + countries: &mut BTreeMap<String, relay_list::RelayListCountry>, + ) -> relay_list::OpenVpnEndpointData { + for mut openvpn_relay in self.relays.into_iter() { + openvpn_relay.convert_to_lowercase(); + if let Some((country_code, city_code)) = split_location_code(&openvpn_relay.location) { + if let Some(country) = countries.get_mut(country_code) { + if let Some(city) = country + .cities + .iter_mut() + .find(|city| city.code == city_code) + { + let location = location::Location { + country: country.name.clone(), + country_code: country.code.clone(), + city: city.name.clone(), + city_code: city.code.clone(), + latitude: city.latitude, + longitude: city.longitude, + }; + let relay = openvpn_relay.into_openvpn_mullvad_relay(location); + city.relays.push(relay); + } + }; + } + } + self.ports + } +} + #[derive(Debug, serde::Deserialize)] struct Relay { hostname: String, @@ -340,11 +227,20 @@ struct Relay { location: String, provider: String, ipv4_addr_in: Ipv4Addr, + ipv6_addr_in: Option<Ipv6Addr>, weight: u64, include_in_country: bool, } impl Relay { + fn into_openvpn_mullvad_relay(self, location: location::Location) -> relay_list::Relay { + 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) + } + fn convert_to_lowercase(&mut self) { self.hostname = self.hostname.to_lowercase(); self.location = self.location.to_lowercase(); @@ -359,16 +255,115 @@ struct Wireguard { relays: Vec<WireGuardRelay>, } +impl From<&Wireguard> for relay_list::WireguardEndpointData { + fn from(wg: &Wireguard) -> Self { + Self { + port_ranges: wg.port_ranges.clone(), + ipv4_gateway: wg.ipv4_gateway, + ipv6_gateway: wg.ipv6_gateway, + udp2tcp_ports: vec![], + } + } +} + +impl Wireguard { + /// Consumes `self` and appends all its relays to `countries`. + fn extract_relays( + self, + countries: &mut BTreeMap<String, relay_list::RelayListCountry>, + ) -> relay_list::WireguardEndpointData { + let endpoint_data = relay_list::WireguardEndpointData::from(&self); + let relays = self.relays; + + for mut wireguard_relay in relays { + wireguard_relay.relay.convert_to_lowercase(); + if let Some((country_code, city_code)) = + split_location_code(&wireguard_relay.relay.location) + { + if let Some(country) = countries.get_mut(country_code) { + if let Some(city) = country + .cities + .iter_mut() + .find(|city| city.code == city_code) + { + let location = location::Location { + country: country.name.clone(), + country_code: country.code.clone(), + city: city.name.clone(), + city_code: city.code.clone(), + latitude: city.latitude, + longitude: city.longitude, + }; + + let relay = wireguard_relay.into_mullvad_relay(location); + city.relays.push(relay); + } + }; + } + } + + endpoint_data + } +} + #[derive(Debug, serde::Deserialize)] struct WireGuardRelay { #[serde(flatten)] relay: Relay, - ipv6_addr_in: Ipv6Addr, public_key: wireguard::PublicKey, } +impl WireGuardRelay { + fn into_mullvad_relay(self, location: location::Location) -> relay_list::Relay { + into_mullvad_relay( + self.relay, + location, + relay_list::RelayEndpointData::Wireguard(relay_list::WireguardRelayEndpointData { + public_key: self.public_key, + }), + ) + } +} + #[derive(Debug, serde::Deserialize)] struct Bridges { shadowsocks: Vec<relay_list::ShadowsocksEndpointData>, relays: Vec<Relay>, } + +impl Bridges { + /// Consumes `self` and appends all its relays to `countries`. + fn extract_relays( + self, + countries: &mut BTreeMap<String, relay_list::RelayListCountry>, + ) -> relay_list::BridgeEndpointData { + for mut bridge_relay in self.relays { + bridge_relay.convert_to_lowercase(); + if let Some((country_code, city_code)) = split_location_code(&bridge_relay.location) { + if let Some(country) = countries.get_mut(country_code) { + if let Some(city) = country + .cities + .iter_mut() + .find(|city| city.code == city_code) + { + let location = location::Location { + country: country.name.clone(), + country_code: country.code.clone(), + city: city.name.clone(), + city_code: city.code.clone(), + latitude: city.latitude, + longitude: city.longitude, + }; + + let relay = bridge_relay.into_bridge_mullvad_relay(location); + city.relays.push(relay); + } + }; + } + } + + relay_list::BridgeEndpointData { + shadowsocks: self.shadowsocks, + } + } +} diff --git a/mullvad-cli/src/cmds/bridge.rs b/mullvad-cli/src/cmds/bridge.rs index b253ae63a9..27532f1416 100644 --- a/mullvad-cli/src/cmds/bridge.rs +++ b/mullvad-cli/src/cmds/bridge.rs @@ -439,8 +439,7 @@ impl Bridge { .filter_map(|mut city| { city.relays.retain(|relay| { relay.active - && relay.bridges.is_some() - && !relay.bridges.as_ref().unwrap().shadowsocks.is_empty() + && relay.endpoint_type == (types::relay::RelayType::Bridge as i32) }); if !city.relays.is_empty() { Some(city) diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs index 46d4567724..8e68adda6c 100644 --- a/mullvad-cli/src/cmds/relay.rs +++ b/mullvad-cli/src/cmds/relay.rs @@ -682,13 +682,9 @@ impl Relay { city.name, city.code, city.latitude, city.longitude ); for relay in &city.relays { - let tunnels = relay.tunnels.as_ref().unwrap(); - let supports_openvpn = !tunnels.openvpn.is_empty(); - let supports_wireguard = !tunnels.wireguard.is_empty(); - let support_msg = match (supports_openvpn, supports_wireguard) { - (true, true) => "OpenVPN and WireGuard", - (true, false) => "OpenVPN", - (false, true) => "WireGuard", + let support_msg = match relay.endpoint_type { + i if i == i32::from(types::relay::RelayType::Openvpn) => "OpenVPN", + i if i == i32::from(types::relay::RelayType::Wireguard) => "WireGuard", _ => unreachable!("Bug in relay filtering earlier on"), }; let ownership = if relay.owned { @@ -737,9 +733,7 @@ impl Relay { .filter_map(|mut city| { city.relays.retain(|relay| { relay.active - && relay.tunnels.is_some() - && !(relay.tunnels.as_ref().unwrap().openvpn.is_empty() - && relay.tunnels.as_ref().unwrap().wireguard.is_empty()) + && relay.endpoint_type != (types::relay::RelayType::Bridge as i32) }); if !city.relays.is_empty() { Some(city) diff --git a/mullvad-jni/src/classes.rs b/mullvad-jni/src/classes.rs index 026d7462c5..9a124f2a12 100644 --- a/mullvad-jni/src/classes.rs +++ b/mullvad-jni/src/classes.rs @@ -28,6 +28,9 @@ pub const CLASSES: &[&str] = &[ "net/mullvad/mullvadvpn/model/PublicKey", "net/mullvad/mullvadvpn/model/Relay", "net/mullvad/mullvadvpn/model/RelayConstraints", + "net/mullvad/mullvadvpn/model/RelayEndpointData$Bridge", + "net/mullvad/mullvadvpn/model/RelayEndpointData$Openvpn", + "net/mullvad/mullvadvpn/model/RelayEndpointData$Wireguard", "net/mullvad/mullvadvpn/model/RelayList", "net/mullvad/mullvadvpn/model/RelayListCity", "net/mullvad/mullvadvpn/model/RelayListCountry", @@ -36,7 +39,6 @@ pub const CLASSES: &[&str] = &[ "net/mullvad/mullvadvpn/model/RelaySettingsUpdate$CustomTunnelEndpoint", "net/mullvad/mullvadvpn/model/RelaySettingsUpdate$Normal", "net/mullvad/mullvadvpn/model/RelayConstraintsUpdate", - "net/mullvad/mullvadvpn/model/RelayTunnels", "net/mullvad/mullvadvpn/model/Settings", "net/mullvad/mullvadvpn/model/TunnelState$Error", "net/mullvad/mullvadvpn/model/TunnelState$Connected", @@ -46,7 +48,7 @@ pub const CLASSES: &[&str] = &[ "net/mullvad/mullvadvpn/model/VoucherSubmission", "net/mullvad/mullvadvpn/model/VoucherSubmissionResult", "net/mullvad/mullvadvpn/model/LoginResult", - "net/mullvad/mullvadvpn/model/WireguardEndpointData", + "net/mullvad/mullvadvpn/model/WireguardRelayEndpointData", "net/mullvad/mullvadvpn/service/MullvadDaemon", "net/mullvad/mullvadvpn/service/MullvadVpnService", "net/mullvad/talpid/net/Endpoint", diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 2f39f496d6..4c6ee5ab71 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -6,6 +6,7 @@ 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 @@ -509,6 +510,12 @@ message RelayListCity { } message Relay { + enum RelayType { + OPENVPN = 0; + BRIDGE = 1; + WIREGUARD = 2; + } + string hostname = 1; string ipv4_addr_in = 2; string ipv6_addr_in = 3; @@ -517,11 +524,15 @@ message Relay { bool owned = 6; string provider = 7; fixed64 weight = 8; - RelayTunnels tunnels = 9; - RelayBridges bridges = 10; + RelayType endpoint_type = 9; + google.protobuf.Any endpoint_data = 10; Location location = 11; } +message WireguardRelayEndpointData { + bytes public_key = 1; +} + message Location { string country = 1; string country_code = 2; @@ -531,15 +542,6 @@ message Location { double longitude = 6; } -message RelayTunnels { - repeated OpenVpnEndpointData openvpn = 1; - repeated WireguardEndpointData wireguard = 2; -} - -message RelayBridges { - repeated ShadowsocksEndpointData shadowsocks = 1; -} - enum TransportProtocol { UDP = 0; TCP = 1; diff --git a/mullvad-management-interface/src/types.rs b/mullvad-management-interface/src/types.rs index cc2086d66e..d3404e2298 100644 --- a/mullvad-management-interface/src/types.rs +++ b/mullvad-management-interface/src/types.rs @@ -718,6 +718,8 @@ impl From<mullvad_types::relay_list::RelayListCountry> for RelayListCountry { impl From<mullvad_types::relay_list::Relay> for Relay { fn from(relay: mullvad_types::relay_list::Relay) -> Self { + use mullvad_types::relay_list::RelayEndpointData as MullvadEndpointData; + Self { hostname: relay.hostname, ipv4_addr_in: relay.ipv4_addr_in.to_string(), @@ -730,51 +732,20 @@ impl From<mullvad_types::relay_list::Relay> for Relay { owned: relay.owned, provider: relay.provider, weight: relay.weight, - tunnels: Some(RelayTunnels { - openvpn: relay - .tunnels - .openvpn - .iter() - .map(|endpoint| OpenVpnEndpointData { - port: u32::from(endpoint.port), - protocol: i32::from(TransportProtocol::from(endpoint.protocol)), - }) - .collect(), - wireguard: relay - .tunnels - .wireguard - .iter() - .map(|endpoint| { - let port_ranges = endpoint - .port_ranges - .iter() - .map(|range| PortRange { - first: u32::from(range.0), - last: u32::from(range.1), - }) - .collect(); - WireguardEndpointData { - port_ranges, - ipv4_gateway: endpoint.ipv4_gateway.to_string(), - ipv6_gateway: endpoint.ipv6_gateway.to_string(), - public_key: endpoint.public_key.as_bytes().to_vec(), - } - }) - .collect(), - }), - bridges: Some(RelayBridges { - shadowsocks: relay - .bridges - .shadowsocks - .into_iter() - .map(|endpoint| ShadowsocksEndpointData { - port: u32::from(endpoint.port), - cipher: endpoint.cipher, - password: endpoint.password, - protocol: i32::from(TransportProtocol::from(endpoint.protocol)), - }) - .collect(), - }), + endpoint_type: match &relay.endpoint_data { + MullvadEndpointData::Openvpn => relay::RelayType::Openvpn as i32, + MullvadEndpointData::Bridge => relay::RelayType::Bridge as i32, + MullvadEndpointData::Wireguard(_) => relay::RelayType::Wireguard as i32, + }, + endpoint_data: match relay.endpoint_data { + MullvadEndpointData::Wireguard(data) => Some(to_proto_any( + "mullvad_daemon.management_interface/WireguardRelayEndpointData", + WireguardRelayEndpointData { + public_key: data.public_key.as_bytes().to_vec(), + }, + )), + _ => None, + }, location: relay.location.map(|location| Location { country: location.country, country_code: location.country_code, @@ -787,6 +758,76 @@ impl From<mullvad_types::relay_list::Relay> for Relay { } } +impl TryFrom<Relay> for mullvad_types::relay_list::Relay { + type Error = FromProtobufTypeError; + + fn try_from(relay: Relay) -> Result<Self, Self::Error> { + use mullvad_types::{ + location::Location as MullvadLocation, + relay_list::{Relay as MullvadRelay, RelayEndpointData as MullvadEndpointData}, + }; + + let endpoint_data = match relay.endpoint_type { + i if i == relay::RelayType::Openvpn as i32 => MullvadEndpointData::Openvpn, + i if i == relay::RelayType::Bridge as i32 => MullvadEndpointData::Bridge, + i if i == relay::RelayType::Wireguard as i32 => { + let data = relay + .endpoint_data + .ok_or(FromProtobufTypeError::InvalidArgument( + "missing endpoint wg data", + ))?; + let data: WireguardRelayEndpointData = try_from_proto_any( + "mullvad_daemon.management_interface/WireguardRelayEndpointData", + data, + ) + .ok_or(FromProtobufTypeError::InvalidArgument( + "invalid endpoint wg data", + ))?; + MullvadEndpointData::Wireguard( + mullvad_types::relay_list::WireguardRelayEndpointData { + public_key: bytes_to_pubkey(&data.public_key)?, + }, + ) + } + _ => { + return Err(FromProtobufTypeError::InvalidArgument( + "invalid relay endpoint type", + )) + } + }; + + let ipv6_addr_in = if relay.ipv6_addr_in.is_empty() { + None + } else { + Some(relay.ipv4_addr_in.parse().map_err(|_err| { + FromProtobufTypeError::InvalidArgument("invalid relay IPv6 address") + })?) + }; + + Ok(MullvadRelay { + hostname: relay.hostname, + ipv4_addr_in: relay.ipv4_addr_in.parse().map_err(|_err| { + FromProtobufTypeError::InvalidArgument("invalid relay IPv4 address") + })?, + ipv6_addr_in, + include_in_country: relay.include_in_country, + active: relay.active, + owned: relay.owned, + provider: relay.provider, + weight: relay.weight, + endpoint_data, + location: relay.location.map(|location| MullvadLocation { + country: location.country, + country_code: location.country_code, + city: location.city, + city_code: location.city_code, + latitude: location.latitude, + longitude: location.longitude, + }), + }) + } +} + impl From<TransportProtocol> for talpid_types::net::TransportProtocol { fn from(protocol: TransportProtocol) -> Self { match protocol { @@ -1591,3 +1632,22 @@ 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-relay-selector/src/lib.rs b/mullvad-relay-selector/src/lib.rs index e7b85fbc4c..01d4f845aa 100644 --- a/mullvad-relay-selector/src/lib.rs +++ b/mullvad-relay-selector/src/lib.rs @@ -3,7 +3,6 @@ use chrono::{DateTime, Local}; use ipnetwork::IpNetwork; -use matcher::AnyTunnelMatcher; use mullvad_types::{ endpoint::{MullvadEndpoint, MullvadWireguardEndpoint}, location::{Coordinates, Location}, @@ -13,7 +12,7 @@ use mullvad_types::{ RelaySettings, SelectedObfuscation, Set, TransportPort, Udp2TcpObfuscationSettings, WireguardConstraints, }, - relay_list::{Relay, RelayList, Udp2TcpEndpointData}, + relay_list::{BridgeEndpointData, Relay, RelayEndpointData, RelayList}, CustomTunnelEndpoint, }; use parking_lot::{Mutex, MutexGuard}; @@ -33,7 +32,7 @@ use talpid_types::{ ErrorExt, }; -use self::matcher::{RelayMatcher, TunnelMatcher, WireguardMatcher}; +use matcher::{OpenVpnMatcher, RelayMatcher, TunnelMatcher, WireguardMatcher}; mod matcher; pub mod updater; @@ -41,12 +40,8 @@ pub mod updater; const DATE_TIME_FORMAT_STR: &str = "%Y-%m-%d %H:%M:%S%.3f"; const RELAYS_FILENAME: &str = "relays.json"; -const DEFAULT_WIREGUARD_PORT: u16 = 51820; -const WIREGUARD_EXIT_CONSTRAINTS: WireguardMatcher = WireguardMatcher { - peer: None, - port: Constraint::Only(DEFAULT_WIREGUARD_PORT), - ip_version: Constraint::Only(IpVersion::V4), -}; +const WIREGUARD_EXIT_PORT: Constraint<u16> = Constraint::Only(51820); +const WIREGUARD_EXIT_IP_VERSION: Constraint<IpVersion> = Constraint::Only(IpVersion::V4); const UDP2TCP_PORTS: [u16; 3] = [80, 443, 5001]; @@ -95,7 +90,15 @@ impl ParsedRelays { } } - pub fn from_relay_list(relay_list: RelayList, last_updated: SystemTime) -> Self { + pub fn from_relay_list(mut relay_list: RelayList, last_updated: SystemTime) -> Self { + // Append data for obfuscation protocols ourselves, since the API does not provide it. + if relay_list.wireguard.udp2tcp_ports.is_empty() { + relay_list + .wireguard + .udp2tcp_ports + .extend(UDP2TCP_PORTS.into_iter()); + } + let mut relays = Vec::new(); for country in &relay_list.countries { let country_name = country.name.clone(); @@ -115,26 +118,6 @@ impl ParsedRelays { latitude, longitude, }); - - Self::filter_invalid_relays(&mut relay_with_location); - - // TODO: The WireGuard data is incorrectly modelled. - // Using a vector here suggests that a relay may use multiple key pairs at a - // time. This is incorrect and will never be the case. - // - // Currently, the `wireguard` vector will have 0 or 1 entries. - // This should be changed into e.g. using an Option<_> instead. - // - - if !relay.tunnels.wireguard.is_empty() { - for port in UDP2TCP_PORTS { - relay_with_location - .obfuscators - .udp2tcp - .push(Udp2TcpEndpointData { port }); - } - } - relays.push(relay_with_location); } } @@ -147,36 +130,6 @@ impl ParsedRelays { } } - fn filter_invalid_relays(relay: &mut Relay) { - let total_openvpn_endpoints = relay.tunnels.openvpn.len(); - let openvpn_endpoints = &mut relay.tunnels.openvpn; - openvpn_endpoints.retain(|data| data.port != 0); - - if openvpn_endpoints.len() < total_openvpn_endpoints { - log::error!( - "Relay {} contained {} invalid OpenVPN endpoints out of {} endpoints", - relay.hostname, - total_openvpn_endpoints - openvpn_endpoints.len(), - total_openvpn_endpoints - ); - } - - let total_wireguard_endpoints = relay.tunnels.wireguard.len(); - let wireguard_endpoints = &mut relay.tunnels.wireguard; - wireguard_endpoints.retain(|data| { - !data.port_ranges.is_empty() && data.port_ranges.iter().all(|(start, end)| start <= end) - }); - - if wireguard_endpoints.len() < total_wireguard_endpoints { - log::error!( - "Relay {} contained {} invalid WireGuard endpoints out of {} endpoints", - relay.hostname, - total_wireguard_endpoints - wireguard_endpoints.len(), - total_wireguard_endpoints - ); - } - } - pub fn from_file(path: impl AsRef<Path>) -> Result<Self, Error> { log::debug!("Reading relays from {}", path.as_ref().display()); let (last_modified, file) = @@ -349,7 +302,15 @@ impl RelaySelector { return None; } - let matcher = RelayMatcher::from(relay_constraints.clone()); + let (openvpn_data, wireguard_data) = { + let relays = self.parsed_relays.lock(); + ( + relays.locations.openvpn.clone(), + relays.locations.wireguard.clone(), + ) + }; + + let matcher = RelayMatcher::new(relay_constraints.clone(), openvpn_data, wireguard_data); let mut matching_locations: Vec<Location> = self .parsed_relays .lock() @@ -385,11 +346,14 @@ impl RelaySelector { location: location.clone(), providers: providers.clone(), ownership: *ownership, - tunnel: openvpn_constraints, + tunnel: OpenVpnMatcher::new( + openvpn_constraints, + self.parsed_relays.lock().locations.openvpn.clone(), + ), }; - if relay_matcher.tunnel.port.is_any() && bridge_state == BridgeState::On { - relay_matcher.tunnel.port = Constraint::Only(TransportPort { + if relay_matcher.tunnel.constraints.port.is_any() && bridge_state == BridgeState::On { + relay_matcher.tunnel.constraints.port = Constraint::Only(TransportPort { protocol: TransportProtocol::Tcp, port: Constraint::Any, }); @@ -401,7 +365,7 @@ impl RelaySelector { let (preferred_port, preferred_protocol) = Self::preferred_openvpn_constraints(retry_attempt); - let should_try_preferred = match &mut preferred_relay_matcher.tunnel.port { + let should_try_preferred = match &mut preferred_relay_matcher.tunnel.constraints.port { any @ Constraint::Any => { *any = Constraint::Only(TransportPort { protocol: preferred_protocol, @@ -434,7 +398,7 @@ impl RelaySelector { ) -> Result<NormalSelectedRelay, Error> { let mut exit_matcher = RelayMatcher { location: exit_location, - tunnel: WIREGUARD_EXIT_CONSTRAINTS.clone(), + tunnel: self.wireguard_exit_matcher(), ..entry_matcher.clone() }; @@ -493,7 +457,10 @@ impl RelaySelector { location: location.clone(), providers: providers.clone(), ownership: *ownership, - tunnel: wireguard_constraints.clone().into(), + tunnel: WireguardMatcher::new( + wireguard_constraints.clone(), + self.parsed_relays.lock().locations.wireguard.clone(), + ), }; let mut preferred_matcher: RelayMatcher<WireguardMatcher> = entry_relay_matcher.clone(); @@ -521,7 +488,15 @@ impl RelaySelector { &self, relay_constraints: &RelayConstraints, ) -> Result<NormalSelectedRelay, Error> { - let mut matcher: RelayMatcher<AnyTunnelMatcher> = relay_constraints.clone().into(); + let (openvpn_data, wireguard_data) = { + let relays = self.parsed_relays.lock(); + ( + relays.locations.openvpn.clone(), + relays.locations.wireguard.clone(), + ) + }; + let mut matcher = + RelayMatcher::new(relay_constraints.clone(), openvpn_data, wireguard_data); let mut selected_entry_relay = None; let mut selected_entry_endpoint = None; @@ -536,7 +511,7 @@ impl RelaySelector { // Pick the entry relay first if its location constraint is a subset of the exit location. if relay_constraints.wireguard_constraints.use_multihop { - matcher.tunnel.wireguard = WIREGUARD_EXIT_CONSTRAINTS.clone(); + matcher.tunnel.wireguard = self.wireguard_exit_matcher(); if relay_constraints .wireguard_constraints .entry_location @@ -826,8 +801,8 @@ impl RelaySelector { .lock() .relays() .iter() - .filter(|relay| relay.active) - .filter_map(|relay| Self::matching_bridge_relay(relay, constraints)) + .filter(|relay| relay.active && Self::matching_bridge_relay(relay, constraints)) + .cloned() .collect(); if matching_relays.is_empty() { @@ -849,7 +824,7 @@ impl RelaySelector { self.pick_random_relay(&matching_relays) }; relay.and_then(|relay| { - self.pick_random_bridge(relay) + self.pick_random_bridge(&self.parsed_relays.lock().locations.bridge, relay) .map(|bridge| (bridge, relay.clone())) }) } @@ -931,21 +906,17 @@ impl RelaySelector { _endpoint: &MullvadWireguardEndpoint, retry_attempt: u32, ) -> Option<SelectedObfuscator> { + let udp2tcp_ports = &self.parsed_relays.lock().locations.wireguard.udp2tcp_ports; let udp2tcp_endpoint = if obfuscation_settings.port.is_only() { - relay - .obfuscators - .udp2tcp + udp2tcp_ports .iter() - .find(|&candidate| obfuscation_settings.port.matches_eq(&candidate.port)) + .find(|&candidate| obfuscation_settings.port == Constraint::Only(*candidate)) } else { - relay - .obfuscators - .udp2tcp - .get(retry_attempt as usize % relay.obfuscators.udp2tcp.len()) + udp2tcp_ports.get(retry_attempt as usize % udp2tcp_ports.len()) }; udp2tcp_endpoint .map(|udp2tcp_endpoint| ObfuscatorConfig::Udp2Tcp { - endpoint: SocketAddr::new(relay.ipv4_addr_in.into(), udp2tcp_endpoint.port), + endpoint: SocketAddr::new(relay.ipv4_addr_in.into(), *udp2tcp_endpoint), }) .map(|config| SelectedObfuscator { config, @@ -967,7 +938,7 @@ impl RelaySelector { let location_supports_openvpn = self.parsed_relays.lock().relays().iter().any(|relay| { relay.active - && !relay.tunnels.openvpn.is_empty() + && relay.endpoint_data == RelayEndpointData::Openvpn && location_constraint.matches(relay) && providers_constraint.matches(relay) && ownership_constraint.matches(relay) @@ -981,7 +952,7 @@ impl RelaySelector { let location_supports_wireguard = self.parsed_relays.lock().relays().iter().any(|relay| { relay.active - && !relay.tunnels.wireguard.is_empty() + && matches!(relay.endpoint_data, RelayEndpointData::Wireguard(_)) && location_constraint.matches(relay) && providers_constraint.matches(relay) && ownership_constraint.matches(relay) @@ -1071,27 +1042,11 @@ impl RelaySelector { .ok_or(Error::NoRelay) } - fn matching_bridge_relay( - relay: &Relay, - constraints: &InternalBridgeConstraints, - ) -> Option<Relay> { - if !constraints.location.matches(relay) - || !constraints.providers.matches(relay) - || !constraints.ownership.matches(relay) - { - return None; - } - - let mut filtered_relay = relay.clone(); - filtered_relay - .bridges - .shadowsocks - .retain(|bridge| constraints.transport_protocol.matches_eq(&bridge.protocol)); - if filtered_relay.bridges.shadowsocks.is_empty() { - return None; - } - - Some(filtered_relay) + fn matching_bridge_relay(relay: &Relay, constraints: &InternalBridgeConstraints) -> bool { + constraints.location.matches(relay) + && constraints.providers.matches(relay) + && constraints.ownership.matches(relay) + && relay.endpoint_data == RelayEndpointData::Bridge } /// Picks a relay using [Self::pick_random_relay_fn], using the `weight` member of each relay @@ -1136,10 +1091,15 @@ impl RelaySelector { } /// Picks a random bridge from a relay. - fn pick_random_bridge(&self, relay: &Relay) -> Option<ProxySettings> { - relay - .bridges - .shadowsocks + fn pick_random_bridge( + &self, + data: &BridgeEndpointData, + relay: &Relay, + ) -> Option<ProxySettings> { + if relay.endpoint_data != RelayEndpointData::Bridge { + return None; + } + data.shadowsocks .choose(&mut rand::thread_rng()) .map(|shadowsocks_endpoint| { log::info!( @@ -1149,9 +1109,7 @@ impl RelaySelector { shadowsocks_endpoint.port, shadowsocks_endpoint.protocol ); - shadowsocks_endpoint - .clone() - .to_proxy_settings(relay.ipv4_addr_in.into()) + shadowsocks_endpoint.to_proxy_settings(relay.ipv4_addr_in.into()) }) } @@ -1181,6 +1139,14 @@ impl RelaySelector { Ok(bundled_relays) } } + + fn wireguard_exit_matcher(&self) -> WireguardMatcher { + let mut tunnel = + WireguardMatcher::from_endpoint(self.parsed_relays.lock().locations.wireguard.clone()); + tunnel.ip_version = WIREGUARD_EXIT_IP_VERSION; + tunnel.port = WIREGUARD_EXIT_PORT; + tunnel + } } #[derive(Debug)] @@ -1242,8 +1208,8 @@ mod test { use mullvad_types::{ relay_constraints::{BridgeConstraints, RelayConstraints}, relay_list::{ - OpenVpnEndpointData, Relay, RelayBridges, RelayListCity, RelayListCountry, - RelayObfuscators, RelayTunnels, WireguardEndpointData, + OpenVpnEndpoint, OpenVpnEndpointData, Relay, RelayListCity, RelayListCountry, + WireguardEndpointData, WireguardRelayEndpointData, }, }; use talpid_types::net::wireguard::PublicKey; @@ -1271,23 +1237,9 @@ mod test { owned: true, provider: "31173".to_string(), weight: 1, - tunnels: RelayTunnels { - openvpn: vec![], - wireguard: vec![ - WireguardEndpointData { - port_ranges: vec![(53, 53), (4000, 33433), (33565, 51820), (52000, 60000)], - ipv4_gateway: "10.64.0.1".parse().unwrap(), - ipv6_gateway: "fc00:bbbb:bbbb:bb01::1".parse().unwrap(), - public_key: PublicKey::from_base64("BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=").unwrap(), - }, - ], - }, - bridges: RelayBridges { - shadowsocks: vec![], - }, - obfuscators: RelayObfuscators { - udp2tcp: vec![], - }, + endpoint_data: RelayEndpointData::Wireguard(WireguardRelayEndpointData { + public_key: PublicKey::from_base64("BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=").unwrap(), + }), location: None, }, Relay { @@ -1299,23 +1251,9 @@ mod test { owned: false, provider: "31173".to_string(), weight: 1, - tunnels: RelayTunnels { - openvpn: vec![], - wireguard: vec![ - WireguardEndpointData { - port_ranges: vec![(53, 53), (4000, 33433), (33565, 51820), (52000, 60000)], - ipv4_gateway: "10.64.0.1".parse().unwrap(), - ipv6_gateway: "fc00:bbbb:bbbb:bb01::1".parse().unwrap(), - public_key: PublicKey::from_base64("veGD6/aEY6sMfN3Ls7YWPmNgu3AheO7nQqsFT47YSws=").unwrap(), - }, - ], - }, - bridges: RelayBridges { - shadowsocks: vec![], - }, - obfuscators: RelayObfuscators { - udp2tcp: vec![], - }, + endpoint_data: RelayEndpointData::Wireguard(WireguardRelayEndpointData { + public_key: PublicKey::from_base64("BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=").unwrap(), + }), location: None, }, Relay { @@ -1327,81 +1265,7 @@ mod test { owned: true, provider: "31173".to_string(), weight: 1, - tunnels: RelayTunnels { - openvpn: vec![ - OpenVpnEndpointData { - port: 1194, - protocol: TransportProtocol::Udp, - }, - OpenVpnEndpointData { - port: 443, - protocol: TransportProtocol::Tcp, - }, - OpenVpnEndpointData { - port: 80, - protocol: TransportProtocol::Tcp, - }, - ], - wireguard: vec![], - }, - bridges: RelayBridges { - shadowsocks: vec![], - }, - obfuscators: RelayObfuscators { - udp2tcp: vec![], - }, - location: None, - }, - Relay { - hostname: "se11-wireguard-filtered".to_string(), - ipv4_addr_in: "185.213.154.69".parse().unwrap(), - ipv6_addr_in: Some("2a03:1b20:5:f011::a10f".parse().unwrap()), - include_in_country: true, - active: true, - owned: true, - provider: "31173".to_string(), - weight: 1, - tunnels: RelayTunnels { - openvpn: vec![], - wireguard: vec![ - WireguardEndpointData { - port_ranges: vec![], - ipv4_gateway: "10.64.0.1".parse().unwrap(), - ipv6_gateway: "fc00:bbbb:bbbb:bb01::1".parse().unwrap(), - public_key: PublicKey::from_base64("veGD6/aEY6sMfN3Ls7YWPmNgu3AheO7nQqsFT47YSws=").unwrap(), - }, - ], - }, - bridges: RelayBridges { - shadowsocks: vec![], - }, - obfuscators: RelayObfuscators { - udp2tcp: vec![], - }, - location: None, - }, - Relay { - hostname: "se-got-010-filtered".to_string(), - ipv4_addr_in: "185.213.154.69".parse().unwrap(), - ipv6_addr_in: Some("2a03:1b20:5:f011::a10f".parse().unwrap()), - include_in_country: true, - active: true, - owned: true, - provider: "31173".to_string(), - weight: 1, - tunnels: RelayTunnels { - openvpn: vec![OpenVpnEndpointData{ - port: 0, - protocol: TransportProtocol::Udp, - }], - wireguard: vec![], - }, - bridges: RelayBridges { - shadowsocks: vec![], - }, - obfuscators: RelayObfuscators { - udp2tcp: vec![], - }, + endpoint_data: RelayEndpointData::Openvpn, location: None, } ], @@ -1409,6 +1273,31 @@ mod test { ], } ], + openvpn: OpenVpnEndpointData { + ports: vec![ + OpenVpnEndpoint { + port: 1194, + protocol: TransportProtocol::Udp, + }, + OpenVpnEndpoint { + port: 443, + protocol: TransportProtocol::Tcp, + }, + OpenVpnEndpoint { + port: 80, + protocol: TransportProtocol::Tcp, + }, + ], + }, + bridge: BridgeEndpointData { + shadowsocks: vec![], + }, + wireguard: WireguardEndpointData { + port_ranges: vec![(53, 53), (4000, 33433), (33565, 51820), (52000, 60000)], + ipv4_gateway: "10.64.0.1".parse().unwrap(), + ipv6_gateway: "fc00:bbbb:bbbb:bb01::1".parse().unwrap(), + udp2tcp_ports: vec![], + }, }; } @@ -1870,32 +1759,6 @@ mod test { } #[test] - fn test_filtering_invalid_endpoint_relays() { - let relay_selector = new_relay_selector(); - let mut constraints = RelayConstraints { - location: Constraint::Only(LocationConstraint::Hostname( - "se".to_string(), - "got".to_string(), - "se11-wireguard-filtered".to_string(), - )), - ..RelayConstraints::default() - }; - relay_selector - .get_tunnel_endpoint(&constraints, BridgeState::Off, 0) - .expect_err("Successfully selected a relay that should be filtered"); - - constraints.location = Constraint::Only(LocationConstraint::Hostname( - "se".to_string(), - "got".to_string(), - "se-got-010-filtered".to_string(), - )); - - relay_selector - .get_tunnel_endpoint(&constraints, BridgeState::Off, 0) - .expect_err("Successfully selected a relay that should be filtered"); - } - - #[test] fn test_ownership() { let relay_selector = new_relay_selector(); let mut constraints = RelayConstraints::default(); diff --git a/mullvad-relay-selector/src/matcher.rs b/mullvad-relay-selector/src/matcher.rs index 350a106745..260dc7e7db 100644 --- a/mullvad-relay-selector/src/matcher.rs +++ b/mullvad-relay-selector/src/matcher.rs @@ -4,11 +4,16 @@ use mullvad_types::{ Constraint, LocationConstraint, Match, OpenVpnConstraints, Ownership, Providers, RelayConstraints, WireguardConstraints, }, - relay_list::{Relay, RelayTunnels, WireguardEndpointData}, + relay_list::{ + OpenVpnEndpoint, OpenVpnEndpointData, Relay, RelayEndpointData, WireguardEndpointData, + }, +}; +use rand::{ + prelude::{IteratorRandom, SliceRandom}, + Rng, }; -use rand::{seq::SliceRandom, Rng}; use std::net::{IpAddr, SocketAddr}; -use talpid_types::net::{all_of_the_internet, wireguard, IpVersion, TunnelType}; +use talpid_types::net::{all_of_the_internet, wireguard, Endpoint, IpVersion, TunnelType}; #[derive(Clone)] pub struct RelayMatcher<T: TunnelMatcher> { @@ -18,22 +23,24 @@ pub struct RelayMatcher<T: TunnelMatcher> { pub tunnel: T, } -impl From<RelayConstraints> for RelayMatcher<AnyTunnelMatcher> { - fn from(constraints: RelayConstraints) -> Self { +impl RelayMatcher<AnyTunnelMatcher> { + pub fn new( + constraints: RelayConstraints, + openvpn_data: OpenVpnEndpointData, + wireguard_data: WireguardEndpointData, + ) -> Self { Self { location: constraints.location, providers: constraints.providers, ownership: constraints.ownership, tunnel: AnyTunnelMatcher { - wireguard: constraints.wireguard_constraints.into(), - openvpn: constraints.openvpn_constraints, + wireguard: WireguardMatcher::new(constraints.wireguard_constraints, wireguard_data), + openvpn: OpenVpnMatcher::new(constraints.openvpn_constraints, openvpn_data), tunnel_type: constraints.tunnel_protocol, }, } } -} -impl RelayMatcher<AnyTunnelMatcher> { pub fn into_wireguard_matcher(self) -> RelayMatcher<WireguardMatcher> { RelayMatcher { tunnel: self.tunnel.wireguard, @@ -83,35 +90,65 @@ pub trait TunnelMatcher: Clone { impl TunnelMatcher for OpenVpnMatcher { fn filter_matching_endpoints(&self, relay: &Relay) -> Option<Relay> { - let tunnels = relay - .tunnels - .openvpn - .iter() - .filter(|endpoint| self.matches(endpoint)) - .cloned() - .collect::<Vec<_>>(); - if tunnels.is_empty() { + if !self.matches(&self.data) || !matches!(relay.endpoint_data, RelayEndpointData::Openvpn) { return None; } - let mut relay = relay.clone(); - relay.tunnels = RelayTunnels { - openvpn: tunnels, - wireguard: vec![], - }; - Some(relay) + Some(relay.clone()) } fn mullvad_endpoint(&self, relay: &Relay) -> Option<MullvadEndpoint> { - relay - .tunnels - .openvpn - .choose(&mut rand::thread_rng()) - .cloned() - .map(|endpoint| endpoint.into_mullvad_endpoint(relay.ipv4_addr_in.into())) + self.get_transport_port().map(|endpoint| { + MullvadEndpoint::OpenVpn(Endpoint::new( + relay.ipv4_addr_in, + endpoint.port, + endpoint.protocol, + )) + }) + } +} + +#[derive(Debug, Clone)] +pub struct OpenVpnMatcher { + pub constraints: OpenVpnConstraints, + pub data: OpenVpnEndpointData, +} + +impl OpenVpnMatcher { + pub fn new(constraints: OpenVpnConstraints, data: OpenVpnEndpointData) -> Self { + Self { constraints, data } + } + + fn get_transport_port(&self) -> Option<&OpenVpnEndpoint> { + match self.constraints.port { + Constraint::Any => self.data.ports.choose(&mut rand::thread_rng()), + Constraint::Only(transport_port) => self + .data + .ports + .iter() + .filter(|endpoint| { + transport_port + .port + .map(|port| port == endpoint.port) + .unwrap_or(true) + && transport_port.protocol == endpoint.protocol + }) + .choose(&mut rand::thread_rng()), + } } } -pub type OpenVpnMatcher = OpenVpnConstraints; +impl Match<OpenVpnEndpointData> for OpenVpnMatcher { + fn matches(&self, endpoint: &OpenVpnEndpointData) -> bool { + match self.constraints.port { + Constraint::Any => true, + Constraint::Only(transport_port) => endpoint.ports.iter().any(|endpoint| { + transport_port.protocol == endpoint.protocol + && (transport_port.port.is_any() + || transport_port.port == Constraint::Only(endpoint.port)) + }), + } + } +} #[derive(Clone)] pub struct AnyTunnelMatcher { @@ -132,11 +169,10 @@ impl TunnelMatcher for AnyTunnelMatcher { let openvpn_relay = self.openvpn.filter_matching_endpoints(relay); match (wireguard_relay, openvpn_relay) { - (Some(mut matched_relay), Some(openvpn_relay)) => { - matched_relay.tunnels.openvpn = openvpn_relay.tunnels.openvpn; - Some(matched_relay) - } (Some(relay), None) | (None, Some(relay)) => Some(relay), + (Some(_), Some(_)) => { + unreachable!("relay cannot match multiple endpoint types") + } _ => None, } } @@ -150,15 +186,10 @@ impl TunnelMatcher for AnyTunnelMatcher { fn mullvad_endpoint(&self, relay: &Relay) -> Option<MullvadEndpoint> { #[cfg(not(target_os = "android"))] match self.tunnel_type { - Constraint::Any => vec![ - self.openvpn.mullvad_endpoint(relay), - self.wireguard.mullvad_endpoint(relay), - ] - .into_iter() - .flatten() - .collect::<Vec<_>>() - .choose(&mut rand::thread_rng()) - .cloned(), + Constraint::Any => self + .openvpn + .mullvad_endpoint(relay) + .or_else(|| self.wireguard.mullvad_endpoint(relay)), Constraint::Only(TunnelType::OpenVpn) => self.openvpn.mullvad_endpoint(relay), Constraint::Only(TunnelType::Wireguard) => self.wireguard.mullvad_endpoint(relay), } @@ -168,25 +199,47 @@ impl TunnelMatcher for AnyTunnelMatcher { } } -#[derive(Clone)] +#[derive(Default, Clone)] pub struct WireguardMatcher { /// The peer is an already selected peer relay to be used with multihop. /// It's stored here so we can exclude it from further selections being made. pub peer: Option<Relay>, pub port: Constraint<u16>, pub ip_version: Constraint<IpVersion>, + + pub data: WireguardEndpointData, } impl WireguardMatcher { + pub fn new(constraints: WireguardConstraints, data: WireguardEndpointData) -> Self { + Self { + peer: None, + port: constraints.port, + ip_version: constraints.ip_version, + data, + } + } + + pub fn from_endpoint(data: WireguardEndpointData) -> Self { + Self { + data, + ..Default::default() + } + } + fn wg_data_to_endpoint( &self, relay: &Relay, - data: WireguardEndpointData, + data: &WireguardEndpointData, ) -> Option<MullvadEndpoint> { let host = self.get_address_for_wireguard_relay(relay)?; - let port = self.get_port_for_wireguard_relay(&data)?; + let port = self.get_port_for_wireguard_relay(data)?; let peer_config = wireguard::PeerConfig { - public_key: data.public_key, + public_key: relay + .endpoint_data + .unwrap_wireguard_ref() + .public_key + .clone(), endpoint: SocketAddr::new(host, port), allowed_ips: all_of_the_internet(), psk: None, @@ -244,28 +297,6 @@ impl WireguardMatcher { } } -impl From<WireguardConstraints> for WireguardMatcher { - fn from(constraints: WireguardConstraints) -> Self { - Self { - peer: None, - port: constraints.port, - ip_version: constraints.ip_version, - } - } -} - -impl Match<WireguardEndpointData> for WireguardMatcher { - fn matches(&self, endpoint: &WireguardEndpointData) -> bool { - match self.port { - Constraint::Any => true, - Constraint::Only(port) => endpoint - .port_ranges - .iter() - .any(|range| (port >= range.0 && port <= range.1)), - } - } -} - impl TunnelMatcher for WireguardMatcher { fn filter_matching_endpoints(&self, relay: &Relay) -> Option<Relay> { if self @@ -276,30 +307,13 @@ impl TunnelMatcher for WireguardMatcher { { return None; } - - let tunnels = relay - .tunnels - .wireguard - .iter() - .filter(|endpoint| self.matches(*endpoint)) - .cloned() - .collect::<Vec<_>>(); - if tunnels.is_empty() { + if !matches!(relay.endpoint_data, RelayEndpointData::Wireguard(..)) { return None; } - let mut relay = relay.clone(); - relay.tunnels = RelayTunnels { - wireguard: tunnels, - openvpn: vec![], - }; - Some(relay) + Some(relay.clone()) } fn mullvad_endpoint(&self, relay: &Relay) -> Option<MullvadEndpoint> { - relay - .tunnels - .wireguard - .choose(&mut rand::thread_rng()) - .and_then(|wg_tunnel| self.wg_data_to_endpoint(relay, (*wg_tunnel).clone())) + self.wg_data_to_endpoint(relay, &self.data) } } diff --git a/mullvad-types/src/endpoint.rs b/mullvad-types/src/endpoint.rs index bfba11a481..3258032afe 100644 --- a/mullvad-types/src/endpoint.rs +++ b/mullvad-types/src/endpoint.rs @@ -1,12 +1,6 @@ -use serde::{Deserialize, Serialize}; -use std::{ - fmt, - net::{Ipv4Addr, Ipv6Addr}, -}; +use std::net::{Ipv4Addr, Ipv6Addr}; use talpid_types::net::{wireguard, Endpoint, TransportProtocol}; -use crate::relay_list::{OpenVpnEndpointData, WireguardEndpointData}; - /// Contains server data needed to connect to a single mullvad endpoint #[derive(Debug, Clone)] pub enum MullvadEndpoint { @@ -45,40 +39,3 @@ impl MullvadEndpoint { } } } -/// TunnelEndpointData contains data required to connect to a given tunnel endpoint. -/// Different endpoint types can require different types of data. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] -pub enum TunnelEndpointData { - /// Extra parameters for an OpenVPN tunnel endpoint. - #[serde(rename = "openvpn")] - OpenVpn(OpenVpnEndpointData), - /// Extra parameters for a Wireguard tunnel endpoint. - #[serde(rename = "wireguard")] - Wireguard(WireguardEndpointData), -} -impl From<OpenVpnEndpointData> for TunnelEndpointData { - fn from(endpoint_data: OpenVpnEndpointData) -> TunnelEndpointData { - TunnelEndpointData::OpenVpn(endpoint_data) - } -} - -impl From<WireguardEndpointData> for TunnelEndpointData { - fn from(endpoint_data: WireguardEndpointData) -> TunnelEndpointData { - TunnelEndpointData::Wireguard(endpoint_data) - } -} - -impl fmt::Display for TunnelEndpointData { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - match self { - TunnelEndpointData::OpenVpn(openvpn_data) => { - write!(f, "OpenVPN ")?; - openvpn_data.fmt(f) - } - TunnelEndpointData::Wireguard(wireguard_data) => { - write!(f, "Wireguard ")?; - wireguard_data.fmt(f) - } - } - } -} diff --git a/mullvad-types/src/relay_constraints.rs b/mullvad-types/src/relay_constraints.rs index 7311928fec..85db48534a 100644 --- a/mullvad-types/src/relay_constraints.rs +++ b/mullvad-types/src/relay_constraints.rs @@ -3,7 +3,7 @@ use crate::{ location::{CityCode, CountryCode, Hostname}, - relay_list::{OpenVpnEndpointData, Relay}, + relay_list::Relay, CustomTunnelEndpoint, }; #[cfg(target_os = "android")] @@ -440,21 +440,6 @@ impl fmt::Display for OpenVpnConstraints { } } -impl Match<OpenVpnEndpointData> for OpenVpnConstraints { - fn matches(&self, endpoint: &OpenVpnEndpointData) -> bool { - match self.port { - Constraint::Any => true, - Constraint::Only(transport_port) => { - transport_port.protocol == endpoint.protocol - && match transport_port.port { - Constraint::Any => true, - Constraint::Only(port) => port == endpoint.port, - } - } - } - } -} - /// [`Constraint`]s applicable to WireGuard relay servers. #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(default)] diff --git a/mullvad-types/src/relay_list.rs b/mullvad-types/src/relay_list.rs index e5a40a8f4e..643bcbd798 100644 --- a/mullvad-types/src/relay_list.rs +++ b/mullvad-types/src/relay_list.rs @@ -1,36 +1,34 @@ -use crate::{ - endpoint::MullvadEndpoint, - location::{CityCode, CountryCode, Location}, -}; +use crate::location::{CityCode, CountryCode, Location}; #[cfg(target_os = "android")] use jnix::IntoJava; use serde::{Deserialize, Serialize}; -use std::{ - fmt, - net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, -}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use talpid_types::net::{ openvpn::{ProxySettings, ShadowsocksProxySettings}, - wireguard, Endpoint, TransportProtocol, + wireguard, TransportProtocol, }; /// Stores a list of relays for each country obtained from the API using /// `mullvad_api::RelayListProxy`. This can also be passed to frontends. -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Default, Debug, Clone, Deserialize, Serialize)] #[cfg_attr(target_os = "android", derive(IntoJava))] #[cfg_attr(target_os = "android", jnix(package = "net.mullvad.mullvadvpn.model"))] pub struct RelayList { #[cfg_attr(target_os = "android", jnix(skip))] pub etag: Option<String>, pub countries: Vec<RelayListCountry>, + #[cfg_attr(target_os = "android", jnix(skip))] + #[serde(rename = "openvpn")] + pub openvpn: OpenVpnEndpointData, + #[cfg_attr(target_os = "android", jnix(skip))] + pub bridge: BridgeEndpointData, + #[cfg_attr(target_os = "android", jnix(skip))] + pub wireguard: WireguardEndpointData, } impl RelayList { pub fn empty() -> Self { - Self { - etag: None, - countries: Vec::new(), - } + Self::default() } } @@ -78,60 +76,45 @@ pub struct Relay { pub provider: String, #[cfg_attr(target_os = "android", jnix(skip))] pub weight: u64, - #[serde(skip_serializing_if = "RelayTunnels::is_empty", default)] - pub tunnels: RelayTunnels, - #[serde(skip_serializing_if = "RelayBridges::is_empty", default)] - #[cfg_attr(target_os = "android", jnix(skip))] - pub bridges: RelayBridges, - #[serde(skip_serializing_if = "RelayObfuscators::is_empty", default)] - #[cfg_attr(target_os = "android", jnix(skip))] - pub obfuscators: RelayObfuscators, + pub endpoint_data: RelayEndpointData, #[cfg_attr(target_os = "android", jnix(skip))] pub location: Option<Location>, } -/// Provides protocol-specific information about a [`Relay`]. -#[derive(Debug, Default, Clone, Deserialize, Serialize)] -#[serde(default)] +/// Specifies the type of a relay or relay-specific endpoint data. +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] #[cfg_attr(target_os = "android", derive(IntoJava))] #[cfg_attr(target_os = "android", jnix(package = "net.mullvad.mullvadvpn.model"))] -pub struct RelayTunnels { - #[cfg_attr(target_os = "android", jnix(skip))] - pub openvpn: Vec<OpenVpnEndpointData>, - pub wireguard: Vec<WireguardEndpointData>, +pub enum RelayEndpointData { + Openvpn, + Bridge, + Wireguard(WireguardRelayEndpointData), } -impl RelayTunnels { - pub fn is_empty(&self) -> bool { - self.openvpn.is_empty() && self.wireguard.is_empty() - } - - pub fn clear(&mut self) { - self.openvpn.clear(); - self.wireguard.clear(); +impl RelayEndpointData { + pub fn unwrap_wireguard_ref(&self) -> &WireguardRelayEndpointData { + if let RelayEndpointData::Wireguard(wg) = &self { + return wg; + } + panic!("not a wireguard endpoint"); } } -/// Data needed to connect to an OpenVPN endpoint at a [`Relay`]. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)] +/// Data needed to connect to OpenVPN endpoints. +#[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)] pub struct OpenVpnEndpointData { - pub port: u16, - pub protocol: TransportProtocol, -} - -impl OpenVpnEndpointData { - pub fn into_mullvad_endpoint(self, host: IpAddr) -> MullvadEndpoint { - MullvadEndpoint::OpenVpn(Endpoint::new(host, self.port, self.protocol)) - } + pub ports: Vec<OpenVpnEndpoint>, } -impl fmt::Display for OpenVpnEndpointData { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "{} port {}", self.protocol, self.port) - } +/// Data needed to connect to OpenVPN endpoints. +#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)] +pub struct OpenVpnEndpoint { + pub port: u16, + pub protocol: TransportProtocol, } -/// Data needed to connect to a WireGuard endpoint at a [`Relay`]. +/// Contains data about all WireGuard endpoints, such as valid port ranges. #[derive(Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Debug)] #[cfg_attr(target_os = "android", derive(IntoJava))] #[cfg_attr(target_os = "android", jnix(package = "net.mullvad.mullvadvpn.model"))] @@ -142,45 +125,36 @@ pub struct WireguardEndpointData { /// Gateways to be used with the tunnel pub ipv4_gateway: Ipv4Addr, pub ipv6_gateway: Ipv6Addr, - /// The peer's public key - pub public_key: wireguard::PublicKey, + pub udp2tcp_ports: Vec<u16>, } -impl fmt::Display for WireguardEndpointData { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!( - f, - "gateways {} - {} port_ranges {{ {} }} public_key {}", - self.ipv4_gateway, - self.ipv6_gateway, - self.port_ranges - .iter() - .map(|range| format!("[{} - {}]", range.0, range.1)) - .collect::<Vec<_>>() - .join(","), - self.public_key, - ) +impl Default for WireguardEndpointData { + fn default() -> Self { + Self { + port_ranges: vec![], + ipv4_gateway: "0.0.0.0".parse().unwrap(), + ipv6_gateway: "::".parse().unwrap(), + udp2tcp_ports: vec![], + } } } -/// Used by `mullvad_api::RelayListProxy` to store bridge servers for a [`Relay`]. -#[derive(Debug, Default, Clone, Deserialize, Serialize)] -#[serde(default)] -pub struct RelayBridges { - pub shadowsocks: Vec<ShadowsocksEndpointData>, +/// Contains data about specific WireGuard endpoints, i.e. their public keys. +#[derive(Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Debug)] +#[cfg_attr(target_os = "android", derive(IntoJava))] +#[cfg_attr(target_os = "android", jnix(package = "net.mullvad.mullvadvpn.model"))] +#[cfg_attr(target_os = "android", jnix(skip_all))] +pub struct WireguardRelayEndpointData { + /// Public key used by the relay peer + pub public_key: wireguard::PublicKey, } -impl RelayBridges { - pub fn is_empty(&self) -> bool { - self.shadowsocks.is_empty() - } - - pub fn clear(&mut self) { - self.shadowsocks.clear(); - } +#[derive(Debug, Default, Clone, Deserialize, Serialize)] +pub struct BridgeEndpointData { + pub shadowsocks: Vec<ShadowsocksEndpointData>, } -/// Data needed to connect to a Shadowsocks endpoint at a [`Relay`]. +/// Data needed to connect to Shadowsocks endpoints. #[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)] pub struct ShadowsocksEndpointData { pub port: u16, @@ -198,24 +172,3 @@ impl ShadowsocksEndpointData { }) } } - -#[derive(Debug, Default, Clone, Deserialize, Serialize)] -#[serde(default)] -pub struct RelayObfuscators { - pub udp2tcp: Vec<Udp2TcpEndpointData>, -} - -impl RelayObfuscators { - pub fn is_empty(&self) -> bool { - self.udp2tcp.is_empty() - } - - pub fn clear(&mut self) { - self.udp2tcp.clear(); - } -} - -#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)] -pub struct Udp2TcpEndpointData { - pub port: u16, -} |
