diff options
| author | David Lönnhager <david.l@mullvad.net> | 2022-10-17 10:48:35 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2022-10-17 10:48:35 +0200 |
| commit | 988d6008f77ad728ea4fa911a2a103eccac4d589 (patch) | |
| tree | 92207ccae36925f655c2cdb9857892df2251111f | |
| parent | 86777eb87505855851462817f6672194ecbc2e54 (diff) | |
| parent | f8f4ee29ec02db7b6755158a2915b140d43f47ef (diff) | |
| download | mullvadvpn-988d6008f77ad728ea4fa911a2a103eccac4d589.tar.xz mullvadvpn-988d6008f77ad728ea4fa911a2a103eccac4d589.zip | |
Merge branch 'add-rpc-conversions'
22 files changed, 2185 insertions, 1926 deletions
diff --git a/mullvad-cli/src/cmds/bridge.rs b/mullvad-cli/src/cmds/bridge.rs index a8a3e2de7c..9d1c5172fa 100644 --- a/mullvad-cli/src/cmds/bridge.rs +++ b/mullvad-cli/src/cmds/bridge.rs @@ -277,20 +277,25 @@ impl Bridge { } if let Some(new_providers) = providers { constraints.providers = - types::try_providers_constraint_from_proto(&new_providers).unwrap(); + types::relay_constraints::try_providers_constraint_from_proto( + &new_providers, + ) + .unwrap(); } if let Some(new_ownership) = ownership { - constraints.ownership = types::ownership_constraint_from_proto(new_ownership); + constraints.ownership = + types::relay_constraints::ownership_constraint_from_proto(new_ownership); } constraints } _ => { let location = Constraint::<LocationConstraint>::from(location.unwrap_or_default()); - let providers = - types::try_providers_constraint_from_proto(&providers.unwrap_or_default()) - .unwrap(); + let providers = types::relay_constraints::try_providers_constraint_from_proto( + &providers.unwrap_or_default(), + ) + .unwrap(); let ownership = ownership - .map(types::ownership_constraint_from_proto) + .map(types::relay_constraints::ownership_constraint_from_proto) .unwrap_or_default(); BridgeConstraints { diff --git a/mullvad-cli/src/cmds/connect.rs b/mullvad-cli/src/cmds/connect.rs index 3a879fc6a8..0f470d3d2a 100644 --- a/mullvad-cli/src/cmds/connect.rs +++ b/mullvad-cli/src/cmds/connect.rs @@ -1,6 +1,6 @@ use crate::{format, new_rpc_client, state, Command, Error, Result}; use futures::StreamExt; -use mullvad_management_interface::types::tunnel_state::State; +use mullvad_types::states::TunnelState; pub struct Connect; @@ -35,9 +35,9 @@ impl Command for Connect { while let Some(state) = receiver.next().await { let state = state?; format::print_state(&state, false); - match state.state.unwrap() { - State::Connected(_) => return Ok(()), - State::Error(_) => return Err(Error::CommandFailed("connect")), + match state { + TunnelState::Connected { .. } => return Ok(()), + TunnelState::Error(_) => return Err(Error::CommandFailed("connect")), _ => {} } } diff --git a/mullvad-cli/src/cmds/disconnect.rs b/mullvad-cli/src/cmds/disconnect.rs index 2864001656..4ea5722fe9 100644 --- a/mullvad-cli/src/cmds/disconnect.rs +++ b/mullvad-cli/src/cmds/disconnect.rs @@ -1,6 +1,5 @@ use crate::{format, new_rpc_client, state, Command, Error, Result}; use futures::StreamExt; -use mullvad_management_interface::types::tunnel_state::State::Disconnected; pub struct Disconnect; @@ -35,7 +34,7 @@ impl Command for Disconnect { while let Some(state) = receiver.next().await { let state = state?; format::print_state(&state, false); - if let Disconnected(_) = state.state.unwrap() { + if state.is_disconnected() { return Ok(()); } } diff --git a/mullvad-cli/src/cmds/reconnect.rs b/mullvad-cli/src/cmds/reconnect.rs index 53ca666482..0a39d9f33d 100644 --- a/mullvad-cli/src/cmds/reconnect.rs +++ b/mullvad-cli/src/cmds/reconnect.rs @@ -1,6 +1,6 @@ use crate::{format, new_rpc_client, state, Command, Error, Result}; use futures::StreamExt; -use mullvad_management_interface::types::tunnel_state::State; +use mullvad_types::states::TunnelState; pub struct Reconnect; @@ -35,9 +35,9 @@ impl Command for Reconnect { while let Some(state) = receiver.next().await { let state = state?; format::print_state(&state, false); - match state.state.unwrap() { - State::Connected(_) => return Ok(()), - State::Error(_) => return Err(Error::CommandFailed("reconnect")), + match state { + TunnelState::Connected { .. } => return Ok(()), + TunnelState::Error { .. } => return Err(Error::CommandFailed("reconnect")), _ => {} } } diff --git a/mullvad-cli/src/cmds/status.rs b/mullvad-cli/src/cmds/status.rs index 71f31f5fef..80eb91db68 100644 --- a/mullvad-cli/src/cmds/status.rs +++ b/mullvad-cli/src/cmds/status.rs @@ -2,6 +2,7 @@ use crate::{format, new_rpc_client, Command, Error, Result}; use mullvad_management_interface::{ types::daemon_event::Event as EventType, ManagementServiceClient, }; +use mullvad_types::{location::GeoIpLocation, states::TunnelState}; pub struct Status; @@ -45,6 +46,7 @@ impl Command for Status { if debug { println!("Tunnel state: {:#?}", state); } else { + let state = TunnelState::try_from(state).expect("invalid tunnel state"); format::print_state(&state, verbose); } @@ -58,15 +60,17 @@ impl Command for Status { while let Some(event) = events.message().await? { match event.event.unwrap() { EventType::TunnelState(new_state) => { + let new_state = + TunnelState::try_from(new_state).expect("invalid tunnel state"); + if debug { println!("New tunnel state: {:#?}", new_state); } else { format::print_state(&new_state, verbose); } - use mullvad_management_interface::types::tunnel_state::State::*; - match new_state.state.unwrap() { - Connected(..) | Disconnected(..) => { + match new_state { + TunnelState::Connected { .. } | TunnelState::Disconnected => { if show_full_location { print_location(&mut rpc).await?; } @@ -108,9 +112,8 @@ impl Command for Status { } async fn print_location(rpc: &mut ManagementServiceClient) -> Result<()> { - let location = rpc.get_current_location(()).await; - let location = match location { - Ok(response) => response.into_inner(), + let location = match rpc.get_current_location(()).await { + Ok(response) => GeoIpLocation::try_from(response.into_inner()).expect("invalid geoip data"), Err(status) => { if status.code() == mullvad_management_interface::Code::NotFound { println!("Location data unavailable"); @@ -120,11 +123,11 @@ async fn print_location(rpc: &mut ManagementServiceClient) -> Result<()> { } } }; - if !location.ipv4.is_empty() { - println!("IPv4: {}", location.ipv4); + if let Some(ipv4) = location.ipv4 { + println!("IPv4: {}", ipv4); } - if !location.ipv6.is_empty() { - println!("IPv6: {}", location.ipv6); + if let Some(ipv6) = location.ipv6 { + println!("IPv6: {}", ipv6); } println!( diff --git a/mullvad-cli/src/format.rs b/mullvad-cli/src/format.rs index 5a2fd8b654..693f5c9c7e 100644 --- a/mullvad-cli/src/format.rs +++ b/mullvad-cli/src/format.rs @@ -1,89 +1,91 @@ -use mullvad_management_interface::types::{ - error_state::{ - firewall_policy_error::ErrorType as FirewallPolicyErrorType, Cause as ErrorStateCause, - FirewallPolicyError, GenerationError, - }, - tunnel_state, - tunnel_state::State::*, - ErrorState, ObfuscationType, ProxyType, TransportProtocol, TunnelState, TunnelStateRelayInfo, - TunnelType, +use mullvad_types::{location::GeoIpLocation, states::TunnelState}; +use talpid_types::{ + net::{Endpoint, TunnelEndpoint}, + tunnel::ErrorState, }; -use mullvad_types::auth_failed::AuthFailed; -use std::borrow::Cow; pub fn print_state(state: &TunnelState, verbose: bool) { - match state.state.as_ref().unwrap() { - Error(error) => print_error_state(error.error_state.as_ref().unwrap()), - Connected(tunnel_state::Connected { relay_info }) => { + use TunnelState::*; + + match state { + Error(error) => print_error_state(error), + Connected { endpoint, location } => { println!( "Connected to {}", - format_relay_connection(relay_info.as_ref().unwrap(), verbose) + format_relay_connection(endpoint, location.as_ref(), verbose) ); } - Connecting(tunnel_state::Connecting { relay_info }) => { + Connecting { endpoint, location } => { let ellipsis = if !verbose { "..." } else { "" }; println!( "Connecting to {}{ellipsis}", - format_relay_connection(relay_info.as_ref().unwrap(), verbose) + format_relay_connection(endpoint, location.as_ref(), verbose) ); } - Disconnected(_) => println!("Disconnected"), + Disconnected => println!("Disconnected"), Disconnecting(_) => println!("Disconnecting..."), } } -fn format_relay_connection(relay_info: &TunnelStateRelayInfo, verbose: bool) -> String { - let endpoint = relay_info.tunnel_endpoint.as_ref().unwrap(); - let location = &relay_info.location.as_ref(); - +fn format_relay_connection( + endpoint: &TunnelEndpoint, + location: Option<&GeoIpLocation>, + verbose: bool, +) -> String { let prefix_separator = if verbose { "\n\t" } else { " " }; let mut obfuscator_overlaps = false; let exit_endpoint = { - let mut address = Cow::Borrowed(endpoint.address.as_str()); - let mut protocol = endpoint.protocol; - if let Some(obfuscator) = endpoint.obfuscation.as_ref() { + let mut exit_endpoint = &endpoint.endpoint; + if let Some(obfuscator) = &endpoint.obfuscation { if location .map(|l| l.hostname == l.obfuscator_hostname) .unwrap_or(false) { obfuscator_overlaps = true; - address = Cow::Owned(format!("{}:{}", obfuscator.address, obfuscator.port)); - protocol = obfuscator.protocol; + exit_endpoint = &obfuscator.endpoint; } }; let exit = format_endpoint( - location.map(|l| l.hostname.as_str()), - protocol, - &*address, + location.and_then(|l| l.hostname.as_deref()), + exit_endpoint, verbose, ); - if let Some(location) = location { - format!("{exit} in {}, {}", &location.city, &location.country) - } else { - exit + match location { + Some(GeoIpLocation { + country, + city: Some(city), + .. + }) => { + format!("{exit} in {}, {}", city, country) + } + Some(GeoIpLocation { + country, + city: None, + .. + }) => { + format!("{exit} in {}", country) + } + None => exit, } }; let first_hop = endpoint.entry_endpoint.as_ref().map(|entry| { - let mut address = entry.address.as_str(); - let mut protocol = entry.protocol; - if let Some(obfuscator) = endpoint.obfuscation.as_ref() { - obfuscator_overlaps = true; + let mut entry_endpoint = entry; + if let Some(obfuscator) = &endpoint.obfuscation { if location - .map(|l| l.hostname == l.obfuscator_hostname) + .map(|l| l.entry_hostname == l.obfuscator_hostname) .unwrap_or(false) { - address = &obfuscator.address; - protocol = obfuscator.protocol; + obfuscator_overlaps = true; + entry_endpoint = &obfuscator.endpoint; } }; let endpoint = format_endpoint( - location.map(|l| l.entry_hostname.as_str()), - protocol, - address, + location.and_then(|l| l.entry_hostname.as_deref()), + entry_endpoint, verbose, ); format!("{prefix_separator}via {endpoint}") @@ -92,9 +94,8 @@ fn format_relay_connection(relay_info: &TunnelStateRelayInfo, verbose: bool) -> let obfuscator = endpoint.obfuscation.as_ref().map(|obfuscator| { if !obfuscator_overlaps { let endpoint_str = format_endpoint( - location.map(|l| l.obfuscator_hostname.as_str()), - obfuscator.protocol, - obfuscator.address.as_str(), + location.and_then(|l| l.obfuscator_hostname.as_deref()), + &obfuscator.endpoint, verbose, ); format!("{prefix_separator}obfuscated via {endpoint_str}") @@ -105,22 +106,15 @@ fn format_relay_connection(relay_info: &TunnelStateRelayInfo, verbose: bool) -> let bridge = endpoint.proxy.as_ref().map(|proxy| { let proxy_endpoint = format_endpoint( - location.map(|l| l.bridge_hostname.as_str()), - proxy.protocol, - proxy.address.as_str(), + location.and_then(|l| l.bridge_hostname.as_deref()), + &proxy.endpoint, verbose, ); format!("{prefix_separator}via {proxy_endpoint}") }); let tunnel_type = if verbose { - let tunnel = match TunnelType::from_i32(endpoint.tunnel_type).expect("invalid tunnel type") - { - TunnelType::Wireguard => "WireGuard", - TunnelType::Openvpn => "OpenVPN", - }; - - format!("\nTunnel type: {tunnel}") + format!("\nTunnel type: {}", endpoint.tunnel_type) } else { String::new() }; @@ -135,16 +129,11 @@ fn format_relay_connection(relay_info: &TunnelStateRelayInfo, verbose: bool) -> let mut bridge_type = String::new(); let mut obfuscator_type = String::new(); if verbose { - if let Some(bridge) = endpoint.proxy.as_ref() { - let bridge = match ProxyType::from_i32(bridge.proxy_type).expect("invalid proxy type") { - ProxyType::Shadowsocks => "Shadowsocks", - ProxyType::Custom => "custom bridge", - }; - bridge_type = format!("\nBridge type: {}", bridge); + if let Some(bridge) = &endpoint.proxy { + bridge_type = format!("\nBridge type: {}", bridge.proxy_type); } - if let Some(obfuscator) = endpoint.obfuscation.as_ref() { - let obfuscation = convert_obfuscator_type(obfuscator.obfuscation_type); - obfuscator_type = format!("\nObfuscator: {obfuscation}"); + if let Some(obfuscator) = &endpoint.obfuscation { + obfuscator_type = format!("\nObfuscator: {}", obfuscator.obfuscation_type); } } @@ -156,118 +145,27 @@ fn format_relay_connection(relay_info: &TunnelStateRelayInfo, verbose: bool) -> ) } -fn convert_obfuscator_type(obfuscator: i32) -> &'static str { - match ObfuscationType::from_i32(obfuscator).expect("invalid obfuscator type") { - ObfuscationType::Udp2tcp => "Udp2Tcp", - } -} - -fn format_endpoint( - hostname: Option<&str>, - protocol_enum: i32, - addr: &str, - verbose: bool, -) -> String { - let protocol = format_protocol( - TransportProtocol::from_i32(protocol_enum).expect("invalid transport protocol"), - ); - +fn format_endpoint(hostname: Option<&str>, endpoint: &Endpoint, verbose: bool) -> String { match (hostname, verbose) { - (Some(hostname), true) => format!("{hostname} ({addr}/{protocol})"), - (None, true) => format!("{addr}/{protocol}"), + (Some(hostname), true) => format!("{hostname} ({endpoint})"), + (None, true) => endpoint.to_string(), (Some(hostname), false) => hostname.to_string(), - (None, false) => addr.to_string(), + (None, false) => endpoint.address.to_string(), } } fn print_error_state(error_state: &ErrorState) { - if error_state.blocking_error.is_some() { + if error_state.block_failure().is_some() { eprintln!("Mullvad daemon failed to setup firewall rules!"); eprintln!("Daemon cannot block traffic from flowing, non-local traffic will leak"); } - match ErrorStateCause::from_i32(error_state.cause) { - Some(ErrorStateCause::AuthFailed) => { - println!( - "Blocked: {}", - AuthFailed::from(error_state.auth_fail_reason.as_ref()) - ); - } + match error_state.cause() { #[cfg(target_os = "linux")] - Some(ErrorStateCause::SetFirewallPolicyError) => { - println!("Blocked: {}", error_state_to_string(error_state)); + cause @ talpid_types::tunnel::ErrorStateCause::SetFirewallPolicyError(_) => { + println!("Blocked: {}", cause); println!("Your kernel might be terribly out of date or missing nftables"); } - _ => println!("Blocked: {}", error_state_to_string(error_state)), - } -} - -fn error_state_to_string(error_state: &ErrorState) -> String { - use ErrorStateCause::*; - - let error_str = match ErrorStateCause::from_i32(error_state.cause).expect("unknown error cause") - { - AuthFailed => { - return if error_state.auth_fail_reason.is_empty() { - "Authentication with remote server failed".to_string() - } else { - format!( - "Authentication with remote server failed: {}", - error_state.auth_fail_reason - ) - }; - } - Ipv6Unavailable => "Failed to configure IPv6 because it's disabled in the platform", - SetFirewallPolicyError => { - return policy_error_to_string(error_state.policy_error.as_ref().unwrap()) - } - SetDnsError => "Failed to set system DNS server", - StartTunnelError => "Failed to start connection to remote server", - TunnelParameterError => { - return format!( - "Failure to generate tunnel parameters: {}", - tunnel_parameter_error_to_string(error_state.parameter_error) - ); - } - IsOffline => "This device is offline, no tunnels can be established", - #[cfg(target_os = "android")] - VpnPermissionDenied => "The Android VPN permission was denied when creating the tunnel", - #[cfg(target_os = "windows")] - SplitTunnelError => "The split tunneling module reported an error", - #[cfg(not(target_os = "android"))] - _ => unreachable!("unknown error cause"), - }; - - error_str.to_string() -} - -fn tunnel_parameter_error_to_string(parameter_error: i32) -> &'static str { - match GenerationError::from_i32(parameter_error).expect("unknown generation error") { - GenerationError::NoMatchingRelay => "Failure to select a matching tunnel relay", - GenerationError::NoMatchingBridgeRelay => "Failure to select a matching bridge relay", - GenerationError::NoWireguardKey => "No wireguard key available", - GenerationError::CustomTunnelHostResolutionError => { - "Can't resolve hostname for custom tunnel host" - } - } -} - -fn policy_error_to_string(policy_error: &FirewallPolicyError) -> String { - let cause = match FirewallPolicyErrorType::from_i32(policy_error.r#type) - .expect("unknown policy error") - { - FirewallPolicyErrorType::Generic => return "Failed to set firewall policy".to_string(), - FirewallPolicyErrorType::Locked => format!( - "An application prevented the firewall policy from being set: {} (pid {})", - policy_error.lock_name, policy_error.lock_pid - ), - }; - format!("Failed to set firewall policy: {}", cause) -} - -fn format_protocol(protocol: TransportProtocol) -> &'static str { - match protocol { - TransportProtocol::Udp => "UDP", - TransportProtocol::Tcp => "TCP", + cause => println!("Blocked: {}", cause), } } diff --git a/mullvad-cli/src/state.rs b/mullvad-cli/src/state.rs index f237689a3b..7b3dfdc955 100644 --- a/mullvad-cli/src/state.rs +++ b/mullvad-cli/src/state.rs @@ -4,9 +4,9 @@ use futures::{ SinkExt, }; use mullvad_management_interface::{ - types::{daemon_event::Event as EventType, TunnelState}, - ManagementServiceClient, + types::daemon_event::Event as EventType, ManagementServiceClient, }; +use mullvad_types::states::TunnelState; // Spawns a new task that listens for tunnel state changes and forwards it through the returned // channel. Panics if called from outside of the Tokio runtime. @@ -19,7 +19,9 @@ pub fn state_listen(mut rpc: ManagementServiceClient) -> Receiver<Result<TunnelS loop { let forward = match events.message().await { Ok(Some(event)) => match event.event.unwrap() { - EventType::TunnelState(new_state) => Ok(new_state), + EventType::TunnelState(new_state) => { + Ok(TunnelState::try_from(new_state).expect("invalid tunnel state")) + } _ => continue, }, Ok(None) => break, diff --git a/mullvad-management-interface/src/types.rs b/mullvad-management-interface/src/types.rs deleted file mode 100644 index 0f70d11a91..0000000000 --- a/mullvad-management-interface/src/types.rs +++ /dev/null @@ -1,1725 +0,0 @@ -pub use prost_types::{Duration, Timestamp}; - -use mullvad_types::relay_constraints::Constraint; -use std::convert::TryFrom; -use talpid_types::{net::wireguard, ErrorExt}; - -#[allow(clippy::derive_partial_eq_without_eq)] -mod proto { - tonic::include_proto!("mullvad_daemon.management_interface"); -} - -pub use proto::*; - -impl From<mullvad_types::location::GeoIpLocation> for GeoIpLocation { - fn from(geoip: mullvad_types::location::GeoIpLocation) -> GeoIpLocation { - GeoIpLocation { - ipv4: geoip.ipv4.map(|ip| ip.to_string()).unwrap_or_default(), - ipv6: geoip.ipv6.map(|ip| ip.to_string()).unwrap_or_default(), - country: geoip.country, - city: geoip.city.unwrap_or_default(), - latitude: geoip.latitude, - longitude: geoip.longitude, - mullvad_exit_ip: geoip.mullvad_exit_ip, - hostname: geoip.hostname.unwrap_or_default(), - bridge_hostname: geoip.bridge_hostname.unwrap_or_default(), - entry_hostname: geoip.entry_hostname.unwrap_or_default(), - obfuscator_hostname: geoip.obfuscator_hostname.unwrap_or_default(), - } - } -} - -impl From<talpid_types::net::TunnelEndpoint> for TunnelEndpoint { - fn from(endpoint: talpid_types::net::TunnelEndpoint) -> Self { - use talpid_types::net; - - TunnelEndpoint { - address: endpoint.endpoint.address.to_string(), - protocol: i32::from(TransportProtocol::from(endpoint.endpoint.protocol)), - tunnel_type: match endpoint.tunnel_type { - net::TunnelType::Wireguard => i32::from(TunnelType::Wireguard), - net::TunnelType::OpenVpn => i32::from(TunnelType::Openvpn), - }, - quantum_resistant: endpoint.quantum_resistant, - proxy: endpoint.proxy.map(|proxy_ep| ProxyEndpoint { - address: proxy_ep.endpoint.address.to_string(), - protocol: i32::from(TransportProtocol::from(proxy_ep.endpoint.protocol)), - proxy_type: match proxy_ep.proxy_type { - net::proxy::ProxyType::Shadowsocks => i32::from(ProxyType::Shadowsocks), - net::proxy::ProxyType::Custom => i32::from(ProxyType::Custom), - }, - }), - obfuscation: endpoint - .obfuscation - .map(|obfuscation_endpoint| ObfuscationEndpoint { - address: obfuscation_endpoint.endpoint.address.ip().to_string(), - port: u32::from(obfuscation_endpoint.endpoint.address.port()), - protocol: i32::from(TransportProtocol::from( - obfuscation_endpoint.endpoint.protocol, - )), - obfuscation_type: match obfuscation_endpoint.obfuscation_type { - net::ObfuscationType::Udp2Tcp => i32::from(ObfuscationType::Udp2tcp), - }, - }), - entry_endpoint: endpoint.entry_endpoint.map(|entry| Endpoint { - address: entry.address.to_string(), - protocol: i32::from(TransportProtocol::from(entry.protocol)), - }), - } - } -} - -impl From<mullvad_types::states::TunnelState> for TunnelState { - fn from(state: mullvad_types::states::TunnelState) -> Self { - use error_state::{ - firewall_policy_error::ErrorType as PolicyErrorType, Cause, FirewallPolicyError, - GenerationError, - }; - use mullvad_types::states::TunnelState as MullvadTunnelState; - - use talpid_types::tunnel as talpid_tunnel; - - let map_firewall_error = - |firewall_error: &talpid_tunnel::FirewallPolicyError| match firewall_error { - talpid_tunnel::FirewallPolicyError::Generic => FirewallPolicyError { - r#type: i32::from(PolicyErrorType::Generic), - ..Default::default() - }, - #[cfg(windows)] - talpid_tunnel::FirewallPolicyError::Locked(blocking_app) => { - let (lock_pid, lock_name) = match blocking_app { - Some(app) => (app.pid, app.name.clone()), - None => (0, "".to_string()), - }; - - FirewallPolicyError { - r#type: i32::from(PolicyErrorType::Locked), - lock_pid, - lock_name, - } - } - }; - - let state = match state { - MullvadTunnelState::Disconnected => { - tunnel_state::State::Disconnected(tunnel_state::Disconnected {}) - } - MullvadTunnelState::Connecting { endpoint, location } => { - tunnel_state::State::Connecting(tunnel_state::Connecting { - relay_info: Some(TunnelStateRelayInfo { - tunnel_endpoint: Some(TunnelEndpoint::from(endpoint)), - location: location.map(GeoIpLocation::from), - }), - }) - } - MullvadTunnelState::Connected { endpoint, location } => { - tunnel_state::State::Connected(tunnel_state::Connected { - relay_info: Some(TunnelStateRelayInfo { - tunnel_endpoint: Some(TunnelEndpoint::from(endpoint)), - location: location.map(GeoIpLocation::from), - }), - }) - } - MullvadTunnelState::Disconnecting(after_disconnect) => { - tunnel_state::State::Disconnecting(tunnel_state::Disconnecting { - after_disconnect: match after_disconnect { - talpid_tunnel::ActionAfterDisconnect::Nothing => { - i32::from(AfterDisconnect::Nothing) - } - talpid_tunnel::ActionAfterDisconnect::Block => { - i32::from(AfterDisconnect::Block) - } - talpid_tunnel::ActionAfterDisconnect::Reconnect => { - i32::from(AfterDisconnect::Reconnect) - } - }, - }) - } - MullvadTunnelState::Error(error_state) => { - tunnel_state::State::Error(tunnel_state::Error { - error_state: Some(ErrorState { - cause: match error_state.cause() { - talpid_tunnel::ErrorStateCause::AuthFailed(_) => { - i32::from(Cause::AuthFailed) - } - talpid_tunnel::ErrorStateCause::Ipv6Unavailable => { - i32::from(Cause::Ipv6Unavailable) - } - talpid_tunnel::ErrorStateCause::SetFirewallPolicyError(_) => { - i32::from(Cause::SetFirewallPolicyError) - } - talpid_tunnel::ErrorStateCause::SetDnsError => { - i32::from(Cause::SetDnsError) - } - talpid_tunnel::ErrorStateCause::StartTunnelError => { - i32::from(Cause::StartTunnelError) - } - talpid_tunnel::ErrorStateCause::TunnelParameterError(_) => { - i32::from(Cause::TunnelParameterError) - } - talpid_tunnel::ErrorStateCause::IsOffline => { - i32::from(Cause::IsOffline) - } - #[cfg(target_os = "android")] - talpid_tunnel::ErrorStateCause::VpnPermissionDenied => { - i32::from(Cause::VpnPermissionDenied) - } - #[cfg(target_os = "windows")] - talpid_tunnel::ErrorStateCause::SplitTunnelError => { - i32::from(Cause::SplitTunnelError) - } - }, - blocking_error: error_state.block_failure().map(map_firewall_error), - auth_fail_reason: if let talpid_tunnel::ErrorStateCause::AuthFailed( - reason, - ) = error_state.cause() - { - reason.clone().unwrap_or_default() - } else { - "".to_string() - }, - parameter_error: - if let talpid_tunnel::ErrorStateCause::TunnelParameterError(reason) = - error_state.cause() - { - match reason { - talpid_tunnel::ParameterGenerationError::NoMatchingRelay => { - i32::from(GenerationError::NoMatchingRelay) - } - talpid_tunnel::ParameterGenerationError::NoMatchingBridgeRelay => { - i32::from(GenerationError::NoMatchingBridgeRelay) - } - talpid_tunnel::ParameterGenerationError::NoWireguardKey => { - i32::from(GenerationError::NoWireguardKey) - } - talpid_tunnel::ParameterGenerationError::CustomTunnelHostResultionError => { - i32::from(GenerationError::CustomTunnelHostResolutionError) - } - } - } else { - 0 - }, - policy_error: - if let talpid_tunnel::ErrorStateCause::SetFirewallPolicyError(reason) = - error_state.cause() - { - Some(map_firewall_error(reason)) - } else { - None - }, - }), - }) - } - }; - - TunnelState { state: Some(state) } - } -} - -impl From<mullvad_types::device::Device> for Device { - fn from(device: mullvad_types::device::Device) -> Self { - Device { - id: device.id, - name: device.name, - pubkey: device.pubkey.as_bytes().to_vec(), - ports: device.ports.into_iter().map(DevicePort::from).collect(), - hijack_dns: device.hijack_dns, - created: Some(Timestamp { - seconds: device.created.timestamp(), - nanos: 0, - }), - } - } -} - -impl From<mullvad_types::device::DevicePort> for DevicePort { - fn from(port: mullvad_types::device::DevicePort) -> Self { - DevicePort { id: port.id } - } -} - -impl From<mullvad_types::device::DeviceState> for DeviceState { - fn from(state: mullvad_types::device::DeviceState) -> Self { - DeviceState { - state: device_state::State::from(&state) as i32, - device: state.into_device().map(|device| AccountAndDevice { - account_token: device.account_token, - device: Some(Device::from(device.device)), - }), - } - } -} - -impl From<&mullvad_types::device::DeviceState> for device_state::State { - fn from(state: &mullvad_types::device::DeviceState) -> Self { - use mullvad_types::device::DeviceState as MullvadState; - match state { - MullvadState::LoggedIn(_) => device_state::State::LoggedIn, - MullvadState::LoggedOut => device_state::State::LoggedOut, - MullvadState::Revoked => device_state::State::Revoked, - } - } -} - -impl From<mullvad_types::device::DeviceEvent> for DeviceEvent { - fn from(event: mullvad_types::device::DeviceEvent) -> Self { - DeviceEvent { - cause: device_event::Cause::from(event.cause) as i32, - new_state: Some(DeviceState::from(event.new_state)), - } - } -} - -impl From<mullvad_types::device::DeviceEventCause> for device_event::Cause { - fn from(cause: mullvad_types::device::DeviceEventCause) -> Self { - use mullvad_types::device::DeviceEventCause as MullvadEvent; - match cause { - MullvadEvent::LoggedIn => device_event::Cause::LoggedIn, - MullvadEvent::LoggedOut => device_event::Cause::LoggedOut, - MullvadEvent::Revoked => device_event::Cause::Revoked, - MullvadEvent::Updated => device_event::Cause::Updated, - MullvadEvent::RotatedKey => device_event::Cause::RotatedKey, - } - } -} - -impl From<mullvad_types::device::RemoveDeviceEvent> for RemoveDeviceEvent { - fn from(event: mullvad_types::device::RemoveDeviceEvent) -> Self { - RemoveDeviceEvent { - account_token: event.account_token, - new_device_list: event.new_devices.into_iter().map(Device::from).collect(), - } - } -} - -impl From<mullvad_types::device::AccountAndDevice> for AccountAndDevice { - fn from(device: mullvad_types::device::AccountAndDevice) -> Self { - AccountAndDevice { - account_token: device.account_token, - device: Some(Device::from(device.device)), - } - } -} - -impl From<Vec<mullvad_types::device::Device>> for DeviceList { - fn from(devices: Vec<mullvad_types::device::Device>) -> Self { - DeviceList { - devices: devices.into_iter().map(Device::from).collect(), - } - } -} - -impl From<mullvad_types::wireguard::PublicKey> for PublicKey { - fn from(public_key: mullvad_types::wireguard::PublicKey) -> Self { - PublicKey { - key: public_key.key.as_bytes().to_vec(), - created: Some(Timestamp { - seconds: public_key.created.timestamp(), - nanos: 0, - }), - } - } -} - -impl From<mullvad_types::version::AppVersionInfo> for AppVersionInfo { - fn from(version_info: mullvad_types::version::AppVersionInfo) -> Self { - Self { - supported: version_info.supported, - latest_stable: version_info.latest_stable, - latest_beta: version_info.latest_beta, - suggested_upgrade: version_info.suggested_upgrade.unwrap_or_default(), - } - } -} - -impl From<mullvad_types::ConnectionConfig> for ConnectionConfig { - fn from(config: mullvad_types::ConnectionConfig) -> Self { - Self { - config: Some(match config { - mullvad_types::ConnectionConfig::OpenVpn(config) => { - connection_config::Config::Openvpn(connection_config::OpenvpnConfig { - address: config.endpoint.address.to_string(), - protocol: i32::from(TransportProtocol::from(config.endpoint.protocol)), - username: config.username, - password: config.password, - }) - } - mullvad_types::ConnectionConfig::Wireguard(config) => { - connection_config::Config::Wireguard(connection_config::WireguardConfig { - tunnel: Some(connection_config::wireguard_config::TunnelConfig { - private_key: config.tunnel.private_key.to_bytes().to_vec(), - addresses: config - .tunnel - .addresses - .iter() - .map(|address| address.to_string()) - .collect(), - }), - peer: Some(connection_config::wireguard_config::PeerConfig { - public_key: config.peer.public_key.as_bytes().to_vec(), - allowed_ips: config - .peer - .allowed_ips - .iter() - .map(|address| address.to_string()) - .collect(), - endpoint: config.peer.endpoint.to_string(), - }), - ipv4_gateway: config.ipv4_gateway.to_string(), - ipv6_gateway: config - .ipv6_gateway - .as_ref() - .map(|address| address.to_string()) - .unwrap_or_default(), - }) - } - }), - } - } -} - -impl From<talpid_types::net::TransportProtocol> for TransportProtocol { - fn from(protocol: talpid_types::net::TransportProtocol) -> Self { - match protocol { - talpid_types::net::TransportProtocol::Udp => TransportProtocol::Udp, - talpid_types::net::TransportProtocol::Tcp => TransportProtocol::Tcp, - } - } -} - -impl From<talpid_types::net::IpVersion> for IpVersion { - fn from(version: talpid_types::net::IpVersion) -> Self { - match version { - talpid_types::net::IpVersion::V4 => Self::V4, - talpid_types::net::IpVersion::V6 => Self::V6, - } - } -} - -impl From<IpVersion> for IpVersionConstraint { - fn from(version: IpVersion) -> Self { - Self { - protocol: i32::from(version), - } - } -} - -impl From<mullvad_types::relay_constraints::TransportPort> for TransportPort { - fn from(port: mullvad_types::relay_constraints::TransportPort) -> Self { - TransportPort { - protocol: TransportProtocol::from(port.protocol) as i32, - port: port.port.map(u32::from).unwrap_or(0), - } - } -} - -impl - From< - mullvad_types::relay_constraints::Constraint< - mullvad_types::relay_constraints::LocationConstraint, - >, - > for RelayLocation -{ - fn from( - location: mullvad_types::relay_constraints::Constraint< - mullvad_types::relay_constraints::LocationConstraint, - >, - ) -> Self { - location - .option() - .map(RelayLocation::from) - .unwrap_or_default() - } -} - -impl From<mullvad_types::relay_constraints::LocationConstraint> for RelayLocation { - fn from(location: mullvad_types::relay_constraints::LocationConstraint) -> Self { - use mullvad_types::relay_constraints::LocationConstraint; - - match location { - LocationConstraint::Country(country) => Self { - country, - ..Default::default() - }, - LocationConstraint::City(country, city) => Self { - country, - city, - ..Default::default() - }, - LocationConstraint::Hostname(country, city, hostname) => Self { - country, - city, - hostname, - }, - } - } -} - -impl From<&mullvad_types::settings::Settings> for Settings { - fn from(settings: &mullvad_types::settings::Settings) -> Self { - #[cfg(windows)] - let split_tunnel = { - let mut converted_list = vec![]; - for path in settings.split_tunnel.apps.clone().iter() { - match path.as_path().as_os_str().to_str() { - Some(path) => converted_list.push(path.to_string()), - None => { - log::error!("failed to convert OS string: {:?}", path); - } - } - } - - Some(SplitTunnelSettings { - enable_exclusions: settings.split_tunnel.enable_exclusions, - apps: converted_list, - }) - }; - #[cfg(not(windows))] - let split_tunnel = None; - - Self { - relay_settings: Some(RelaySettings::from(settings.get_relay_settings())), - bridge_settings: Some(BridgeSettings::from(settings.bridge_settings.clone())), - bridge_state: Some(BridgeState::from(settings.get_bridge_state())), - allow_lan: settings.allow_lan, - block_when_disconnected: settings.block_when_disconnected, - auto_connect: settings.auto_connect, - tunnel_options: Some(TunnelOptions::from(&settings.tunnel_options)), - show_beta_releases: settings.show_beta_releases, - obfuscation_settings: Some(ObfuscationSettings::from(&settings.obfuscation_settings)), - split_tunnel, - } - } -} - -impl From<mullvad_types::relay_constraints::BridgeState> for BridgeState { - fn from(state: mullvad_types::relay_constraints::BridgeState) -> Self { - use mullvad_types::relay_constraints::BridgeState; - Self { - state: i32::from(match state { - BridgeState::Auto => bridge_state::State::Auto, - BridgeState::On => bridge_state::State::On, - BridgeState::Off => bridge_state::State::Off, - }), - } - } -} - -impl From<&mullvad_types::relay_constraints::ObfuscationSettings> for ObfuscationSettings { - fn from(settings: &mullvad_types::relay_constraints::ObfuscationSettings) -> Self { - use mullvad_types::relay_constraints::SelectedObfuscation; - let selected_obfuscation = i32::from(match settings.selected_obfuscation { - SelectedObfuscation::Auto => obfuscation_settings::SelectedObfuscation::Auto, - SelectedObfuscation::Off => obfuscation_settings::SelectedObfuscation::Off, - SelectedObfuscation::Udp2Tcp => obfuscation_settings::SelectedObfuscation::Udp2tcp, - }); - Self { - selected_obfuscation, - udp2tcp: Some(Udp2TcpObfuscationSettings::from(&settings.udp2tcp)), - } - } -} - -impl From<&mullvad_types::relay_constraints::Udp2TcpObfuscationSettings> - for Udp2TcpObfuscationSettings -{ - fn from(settings: &mullvad_types::relay_constraints::Udp2TcpObfuscationSettings) -> Self { - Self { - port: u32::from(settings.port.unwrap_or(0)), - } - } -} - -impl From<mullvad_types::relay_constraints::BridgeSettings> for BridgeSettings { - fn from(settings: mullvad_types::relay_constraints::BridgeSettings) -> Self { - use mullvad_types::relay_constraints::BridgeSettings as MullvadBridgeSettings; - use talpid_types::net as talpid_net; - - let settings = match settings { - MullvadBridgeSettings::Normal(constraints) => { - bridge_settings::Type::Normal(bridge_settings::BridgeConstraints { - location: constraints - .location - .clone() - .option() - .map(RelayLocation::from), - providers: convert_providers_constraint(&constraints.providers), - ownership: convert_ownership_constraint(&constraints.ownership) as i32, - }) - } - MullvadBridgeSettings::Custom(proxy_settings) => match proxy_settings { - talpid_net::openvpn::ProxySettings::Local(proxy_settings) => { - bridge_settings::Type::Local(bridge_settings::LocalProxySettings { - port: u32::from(proxy_settings.port), - peer: proxy_settings.peer.to_string(), - }) - } - talpid_net::openvpn::ProxySettings::Remote(proxy_settings) => { - bridge_settings::Type::Remote(bridge_settings::RemoteProxySettings { - address: proxy_settings.address.to_string(), - auth: proxy_settings.auth.as_ref().map(|auth| { - bridge_settings::RemoteProxyAuth { - username: auth.username.clone(), - password: auth.password.clone(), - } - }), - }) - } - talpid_net::openvpn::ProxySettings::Shadowsocks(proxy_settings) => { - bridge_settings::Type::Shadowsocks(bridge_settings::ShadowsocksProxySettings { - peer: proxy_settings.peer.to_string(), - password: proxy_settings.password.clone(), - cipher: proxy_settings.cipher, - }) - } - }, - }; - - BridgeSettings { - r#type: Some(settings), - } - } -} - -impl From<mullvad_types::relay_constraints::RelaySettings> for RelaySettings { - fn from(settings: mullvad_types::relay_constraints::RelaySettings) -> Self { - use mullvad_types::relay_constraints::RelaySettings as MullvadRelaySettings; - use talpid_types::net as talpid_net; - - let endpoint = match settings { - MullvadRelaySettings::CustomTunnelEndpoint(endpoint) => { - relay_settings::Endpoint::Custom(CustomRelaySettings { - host: endpoint.host, - config: Some(ConnectionConfig::from(endpoint.config)), - }) - } - MullvadRelaySettings::Normal(constraints) => { - relay_settings::Endpoint::Normal(NormalRelaySettings { - location: constraints.location.option().map(RelayLocation::from), - providers: convert_providers_constraint(&constraints.providers), - ownership: convert_ownership_constraint(&constraints.ownership) as i32, - tunnel_type: match constraints.tunnel_protocol { - Constraint::Any => None, - Constraint::Only(talpid_net::TunnelType::Wireguard) => { - Some(TunnelType::Wireguard) - } - Constraint::Only(talpid_net::TunnelType::OpenVpn) => { - Some(TunnelType::Openvpn) - } - } - .map(|tunnel_type| TunnelTypeConstraint { - tunnel_type: i32::from(tunnel_type), - }), - - wireguard_constraints: Some(WireguardConstraints { - port: u32::from(constraints.wireguard_constraints.port.unwrap_or(0)), - ip_version: constraints - .wireguard_constraints - .ip_version - .option() - .map(IpVersion::from) - .map(IpVersionConstraint::from), - use_multihop: constraints.wireguard_constraints.use_multihop, - entry_location: constraints - .wireguard_constraints - .entry_location - .option() - .map(RelayLocation::from), - }), - - openvpn_constraints: Some(OpenvpnConstraints { - port: constraints - .openvpn_constraints - .port - .option() - .map(TransportPort::from), - }), - }) - } - }; - - Self { - endpoint: Some(endpoint), - } - } -} - -impl From<&mullvad_types::settings::DnsOptions> for DnsOptions { - fn from(options: &mullvad_types::settings::DnsOptions) -> Self { - DnsOptions { - state: match options.state { - mullvad_types::settings::DnsState::Default => dns_options::DnsState::Default as i32, - mullvad_types::settings::DnsState::Custom => dns_options::DnsState::Custom as i32, - }, - default_options: Some(DefaultDnsOptions { - block_ads: options.default_options.block_ads, - block_trackers: options.default_options.block_trackers, - block_malware: options.default_options.block_malware, - block_adult_content: options.default_options.block_adult_content, - block_gambling: options.default_options.block_gambling, - }), - custom_options: Some(CustomDnsOptions { - addresses: options - .custom_options - .addresses - .iter() - .map(|addr| addr.to_string()) - .collect(), - }), - } - } -} - -impl From<&mullvad_types::settings::TunnelOptions> for TunnelOptions { - fn from(options: &mullvad_types::settings::TunnelOptions) -> Self { - Self { - openvpn: Some(tunnel_options::OpenvpnOptions { - mssfix: u32::from(options.openvpn.mssfix.unwrap_or_default()), - }), - wireguard: Some(tunnel_options::WireguardOptions { - mtu: u32::from(options.wireguard.options.mtu.unwrap_or_default()), - rotation_interval: options.wireguard.rotation_interval.map(|ivl| { - prost_types::Duration::try_from(std::time::Duration::from(ivl)) - .expect("Failed to convert std::time::Duration to prost_types::Duration for tunnel_options.wireguard.rotation_interval") - }), - #[cfg(windows)] - use_wireguard_nt: options.wireguard.options.use_wireguard_nt, - #[cfg(not(windows))] - use_wireguard_nt: false, - use_pq_safe_psk: options.wireguard.options.use_pq_safe_psk, - }), - generic: Some(tunnel_options::GenericOptions { - enable_ipv6: options.generic.enable_ipv6, - }), - #[cfg(not(target_os = "android"))] - dns_options: Some(DnsOptions::from(&options.dns_options)), - #[cfg(target_os = "android")] - dns_options: None, - } - } -} - -impl From<mullvad_types::relay_list::RelayList> for RelayList { - fn from(relay_list: mullvad_types::relay_list::RelayList) -> Self { - let mut proto_list = RelayList { - countries: vec![], - openvpn: Some(OpenVpnEndpointData::from(relay_list.openvpn)), - bridge: Some(BridgeEndpointData::from(relay_list.bridge)), - wireguard: Some(WireguardEndpointData::from(relay_list.wireguard)), - }; - proto_list.countries = relay_list - .countries - .into_iter() - .map(RelayListCountry::from) - .collect(); - proto_list - } -} - -impl From<mullvad_types::relay_list::OpenVpnEndpointData> for OpenVpnEndpointData { - fn from(openvpn: mullvad_types::relay_list::OpenVpnEndpointData) -> Self { - OpenVpnEndpointData { - endpoints: openvpn - .ports - .into_iter() - .map(|endpoint| OpenVpnEndpoint { - port: u32::from(endpoint.port), - protocol: TransportProtocol::from(endpoint.protocol) as i32, - }) - .collect(), - } - } -} - -impl From<mullvad_types::relay_list::BridgeEndpointData> for BridgeEndpointData { - fn from(bridge: mullvad_types::relay_list::BridgeEndpointData) -> Self { - BridgeEndpointData { - shadowsocks: bridge - .shadowsocks - .into_iter() - .map(|endpoint| ShadowsocksEndpointData { - port: u32::from(endpoint.port), - cipher: endpoint.cipher, - password: endpoint.password, - protocol: TransportProtocol::from(endpoint.protocol) as i32, - }) - .collect(), - } - } -} - -impl From<mullvad_types::relay_list::WireguardEndpointData> for WireguardEndpointData { - fn from(wireguard: mullvad_types::relay_list::WireguardEndpointData) -> Self { - WireguardEndpointData { - port_ranges: wireguard - .port_ranges - .into_iter() - .map(|(first, last)| PortRange { - first: u32::from(first), - last: u32::from(last), - }) - .collect(), - ipv4_gateway: wireguard.ipv4_gateway.to_string(), - ipv6_gateway: wireguard.ipv6_gateway.to_string(), - udp2tcp_ports: wireguard.udp2tcp_ports.into_iter().map(u32::from).collect(), - } - } -} - -impl From<mullvad_types::relay_list::RelayListCountry> for RelayListCountry { - fn from(country: mullvad_types::relay_list::RelayListCountry) -> Self { - let mut proto_country = RelayListCountry { - name: country.name, - code: country.code, - cities: Vec::with_capacity(country.cities.len()), - }; - - for city in country.cities.into_iter() { - proto_country.cities.push(RelayListCity { - name: city.name, - code: city.code, - latitude: city.latitude, - longitude: city.longitude, - relays: city.relays.into_iter().map(Relay::from).collect(), - }); - } - - proto_country - } -} - -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(), - ipv6_addr_in: relay - .ipv6_addr_in - .map(|addr| addr.to_string()) - .unwrap_or_default(), - include_in_country: relay.include_in_country, - active: relay.active, - owned: relay.owned, - provider: relay.provider, - weight: relay.weight, - 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, - city: location.city, - city_code: location.city_code, - latitude: location.latitude, - longitude: location.longitude, - }), - } - } -} - -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 { - TransportProtocol::Udp => talpid_types::net::TransportProtocol::Udp, - TransportProtocol::Tcp => talpid_types::net::TransportProtocol::Tcp, - } - } -} - -#[derive(Debug)] -pub enum FromProtobufTypeError { - InvalidArgument(&'static str), -} - -impl TryFrom<Device> for mullvad_types::device::Device { - type Error = FromProtobufTypeError; - - fn try_from(device: Device) -> Result<Self, Self::Error> { - Ok(mullvad_types::device::Device { - id: device.id, - name: device.name, - pubkey: bytes_to_pubkey(&device.pubkey)?, - ports: device - .ports - .into_iter() - .map(mullvad_types::device::DevicePort::from) - .collect(), - hijack_dns: device.hijack_dns, - created: chrono::DateTime::from_utc( - chrono::NaiveDateTime::from_timestamp( - device - .created - .ok_or(FromProtobufTypeError::InvalidArgument( - "missing 'created' field", - ))? - .seconds, - 0, - ), - chrono::Utc, - ), - }) - } -} - -impl From<DevicePort> for mullvad_types::device::DevicePort { - fn from(port: DevicePort) -> Self { - mullvad_types::device::DevicePort { id: port.id } - } -} - -impl TryFrom<&WireguardConstraints> for mullvad_types::relay_constraints::WireguardConstraints { - type Error = FromProtobufTypeError; - - fn try_from( - constraints: &WireguardConstraints, - ) -> Result<mullvad_types::relay_constraints::WireguardConstraints, Self::Error> { - use mullvad_types::relay_constraints as mullvad_constraints; - use talpid_types::net; - - let ip_version = match &constraints.ip_version { - Some(constraint) => match IpVersion::from_i32(constraint.protocol) { - Some(IpVersion::V4) => Some(net::IpVersion::V4), - Some(IpVersion::V6) => Some(net::IpVersion::V6), - None => { - return Err(FromProtobufTypeError::InvalidArgument( - "invalid ip protocol version", - )) - } - }, - None => None, - }; - - Ok(mullvad_constraints::WireguardConstraints { - port: if constraints.port == 0 { - Constraint::Any - } else { - Constraint::Only(constraints.port as u16) - }, - ip_version: Constraint::from(ip_version), - use_multihop: constraints.use_multihop, - entry_location: constraints - .entry_location - .clone() - .map(Constraint::<mullvad_types::relay_constraints::LocationConstraint>::from) - .unwrap_or(Constraint::Any), - }) - } -} - -impl TryFrom<&OpenvpnConstraints> for mullvad_types::relay_constraints::OpenVpnConstraints { - type Error = FromProtobufTypeError; - - fn try_from( - constraints: &OpenvpnConstraints, - ) -> Result<mullvad_types::relay_constraints::OpenVpnConstraints, Self::Error> { - use mullvad_types::relay_constraints as mullvad_constraints; - - Ok(mullvad_constraints::OpenVpnConstraints { - port: Constraint::from(match &constraints.port { - Some(port) => Some(mullvad_constraints::TransportPort::try_from(port.clone())?), - None => None, - }), - }) - } -} - -impl TryFrom<RelaySettings> for mullvad_types::relay_constraints::RelaySettings { - type Error = FromProtobufTypeError; - - fn try_from( - settings: RelaySettings, - ) -> Result<mullvad_types::relay_constraints::RelaySettings, Self::Error> { - use mullvad_types::{relay_constraints as mullvad_constraints, CustomTunnelEndpoint}; - use talpid_types::net; - - let update_value = settings - .endpoint - .ok_or(FromProtobufTypeError::InvalidArgument( - "missing relay settings", - ))?; - - match update_value { - relay_settings::Endpoint::Custom(settings) => { - let config = settings - .config - .ok_or(FromProtobufTypeError::InvalidArgument( - "missing relay connection config", - ))?; - let config = mullvad_types::ConnectionConfig::try_from(config)?; - Ok(mullvad_constraints::RelaySettings::CustomTunnelEndpoint( - CustomTunnelEndpoint { - host: settings.host, - config, - }, - )) - } - - relay_settings::Endpoint::Normal(settings) => { - let location = settings - .location - .map(Constraint::<mullvad_types::relay_constraints::LocationConstraint>::from) - .unwrap_or(Constraint::Any); - let providers = try_providers_constraint_from_proto(&settings.providers)?; - let ownership = try_ownership_constraint_from_i32(settings.ownership)?; - let tunnel_protocol = settings - .tunnel_type - .map(Constraint::<net::TunnelType>::try_from) - .transpose()? - .unwrap_or(Constraint::Any); - let openvpn_constraints = - mullvad_constraints::OpenVpnConstraints::try_from( - &settings.openvpn_constraints.ok_or( - FromProtobufTypeError::InvalidArgument("missing openvpn constraints"), - )?, - )?; - let wireguard_constraints = mullvad_constraints::WireguardConstraints::try_from( - &settings.wireguard_constraints.ok_or( - FromProtobufTypeError::InvalidArgument("missing wireguard constraints"), - )?, - )?; - - Ok(mullvad_constraints::RelaySettings::Normal( - mullvad_constraints::RelayConstraints { - location, - providers, - ownership, - tunnel_protocol, - wireguard_constraints, - openvpn_constraints, - }, - )) - } - } - } -} - -impl TryFrom<RelaySettingsUpdate> for mullvad_types::relay_constraints::RelaySettingsUpdate { - type Error = FromProtobufTypeError; - - fn try_from( - settings: RelaySettingsUpdate, - ) -> Result<mullvad_types::relay_constraints::RelaySettingsUpdate, Self::Error> { - use mullvad_types::{relay_constraints as mullvad_constraints, CustomTunnelEndpoint}; - use talpid_types::net; - - let update_value = settings - .r#type - .ok_or(FromProtobufTypeError::InvalidArgument( - "missing relay settings", - ))?; - - match update_value { - relay_settings_update::Type::Custom(settings) => { - let config = settings - .config - .ok_or(FromProtobufTypeError::InvalidArgument( - "missing relay connection config", - ))?; - let config = mullvad_types::ConnectionConfig::try_from(config)?; - Ok( - mullvad_constraints::RelaySettingsUpdate::CustomTunnelEndpoint( - CustomTunnelEndpoint { - host: settings.host, - config, - }, - ), - ) - } - - relay_settings_update::Type::Normal(settings) => { - // If `location` isn't provided, no changes are made. - // If `location` is provided, but is an empty vector, - // then the constraint is set to `Constraint::Any`. - let location = settings - .location - .map(Constraint::<mullvad_types::relay_constraints::LocationConstraint>::from); - let providers = if let Some(ref provider_update) = settings.providers { - Some(try_providers_constraint_from_proto( - &provider_update.providers, - )?) - } else { - None - }; - let ownership = if let Some(ref ownership_update) = settings.ownership { - Some(try_ownership_constraint_from_i32( - ownership_update.ownership, - )?) - } else { - None - }; - let tunnel_protocol = if let Some(update) = settings.tunnel_type { - Some( - update - .tunnel_type - .map(Constraint::<net::TunnelType>::try_from) - .transpose()? - .unwrap_or(Constraint::Any), - ) - } else { - None - }; - let openvpn_constraints = - if let Some(ref constraints) = settings.openvpn_constraints { - Some(mullvad_constraints::OpenVpnConstraints::try_from( - constraints, - )?) - } else { - None - }; - let wireguard_constraints = - if let Some(ref constraints) = settings.wireguard_constraints { - Some(mullvad_constraints::WireguardConstraints::try_from( - constraints, - )?) - } else { - None - }; - Ok(mullvad_constraints::RelaySettingsUpdate::Normal( - mullvad_constraints::RelayConstraintsUpdate { - location, - providers, - ownership, - tunnel_protocol, - wireguard_constraints, - openvpn_constraints, - }, - )) - } - } - } -} - -impl TryFrom<TunnelTypeConstraint> for Constraint<talpid_types::net::TunnelType> { - type Error = FromProtobufTypeError; - - fn try_from( - tunnel_type: TunnelTypeConstraint, - ) -> Result<Constraint<talpid_types::net::TunnelType>, Self::Error> { - match TunnelType::from_i32(tunnel_type.tunnel_type) { - Some(TunnelType::Openvpn) => { - Ok(Constraint::Only(talpid_types::net::TunnelType::OpenVpn)) - } - Some(TunnelType::Wireguard) => { - Ok(Constraint::Only(talpid_types::net::TunnelType::Wireguard)) - } - None => Err(FromProtobufTypeError::InvalidArgument( - "invalid tunnel protocol", - )), - } - } -} - -impl TryFrom<ConnectionConfig> for mullvad_types::ConnectionConfig { - type Error = FromProtobufTypeError; - - fn try_from(config: ConnectionConfig) -> Result<mullvad_types::ConnectionConfig, Self::Error> { - use talpid_types::net::{self, openvpn}; - - let config = config.config.ok_or(FromProtobufTypeError::InvalidArgument( - "missing connection config", - ))?; - match config { - connection_config::Config::Openvpn(config) => { - let address = match config.address.parse() { - Ok(address) => address, - Err(_) => { - return Err(FromProtobufTypeError::InvalidArgument("invalid address")) - } - }; - - Ok(mullvad_types::ConnectionConfig::OpenVpn( - openvpn::ConnectionConfig { - endpoint: net::Endpoint { - address, - protocol: try_transport_protocol_from_i32(config.protocol)?, - }, - username: config.username, - password: config.password, - }, - )) - } - connection_config::Config::Wireguard(config) => { - let tunnel = config.tunnel.ok_or(FromProtobufTypeError::InvalidArgument( - "missing tunnel config", - ))?; - - // Copy the private key to an array - if tunnel.private_key.len() != 32 { - return Err(FromProtobufTypeError::InvalidArgument( - "invalid private key", - )); - } - - let mut private_key = [0; 32]; - let buffer = &tunnel.private_key[..private_key.len()]; - private_key.copy_from_slice(buffer); - - let peer = config.peer.ok_or(FromProtobufTypeError::InvalidArgument( - "missing peer config", - ))?; - - let public_key = bytes_to_pubkey(&peer.public_key)?; - - let ipv4_gateway = match config.ipv4_gateway.parse() { - Ok(address) => address, - Err(_) => { - return Err(FromProtobufTypeError::InvalidArgument( - "invalid IPv4 gateway", - )) - } - }; - let ipv6_gateway = if !config.ipv6_gateway.is_empty() { - let address = match config.ipv6_gateway.parse() { - Ok(address) => address, - Err(_) => { - return Err(FromProtobufTypeError::InvalidArgument( - "invalid IPv6 gateway", - )) - } - }; - Some(address) - } else { - None - }; - - let endpoint = match peer.endpoint.parse() { - Ok(address) => address, - Err(_) => { - return Err(FromProtobufTypeError::InvalidArgument( - "invalid peer address", - )) - } - }; - - let mut tunnel_addresses = Vec::new(); - for address in tunnel.addresses { - let address = address - .parse() - .map_err(|_| FromProtobufTypeError::InvalidArgument("invalid address"))?; - tunnel_addresses.push(address); - } - - let mut allowed_ips = Vec::new(); - for address in peer.allowed_ips { - let address = address - .parse() - .map_err(|_| FromProtobufTypeError::InvalidArgument("invalid address"))?; - allowed_ips.push(address); - } - - Ok(mullvad_types::ConnectionConfig::Wireguard( - wireguard::ConnectionConfig { - tunnel: wireguard::TunnelConfig { - private_key: wireguard::PrivateKey::from(private_key), - addresses: tunnel_addresses, - }, - peer: wireguard::PeerConfig { - public_key, - allowed_ips, - endpoint, - psk: None, - }, - exit_peer: None, - ipv4_gateway, - ipv6_gateway, - }, - )) - } - } - } -} - -fn bytes_to_pubkey(bytes: &[u8]) -> Result<wireguard::PublicKey, FromProtobufTypeError> { - if bytes.len() != 32 { - return Err(FromProtobufTypeError::InvalidArgument("invalid public key")); - } - let mut public_key = [0; 32]; - public_key.copy_from_slice(&bytes[..32]); - Ok(wireguard::PublicKey::from(public_key)) -} - -impl From<RelayLocation> for Constraint<mullvad_types::relay_constraints::LocationConstraint> { - fn from(location: RelayLocation) -> Self { - use mullvad_types::relay_constraints::LocationConstraint; - - if !location.hostname.is_empty() { - Constraint::Only(LocationConstraint::Hostname( - location.country, - location.city, - location.hostname, - )) - } else if !location.city.is_empty() { - Constraint::Only(LocationConstraint::City(location.country, location.city)) - } else if !location.country.is_empty() { - Constraint::Only(LocationConstraint::Country(location.country)) - } else { - Constraint::Any - } - } -} - -impl TryFrom<BridgeSettings> for mullvad_types::relay_constraints::BridgeSettings { - type Error = FromProtobufTypeError; - - fn try_from(settings: BridgeSettings) -> Result<Self, Self::Error> { - use mullvad_types::relay_constraints as mullvad_constraints; - use talpid_types::net as talpid_net; - - match settings - .r#type - .ok_or(FromProtobufTypeError::InvalidArgument( - "no settings provided", - ))? { - bridge_settings::Type::Normal(constraints) => { - let location = match constraints.location { - None => Constraint::Any, - Some(location) => { - Constraint::<mullvad_constraints::LocationConstraint>::from(location) - } - }; - let providers = try_providers_constraint_from_proto(&constraints.providers)?; - let ownership = try_ownership_constraint_from_i32(constraints.ownership)?; - - Ok(mullvad_constraints::BridgeSettings::Normal( - mullvad_constraints::BridgeConstraints { - location, - providers, - ownership, - }, - )) - } - bridge_settings::Type::Local(proxy_settings) => { - let peer = proxy_settings.peer.parse().map_err(|_| { - FromProtobufTypeError::InvalidArgument("failed to parse peer address") - })?; - let proxy_settings = talpid_net::openvpn::ProxySettings::Local( - talpid_net::openvpn::LocalProxySettings { - port: proxy_settings.port as u16, - peer, - }, - ); - Ok(mullvad_constraints::BridgeSettings::Custom(proxy_settings)) - } - bridge_settings::Type::Remote(proxy_settings) => { - let address = proxy_settings.address.parse().map_err(|_| { - FromProtobufTypeError::InvalidArgument("failed to parse IP address") - })?; - let auth = proxy_settings - .auth - .map(|auth| talpid_net::openvpn::ProxyAuth { - username: auth.username, - password: auth.password, - }); - let proxy_settings = talpid_net::openvpn::ProxySettings::Remote( - talpid_net::openvpn::RemoteProxySettings { address, auth }, - ); - Ok(mullvad_constraints::BridgeSettings::Custom(proxy_settings)) - } - bridge_settings::Type::Shadowsocks(proxy_settings) => { - let peer = proxy_settings.peer.parse().map_err(|_| { - FromProtobufTypeError::InvalidArgument("failed to parse peer address") - })?; - let proxy_settings = talpid_net::openvpn::ProxySettings::Shadowsocks( - talpid_net::openvpn::ShadowsocksProxySettings { - peer, - password: proxy_settings.password, - cipher: proxy_settings.cipher, - }, - ); - Ok(mullvad_constraints::BridgeSettings::Custom(proxy_settings)) - } - } - } -} - -impl TryFrom<ObfuscationSettings> for mullvad_types::relay_constraints::ObfuscationSettings { - type Error = FromProtobufTypeError; - - fn try_from(settings: ObfuscationSettings) -> Result<Self, Self::Error> { - use mullvad_types::relay_constraints::SelectedObfuscation; - use obfuscation_settings::SelectedObfuscation as IpcSelectedObfuscation; - let selected_obfuscation = - match IpcSelectedObfuscation::from_i32(settings.selected_obfuscation) { - Some(IpcSelectedObfuscation::Auto) => SelectedObfuscation::Auto, - Some(IpcSelectedObfuscation::Off) => SelectedObfuscation::Off, - Some(IpcSelectedObfuscation::Udp2tcp) => SelectedObfuscation::Udp2Tcp, - None => { - return Err(FromProtobufTypeError::InvalidArgument( - "invalid selected obfuscator", - )); - } - }; - - let udp2tcp = match settings.udp2tcp { - Some(settings) => { - mullvad_types::relay_constraints::Udp2TcpObfuscationSettings::try_from(&settings)? - } - None => { - return Err(FromProtobufTypeError::InvalidArgument( - "invalid selected obfuscator", - )); - } - }; - - Ok(Self { - selected_obfuscation, - udp2tcp, - }) - } -} - -impl TryFrom<&Udp2TcpObfuscationSettings> - for mullvad_types::relay_constraints::Udp2TcpObfuscationSettings -{ - type Error = FromProtobufTypeError; - - fn try_from(settings: &Udp2TcpObfuscationSettings) -> Result<Self, Self::Error> { - Ok(Self { - port: if settings.port == 0 { - Constraint::Any - } else { - Constraint::Only(settings.port as u16) - }, - }) - } -} - -impl TryFrom<BridgeState> for mullvad_types::relay_constraints::BridgeState { - type Error = FromProtobufTypeError; - - fn try_from(state: BridgeState) -> Result<Self, Self::Error> { - match bridge_state::State::from_i32(state.state) { - Some(bridge_state::State::Auto) => { - Ok(mullvad_types::relay_constraints::BridgeState::Auto) - } - Some(bridge_state::State::On) => Ok(mullvad_types::relay_constraints::BridgeState::On), - Some(bridge_state::State::Off) => { - Ok(mullvad_types::relay_constraints::BridgeState::Off) - } - None => Err(FromProtobufTypeError::InvalidArgument( - "invalid bridge state", - )), - } - } -} - -impl TryFrom<TunnelOptions> for mullvad_types::settings::TunnelOptions { - type Error = FromProtobufTypeError; - - fn try_from(options: TunnelOptions) -> Result<Self, Self::Error> { - use talpid_types::net; - - let openvpn_options = options - .openvpn - .ok_or(FromProtobufTypeError::InvalidArgument( - "missing openvpn tunnel options", - ))?; - let wireguard_options = options - .wireguard - .ok_or(FromProtobufTypeError::InvalidArgument( - "missing openvpn tunnel options", - ))?; - let generic_options = options - .generic - .ok_or(FromProtobufTypeError::InvalidArgument( - "missing generic tunnel options", - ))?; - let dns_options = options - .dns_options - .ok_or(FromProtobufTypeError::InvalidArgument( - "missing tunnel DNS options", - ))?; - - Ok(Self { - openvpn: net::openvpn::TunnelOptions { - mssfix: if openvpn_options.mssfix != 0 { - Some(openvpn_options.mssfix as u16) - } else { - None - }, - }, - wireguard: mullvad_types::wireguard::TunnelOptions { - options: net::wireguard::TunnelOptions { - mtu: if wireguard_options.mtu != 0 { - Some(wireguard_options.mtu as u16) - } else { - None - }, - use_pq_safe_psk: wireguard_options.use_pq_safe_psk, - #[cfg(windows)] - use_wireguard_nt: wireguard_options.use_wireguard_nt, - }, - rotation_interval: wireguard_options - .rotation_interval - .map(std::time::Duration::try_from) - .transpose() - .map_err(|_| FromProtobufTypeError::InvalidArgument("invalid duration"))? - .map(mullvad_types::wireguard::RotationInterval::try_from) - .transpose() - .map_err(|error: mullvad_types::wireguard::RotationIntervalError| { - log::error!( - "{}", - error.display_chain_with_msg("Invalid rotation interval") - ); - FromProtobufTypeError::InvalidArgument("invalid rotation interval") - })?, - }, - generic: net::GenericTunnelOptions { - enable_ipv6: generic_options.enable_ipv6, - }, - #[cfg(not(target_os = "android"))] - dns_options: mullvad_types::settings::DnsOptions::try_from(dns_options)?, - }) - } -} - -impl TryFrom<DnsOptions> for mullvad_types::settings::DnsOptions { - type Error = FromProtobufTypeError; - - fn try_from(options: DnsOptions) -> Result<Self, Self::Error> { - use mullvad_types::settings::{ - CustomDnsOptions as MullvadCustomDnsOptions, - DefaultDnsOptions as MullvadDefaultDnsOptions, DnsOptions as MullvadDnsOptions, - DnsState as MullvadDnsState, - }; - - let state = match dns_options::DnsState::from_i32(options.state) { - Some(dns_options::DnsState::Default) => MullvadDnsState::Default, - Some(dns_options::DnsState::Custom) => MullvadDnsState::Custom, - None => { - return Err(FromProtobufTypeError::InvalidArgument( - "invalid DNS options state", - )) - } - }; - - let default_options = - options - .default_options - .ok_or(FromProtobufTypeError::InvalidArgument( - "missing default DNS options", - ))?; - let custom_options = - options - .custom_options - .ok_or(FromProtobufTypeError::InvalidArgument( - "missing default DNS options", - ))?; - - Ok(MullvadDnsOptions { - state, - default_options: MullvadDefaultDnsOptions { - block_ads: default_options.block_ads, - block_trackers: default_options.block_trackers, - block_malware: default_options.block_malware, - block_adult_content: default_options.block_adult_content, - block_gambling: default_options.block_gambling, - }, - custom_options: MullvadCustomDnsOptions { - addresses: custom_options - .addresses - .into_iter() - .map(|addr| { - addr.parse().map_err(|_| { - FromProtobufTypeError::InvalidArgument("invalid IP address") - }) - }) - .collect::<Result<Vec<_>, _>>()?, - }, - }) - } -} - -impl TryFrom<TransportPort> for mullvad_types::relay_constraints::TransportPort { - type Error = FromProtobufTypeError; - - fn try_from(port: TransportPort) -> Result<Self, Self::Error> { - Ok(mullvad_types::relay_constraints::TransportPort { - protocol: try_transport_protocol_from_i32(port.protocol)?, - port: if port.port == 0 { - Constraint::Any - } else { - Constraint::Only(port.port as u16) - }, - }) - } -} - -fn try_transport_protocol_from_i32( - protocol: i32, -) -> Result<talpid_types::net::TransportProtocol, FromProtobufTypeError> { - Ok(TransportProtocol::from_i32(protocol) - .ok_or(FromProtobufTypeError::InvalidArgument( - "invalid transport protocol", - ))? - .into()) -} - -pub fn try_providers_constraint_from_proto( - providers: &[String], -) -> Result<Constraint<mullvad_types::relay_constraints::Providers>, FromProtobufTypeError> { - if !providers.is_empty() { - Ok(Constraint::Only( - mullvad_types::relay_constraints::Providers::new(providers.iter().cloned()).map_err( - |_| FromProtobufTypeError::InvalidArgument("must specify at least one provider"), - )?, - )) - } else { - Ok(Constraint::Any) - } -} - -pub fn try_ownership_constraint_from_i32( - ownership: i32, -) -> Result<Constraint<mullvad_types::relay_constraints::Ownership>, FromProtobufTypeError> { - Ownership::from_i32(ownership) - .map(ownership_constraint_from_proto) - .ok_or(FromProtobufTypeError::InvalidArgument( - "invalid ownership argument", - )) -} - -pub fn ownership_constraint_from_proto( - ownership: Ownership, -) -> Constraint<mullvad_types::relay_constraints::Ownership> { - use mullvad_types::relay_constraints::Ownership as MullvadOwnership; - - match ownership { - Ownership::Any => Constraint::Any, - Ownership::MullvadOwned => Constraint::Only(MullvadOwnership::MullvadOwned), - Ownership::Rented => Constraint::Only(MullvadOwnership::Rented), - } -} - -fn convert_providers_constraint( - providers: &Constraint<mullvad_types::relay_constraints::Providers>, -) -> Vec<String> { - match providers.as_ref() { - Constraint::Any => vec![], - Constraint::Only(providers) => Vec::from(providers.clone()), - } -} - -fn convert_ownership_constraint( - ownership: &Constraint<mullvad_types::relay_constraints::Ownership>, -) -> Ownership { - use mullvad_types::relay_constraints::Ownership as MullvadOwnership; - - match ownership.as_ref() { - Constraint::Any => Ownership::Any, - Constraint::Only(ownership) => match ownership { - MullvadOwnership::MullvadOwned => Ownership::MullvadOwned, - MullvadOwnership::Rented => Ownership::Rented, - }, - } -} - -impl From<FromProtobufTypeError> for crate::Status { - fn from(err: FromProtobufTypeError) -> Self { - match err { - FromProtobufTypeError::InvalidArgument(err) => crate::Status::invalid_argument(err), - } - } -} - -/// 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/custom_tunnel.rs b/mullvad-management-interface/src/types/conversions/custom_tunnel.rs new file mode 100644 index 0000000000..950f8f920c --- /dev/null +++ b/mullvad-management-interface/src/types/conversions/custom_tunnel.rs @@ -0,0 +1,152 @@ +use crate::types::{ + conversions::{bytes_to_privkey, bytes_to_pubkey, option_from_proto_string}, + proto, FromProtobufTypeError, +}; +use talpid_types::net::wireguard; + +impl TryFrom<proto::ConnectionConfig> for mullvad_types::ConnectionConfig { + type Error = FromProtobufTypeError; + + fn try_from( + config: proto::ConnectionConfig, + ) -> Result<mullvad_types::ConnectionConfig, Self::Error> { + use talpid_types::net::{self, openvpn}; + + let config = config.config.ok_or(FromProtobufTypeError::InvalidArgument( + "missing connection config", + ))?; + match config { + proto::connection_config::Config::Openvpn(config) => { + let address = match config.address.parse() { + Ok(address) => address, + Err(_) => { + return Err(FromProtobufTypeError::InvalidArgument("invalid address")) + } + }; + + Ok(mullvad_types::ConnectionConfig::OpenVpn( + openvpn::ConnectionConfig { + endpoint: net::Endpoint { + address, + protocol: super::net::try_transport_protocol_from_i32(config.protocol)?, + }, + username: config.username, + password: config.password, + }, + )) + } + proto::connection_config::Config::Wireguard(config) => { + let tunnel = config.tunnel.ok_or(FromProtobufTypeError::InvalidArgument( + "missing tunnel config", + ))?; + + let private_key = bytes_to_privkey(&tunnel.private_key)?; + + let peer = config.peer.ok_or(FromProtobufTypeError::InvalidArgument( + "missing peer config", + ))?; + + let public_key = bytes_to_pubkey(&peer.public_key)?; + + let ipv4_gateway = config.ipv4_gateway.parse().map_err(|_err| { + FromProtobufTypeError::InvalidArgument("invalid IPv4 gateway") + })?; + let ipv6_gateway = option_from_proto_string(config.ipv6_gateway) + .map(|addr| { + addr.parse().map_err(|_err| { + FromProtobufTypeError::InvalidArgument("invalid IPv6 gateway") + }) + }) + .transpose()?; + + let endpoint = peer.endpoint.parse().map_err(|_err| { + FromProtobufTypeError::InvalidArgument("invalid peer address") + })?; + + let mut tunnel_addresses = Vec::new(); + for address in tunnel.addresses { + let address = address + .parse() + .map_err(|_| FromProtobufTypeError::InvalidArgument("invalid address"))?; + tunnel_addresses.push(address); + } + + let mut allowed_ips = Vec::new(); + for address in peer.allowed_ips { + let address = address + .parse() + .map_err(|_| FromProtobufTypeError::InvalidArgument("invalid address"))?; + allowed_ips.push(address); + } + + Ok(mullvad_types::ConnectionConfig::Wireguard( + wireguard::ConnectionConfig { + tunnel: wireguard::TunnelConfig { + private_key, + addresses: tunnel_addresses, + }, + peer: wireguard::PeerConfig { + public_key, + allowed_ips, + endpoint, + psk: None, + }, + exit_peer: None, + ipv4_gateway, + ipv6_gateway, + }, + )) + } + } + } +} + +impl From<mullvad_types::ConnectionConfig> for proto::ConnectionConfig { + fn from(config: mullvad_types::ConnectionConfig) -> Self { + use proto::connection_config; + + Self { + config: Some(match config { + mullvad_types::ConnectionConfig::OpenVpn(config) => { + connection_config::Config::Openvpn(connection_config::OpenvpnConfig { + address: config.endpoint.address.to_string(), + protocol: i32::from(proto::TransportProtocol::from( + config.endpoint.protocol, + )), + username: config.username, + password: config.password, + }) + } + mullvad_types::ConnectionConfig::Wireguard(config) => { + connection_config::Config::Wireguard(connection_config::WireguardConfig { + tunnel: Some(connection_config::wireguard_config::TunnelConfig { + private_key: config.tunnel.private_key.to_bytes().to_vec(), + addresses: config + .tunnel + .addresses + .iter() + .map(|address| address.to_string()) + .collect(), + }), + peer: Some(connection_config::wireguard_config::PeerConfig { + public_key: config.peer.public_key.as_bytes().to_vec(), + allowed_ips: config + .peer + .allowed_ips + .iter() + .map(|address| address.to_string()) + .collect(), + endpoint: config.peer.endpoint.to_string(), + }), + ipv4_gateway: config.ipv4_gateway.to_string(), + ipv6_gateway: config + .ipv6_gateway + .as_ref() + .map(|address| address.to_string()) + .unwrap_or_default(), + }) + } + }), + } + } +} diff --git a/mullvad-management-interface/src/types/conversions/device.rs b/mullvad-management-interface/src/types/conversions/device.rs new file mode 100644 index 0000000000..625af6c9c1 --- /dev/null +++ b/mullvad-management-interface/src/types/conversions/device.rs @@ -0,0 +1,139 @@ +use crate::types::{conversions::bytes_to_pubkey, proto, FromProtobufTypeError}; +use prost_types::Timestamp; + +impl TryFrom<proto::Device> for mullvad_types::device::Device { + type Error = FromProtobufTypeError; + + fn try_from(device: proto::Device) -> Result<Self, Self::Error> { + Ok(mullvad_types::device::Device { + id: device.id, + name: device.name, + pubkey: bytes_to_pubkey(&device.pubkey)?, + ports: device + .ports + .into_iter() + .map(mullvad_types::device::DevicePort::from) + .collect(), + hijack_dns: device.hijack_dns, + created: chrono::DateTime::from_utc( + chrono::NaiveDateTime::from_timestamp( + device + .created + .ok_or(FromProtobufTypeError::InvalidArgument( + "missing 'created' field", + ))? + .seconds, + 0, + ), + chrono::Utc, + ), + }) + } +} + +impl From<mullvad_types::device::Device> for proto::Device { + fn from(device: mullvad_types::device::Device) -> Self { + proto::Device { + id: device.id, + name: device.name, + pubkey: device.pubkey.as_bytes().to_vec(), + ports: device + .ports + .into_iter() + .map(proto::DevicePort::from) + .collect(), + hijack_dns: device.hijack_dns, + created: Some(Timestamp { + seconds: device.created.timestamp(), + nanos: 0, + }), + } + } +} + +impl From<mullvad_types::device::DevicePort> for proto::DevicePort { + fn from(port: mullvad_types::device::DevicePort) -> Self { + proto::DevicePort { id: port.id } + } +} + +impl From<mullvad_types::device::DeviceState> for proto::DeviceState { + fn from(state: mullvad_types::device::DeviceState) -> Self { + proto::DeviceState { + state: proto::device_state::State::from(&state) as i32, + device: state.into_device().map(|device| proto::AccountAndDevice { + account_token: device.account_token, + device: Some(proto::Device::from(device.device)), + }), + } + } +} + +impl From<&mullvad_types::device::DeviceState> for proto::device_state::State { + fn from(state: &mullvad_types::device::DeviceState) -> Self { + use mullvad_types::device::DeviceState as MullvadState; + match state { + MullvadState::LoggedIn(_) => proto::device_state::State::LoggedIn, + MullvadState::LoggedOut => proto::device_state::State::LoggedOut, + MullvadState::Revoked => proto::device_state::State::Revoked, + } + } +} + +impl From<mullvad_types::device::DeviceEvent> for proto::DeviceEvent { + fn from(event: mullvad_types::device::DeviceEvent) -> Self { + proto::DeviceEvent { + cause: proto::device_event::Cause::from(event.cause) as i32, + new_state: Some(proto::DeviceState::from(event.new_state)), + } + } +} + +impl From<mullvad_types::device::DeviceEventCause> for proto::device_event::Cause { + fn from(cause: mullvad_types::device::DeviceEventCause) -> Self { + use mullvad_types::device::DeviceEventCause as MullvadEvent; + match cause { + MullvadEvent::LoggedIn => proto::device_event::Cause::LoggedIn, + MullvadEvent::LoggedOut => proto::device_event::Cause::LoggedOut, + MullvadEvent::Revoked => proto::device_event::Cause::Revoked, + MullvadEvent::Updated => proto::device_event::Cause::Updated, + MullvadEvent::RotatedKey => proto::device_event::Cause::RotatedKey, + } + } +} + +impl From<mullvad_types::device::RemoveDeviceEvent> for proto::RemoveDeviceEvent { + fn from(event: mullvad_types::device::RemoveDeviceEvent) -> Self { + proto::RemoveDeviceEvent { + account_token: event.account_token, + new_device_list: event + .new_devices + .into_iter() + .map(proto::Device::from) + .collect(), + } + } +} + +impl From<mullvad_types::device::AccountAndDevice> for proto::AccountAndDevice { + fn from(device: mullvad_types::device::AccountAndDevice) -> Self { + proto::AccountAndDevice { + account_token: device.account_token, + device: Some(proto::Device::from(device.device)), + } + } +} + +impl From<Vec<mullvad_types::device::Device>> for proto::DeviceList { + fn from(devices: Vec<mullvad_types::device::Device>) -> Self { + proto::DeviceList { + devices: devices.into_iter().map(proto::Device::from).collect(), + } + } +} + +impl From<proto::DevicePort> for mullvad_types::device::DevicePort { + fn from(port: proto::DevicePort) -> Self { + mullvad_types::device::DevicePort { id: port.id } + } +} diff --git a/mullvad-management-interface/src/types/conversions/location.rs b/mullvad-management-interface/src/types/conversions/location.rs new file mode 100644 index 0000000000..65fa825638 --- /dev/null +++ b/mullvad-management-interface/src/types/conversions/location.rs @@ -0,0 +1,46 @@ +use crate::types::{ + conversions::{arg_from_str, option_from_proto_string}, + proto, FromProtobufTypeError, +}; + +impl From<mullvad_types::location::GeoIpLocation> for proto::GeoIpLocation { + fn from(geoip: mullvad_types::location::GeoIpLocation) -> proto::GeoIpLocation { + proto::GeoIpLocation { + ipv4: geoip.ipv4.map(|ip| ip.to_string()).unwrap_or_default(), + ipv6: geoip.ipv6.map(|ip| ip.to_string()).unwrap_or_default(), + country: geoip.country, + city: geoip.city.unwrap_or_default(), + latitude: geoip.latitude, + longitude: geoip.longitude, + mullvad_exit_ip: geoip.mullvad_exit_ip, + hostname: geoip.hostname.unwrap_or_default(), + bridge_hostname: geoip.bridge_hostname.unwrap_or_default(), + entry_hostname: geoip.entry_hostname.unwrap_or_default(), + obfuscator_hostname: geoip.obfuscator_hostname.unwrap_or_default(), + } + } +} + +impl TryFrom<proto::GeoIpLocation> for mullvad_types::location::GeoIpLocation { + type Error = FromProtobufTypeError; + + fn try_from(geoip: proto::GeoIpLocation) -> Result<Self, Self::Error> { + Ok(mullvad_types::location::GeoIpLocation { + ipv4: option_from_proto_string(geoip.ipv4) + .map(|addr| arg_from_str(&addr, "invalid IPv4 address")) + .transpose()?, + ipv6: option_from_proto_string(geoip.ipv6) + .map(|addr| arg_from_str(&addr, "invalid IPv6 address")) + .transpose()?, + country: geoip.country, + city: option_from_proto_string(geoip.city), + latitude: geoip.latitude, + longitude: geoip.longitude, + mullvad_exit_ip: geoip.mullvad_exit_ip, + hostname: option_from_proto_string(geoip.hostname), + bridge_hostname: option_from_proto_string(geoip.bridge_hostname), + entry_hostname: option_from_proto_string(geoip.entry_hostname), + obfuscator_hostname: option_from_proto_string(geoip.obfuscator_hostname), + }) + } +} diff --git a/mullvad-management-interface/src/types/conversions/mod.rs b/mullvad-management-interface/src/types/conversions/mod.rs new file mode 100644 index 0000000000..46639424fd --- /dev/null +++ b/mullvad-management-interface/src/types/conversions/mod.rs @@ -0,0 +1,82 @@ +use std::str::FromStr; + +mod custom_tunnel; +mod device; +mod location; +mod net; +pub mod relay_constraints; +mod relay_list; +mod settings; +mod states; +mod version; +mod wireguard; + +#[derive(Debug)] +pub enum FromProtobufTypeError { + InvalidArgument(&'static str), +} + +fn bytes_to_pubkey( + bytes: &[u8], +) -> Result<talpid_types::net::wireguard::PublicKey, FromProtobufTypeError> { + Ok(talpid_types::net::wireguard::PublicKey::from( + *bytes_to_wg_key(bytes, "invalid public key")?, + )) +} + +fn bytes_to_privkey( + bytes: &[u8], +) -> Result<talpid_types::net::wireguard::PrivateKey, FromProtobufTypeError> { + Ok(talpid_types::net::wireguard::PrivateKey::from( + *bytes_to_wg_key(bytes, "invalid private key")?, + )) +} + +fn bytes_to_wg_key<'a>( + bytes: &'a [u8], + error_msg: &'static str, +) -> Result<&'a [u8; 32], FromProtobufTypeError> { + <&[u8; 32]>::try_from(bytes).map_err(|_| FromProtobufTypeError::InvalidArgument(error_msg)) +} + +/// Returns `Option<String>`, where an empty string represents `None`. +fn option_from_proto_string(s: String) -> Option<String> { + match s { + s if s.is_empty() => None, + s => Some(s), + } +} + +fn arg_from_str<T: FromStr<Err = E>, E>( + s: &str, + invalid_arg_msg: &'static str, +) -> Result<T, FromProtobufTypeError> { + T::from_str(s).map_err(|_err| FromProtobufTypeError::InvalidArgument(invalid_arg_msg)) +} + +impl From<FromProtobufTypeError> for crate::Status { + fn from(err: FromProtobufTypeError) -> Self { + match err { + FromProtobufTypeError::InvalidArgument(err) => crate::Status::invalid_argument(err), + } + } +} + +/// 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/net.rs b/mullvad-management-interface/src/types/conversions/net.rs new file mode 100644 index 0000000000..96210c02e3 --- /dev/null +++ b/mullvad-management-interface/src/types/conversions/net.rs @@ -0,0 +1,191 @@ +use crate::types::{conversions::arg_from_str, proto, FromProtobufTypeError}; +use mullvad_types::relay_constraints::Constraint; +use std::net::SocketAddr; + +impl From<talpid_types::net::TunnelEndpoint> for proto::TunnelEndpoint { + fn from(endpoint: talpid_types::net::TunnelEndpoint) -> Self { + use talpid_types::net; + + proto::TunnelEndpoint { + address: endpoint.endpoint.address.to_string(), + protocol: i32::from(proto::TransportProtocol::from(endpoint.endpoint.protocol)), + tunnel_type: match endpoint.tunnel_type { + net::TunnelType::Wireguard => i32::from(proto::TunnelType::Wireguard), + net::TunnelType::OpenVpn => i32::from(proto::TunnelType::Openvpn), + }, + quantum_resistant: endpoint.quantum_resistant, + proxy: endpoint.proxy.map(|proxy_ep| proto::ProxyEndpoint { + address: proxy_ep.endpoint.address.to_string(), + protocol: i32::from(proto::TransportProtocol::from(proxy_ep.endpoint.protocol)), + proxy_type: match proxy_ep.proxy_type { + net::proxy::ProxyType::Shadowsocks => i32::from(proto::ProxyType::Shadowsocks), + net::proxy::ProxyType::Custom => i32::from(proto::ProxyType::Custom), + }, + }), + obfuscation: endpoint.obfuscation.map(|obfuscation_endpoint| { + proto::ObfuscationEndpoint { + address: obfuscation_endpoint.endpoint.address.ip().to_string(), + port: u32::from(obfuscation_endpoint.endpoint.address.port()), + protocol: i32::from(proto::TransportProtocol::from( + obfuscation_endpoint.endpoint.protocol, + )), + obfuscation_type: match obfuscation_endpoint.obfuscation_type { + net::ObfuscationType::Udp2Tcp => i32::from(proto::ObfuscationType::Udp2tcp), + }, + } + }), + entry_endpoint: endpoint.entry_endpoint.map(|entry| proto::Endpoint { + address: entry.address.to_string(), + protocol: i32::from(proto::TransportProtocol::from(entry.protocol)), + }), + } + } +} + +impl TryFrom<proto::TunnelEndpoint> for talpid_types::net::TunnelEndpoint { + type Error = FromProtobufTypeError; + + fn try_from(endpoint: proto::TunnelEndpoint) -> Result<Self, Self::Error> { + use talpid_types::net as talpid_net; + + Ok(talpid_net::TunnelEndpoint { + endpoint: talpid_net::Endpoint { + address: arg_from_str(&endpoint.address, "invalid endpoint address")?, + protocol: try_transport_protocol_from_i32(endpoint.protocol)?, + }, + tunnel_type: try_tunnel_type_from_i32(endpoint.tunnel_type)?, + quantum_resistant: endpoint.quantum_resistant, + proxy: endpoint + .proxy + .map(|proxy_ep| { + Ok(talpid_net::proxy::ProxyEndpoint { + endpoint: talpid_net::Endpoint { + address: arg_from_str( + &proxy_ep.address, + "invalid proxy endpoint address", + )?, + protocol: try_transport_protocol_from_i32(proxy_ep.protocol)?, + }, + proxy_type: match proto::ProxyType::from_i32(proxy_ep.proxy_type) { + Some(proto::ProxyType::Shadowsocks) => { + talpid_net::proxy::ProxyType::Shadowsocks + } + Some(proto::ProxyType::Custom) => talpid_net::proxy::ProxyType::Custom, + None => { + return Err(FromProtobufTypeError::InvalidArgument( + "unknown proxy type", + )) + } + }, + }) + }) + .transpose()?, + obfuscation: endpoint + .obfuscation + .map(|obfs_ep| { + Ok(talpid_net::ObfuscationEndpoint { + endpoint: talpid_net::Endpoint { + address: SocketAddr::new( + arg_from_str( + &obfs_ep.address, + "invalid obfuscation endpoint address", + )?, + obfs_ep.port as u16, + ), + protocol: try_transport_protocol_from_i32(obfs_ep.protocol)?, + }, + obfuscation_type: match proto::ObfuscationType::from_i32( + obfs_ep.obfuscation_type, + ) { + Some(proto::ObfuscationType::Udp2tcp) => { + talpid_net::ObfuscationType::Udp2Tcp + } + None => { + return Err(FromProtobufTypeError::InvalidArgument( + "unknown obfuscation type", + )) + } + }, + }) + }) + .transpose()?, + entry_endpoint: endpoint + .entry_endpoint + .map(|entry| { + Ok(talpid_net::Endpoint { + address: arg_from_str(&entry.address, "invalid entry endpoint address")?, + protocol: try_transport_protocol_from_i32(entry.protocol)?, + }) + }) + .transpose()?, + }) + } +} + +impl From<talpid_types::net::TransportProtocol> for proto::TransportProtocol { + fn from(protocol: talpid_types::net::TransportProtocol) -> Self { + match protocol { + talpid_types::net::TransportProtocol::Udp => proto::TransportProtocol::Udp, + talpid_types::net::TransportProtocol::Tcp => proto::TransportProtocol::Tcp, + } + } +} + +impl From<talpid_types::net::IpVersion> for proto::IpVersion { + fn from(version: talpid_types::net::IpVersion) -> Self { + match version { + talpid_types::net::IpVersion::V4 => proto::IpVersion::V4, + talpid_types::net::IpVersion::V6 => proto::IpVersion::V6, + } + } +} + +impl From<proto::TransportProtocol> for talpid_types::net::TransportProtocol { + fn from(protocol: proto::TransportProtocol) -> Self { + match protocol { + proto::TransportProtocol::Udp => talpid_types::net::TransportProtocol::Udp, + proto::TransportProtocol::Tcp => talpid_types::net::TransportProtocol::Tcp, + } + } +} + +impl TryFrom<proto::TunnelTypeConstraint> for Constraint<talpid_types::net::TunnelType> { + type Error = FromProtobufTypeError; + + fn try_from( + tunnel_type: proto::TunnelTypeConstraint, + ) -> Result<Constraint<talpid_types::net::TunnelType>, Self::Error> { + let tunnel_type = try_tunnel_type_from_i32(tunnel_type.tunnel_type)?; + Ok(Constraint::Only(tunnel_type)) + } +} + +impl From<proto::IpVersion> for proto::IpVersionConstraint { + fn from(version: proto::IpVersion) -> Self { + Self { + protocol: i32::from(version), + } + } +} + +pub fn try_tunnel_type_from_i32( + tunnel_type: i32, +) -> Result<talpid_types::net::TunnelType, FromProtobufTypeError> { + match proto::TunnelType::from_i32(tunnel_type) { + Some(proto::TunnelType::Openvpn) => Ok(talpid_types::net::TunnelType::OpenVpn), + Some(proto::TunnelType::Wireguard) => Ok(talpid_types::net::TunnelType::Wireguard), + None => Err(FromProtobufTypeError::InvalidArgument( + "invalid tunnel protocol", + )), + } +} + +pub fn try_transport_protocol_from_i32( + protocol: i32, +) -> Result<talpid_types::net::TransportProtocol, FromProtobufTypeError> { + Ok(proto::TransportProtocol::from_i32(protocol) + .ok_or(FromProtobufTypeError::InvalidArgument( + "invalid transport protocol", + ))? + .into()) +} diff --git a/mullvad-management-interface/src/types/conversions/relay_constraints.rs b/mullvad-management-interface/src/types/conversions/relay_constraints.rs new file mode 100644 index 0000000000..d76006bf7f --- /dev/null +++ b/mullvad-management-interface/src/types/conversions/relay_constraints.rs @@ -0,0 +1,679 @@ +use crate::types::{conversions::option_from_proto_string, proto, FromProtobufTypeError}; +use mullvad_types::relay_constraints::Constraint; + +impl TryFrom<&proto::WireguardConstraints> + for mullvad_types::relay_constraints::WireguardConstraints +{ + type Error = FromProtobufTypeError; + + fn try_from( + constraints: &proto::WireguardConstraints, + ) -> Result<mullvad_types::relay_constraints::WireguardConstraints, Self::Error> { + use mullvad_types::relay_constraints as mullvad_constraints; + use talpid_types::net; + + let ip_version = match &constraints.ip_version { + Some(constraint) => match proto::IpVersion::from_i32(constraint.protocol) { + Some(proto::IpVersion::V4) => Some(net::IpVersion::V4), + Some(proto::IpVersion::V6) => Some(net::IpVersion::V6), + None => { + return Err(FromProtobufTypeError::InvalidArgument( + "invalid ip protocol version", + )) + } + }, + None => None, + }; + + Ok(mullvad_constraints::WireguardConstraints { + port: if constraints.port == 0 { + Constraint::Any + } else { + Constraint::Only(constraints.port as u16) + }, + ip_version: Constraint::from(ip_version), + use_multihop: constraints.use_multihop, + entry_location: constraints + .entry_location + .clone() + .map(Constraint::<mullvad_types::relay_constraints::LocationConstraint>::from) + .unwrap_or(Constraint::Any), + }) + } +} + +impl TryFrom<&proto::OpenvpnConstraints> for mullvad_types::relay_constraints::OpenVpnConstraints { + type Error = FromProtobufTypeError; + + fn try_from( + constraints: &proto::OpenvpnConstraints, + ) -> Result<mullvad_types::relay_constraints::OpenVpnConstraints, Self::Error> { + use mullvad_types::relay_constraints as mullvad_constraints; + + Ok(mullvad_constraints::OpenVpnConstraints { + port: Constraint::from(match &constraints.port { + Some(port) => Some(mullvad_constraints::TransportPort::try_from(port.clone())?), + None => None, + }), + }) + } +} + +impl TryFrom<proto::RelaySettings> for mullvad_types::relay_constraints::RelaySettings { + type Error = FromProtobufTypeError; + + fn try_from( + settings: proto::RelaySettings, + ) -> Result<mullvad_types::relay_constraints::RelaySettings, Self::Error> { + use mullvad_types::{relay_constraints as mullvad_constraints, CustomTunnelEndpoint}; + use talpid_types::net; + + let update_value = settings + .endpoint + .ok_or(FromProtobufTypeError::InvalidArgument( + "missing relay settings", + ))?; + + match update_value { + proto::relay_settings::Endpoint::Custom(settings) => { + let config = settings + .config + .ok_or(FromProtobufTypeError::InvalidArgument( + "missing relay connection config", + ))?; + let config = mullvad_types::ConnectionConfig::try_from(config)?; + Ok(mullvad_constraints::RelaySettings::CustomTunnelEndpoint( + CustomTunnelEndpoint { + host: settings.host, + config, + }, + )) + } + + proto::relay_settings::Endpoint::Normal(settings) => { + let location = settings + .location + .map(Constraint::<mullvad_types::relay_constraints::LocationConstraint>::from) + .unwrap_or(Constraint::Any); + let providers = try_providers_constraint_from_proto(&settings.providers)?; + let ownership = try_ownership_constraint_from_i32(settings.ownership)?; + let tunnel_protocol = settings + .tunnel_type + .map(Constraint::<net::TunnelType>::try_from) + .transpose()? + .unwrap_or(Constraint::Any); + let openvpn_constraints = + mullvad_constraints::OpenVpnConstraints::try_from( + &settings.openvpn_constraints.ok_or( + FromProtobufTypeError::InvalidArgument("missing openvpn constraints"), + )?, + )?; + let wireguard_constraints = mullvad_constraints::WireguardConstraints::try_from( + &settings.wireguard_constraints.ok_or( + FromProtobufTypeError::InvalidArgument("missing wireguard constraints"), + )?, + )?; + + Ok(mullvad_constraints::RelaySettings::Normal( + mullvad_constraints::RelayConstraints { + location, + providers, + ownership, + tunnel_protocol, + wireguard_constraints, + openvpn_constraints, + }, + )) + } + } + } +} + +impl TryFrom<proto::RelaySettingsUpdate> for mullvad_types::relay_constraints::RelaySettingsUpdate { + type Error = FromProtobufTypeError; + + fn try_from( + settings: proto::RelaySettingsUpdate, + ) -> Result<mullvad_types::relay_constraints::RelaySettingsUpdate, Self::Error> { + use mullvad_types::{relay_constraints as mullvad_constraints, CustomTunnelEndpoint}; + use talpid_types::net; + + let update_value = settings + .r#type + .ok_or(FromProtobufTypeError::InvalidArgument( + "missing relay settings", + ))?; + + match update_value { + proto::relay_settings_update::Type::Custom(settings) => { + let config = settings + .config + .ok_or(FromProtobufTypeError::InvalidArgument( + "missing relay connection config", + ))?; + let config = mullvad_types::ConnectionConfig::try_from(config)?; + Ok( + mullvad_constraints::RelaySettingsUpdate::CustomTunnelEndpoint( + CustomTunnelEndpoint { + host: settings.host, + config, + }, + ), + ) + } + + proto::relay_settings_update::Type::Normal(settings) => { + // If `location` isn't provided, no changes are made. + // If `location` is provided, but is an empty vector, + // then the constraint is set to `Constraint::Any`. + let location = settings + .location + .map(Constraint::<mullvad_types::relay_constraints::LocationConstraint>::from); + let providers = if let Some(ref provider_update) = settings.providers { + Some(try_providers_constraint_from_proto( + &provider_update.providers, + )?) + } else { + None + }; + let ownership = if let Some(ref ownership_update) = settings.ownership { + Some(try_ownership_constraint_from_i32( + ownership_update.ownership, + )?) + } else { + None + }; + let tunnel_protocol = if let Some(update) = settings.tunnel_type { + Some( + update + .tunnel_type + .map(Constraint::<net::TunnelType>::try_from) + .transpose()? + .unwrap_or(Constraint::Any), + ) + } else { + None + }; + let openvpn_constraints = + if let Some(ref constraints) = settings.openvpn_constraints { + Some(mullvad_constraints::OpenVpnConstraints::try_from( + constraints, + )?) + } else { + None + }; + let wireguard_constraints = + if let Some(ref constraints) = settings.wireguard_constraints { + Some(mullvad_constraints::WireguardConstraints::try_from( + constraints, + )?) + } else { + None + }; + Ok(mullvad_constraints::RelaySettingsUpdate::Normal( + mullvad_constraints::RelayConstraintsUpdate { + location, + providers, + ownership, + tunnel_protocol, + wireguard_constraints, + openvpn_constraints, + }, + )) + } + } + } +} + +impl From<mullvad_types::relay_constraints::BridgeState> for proto::BridgeState { + fn from(state: mullvad_types::relay_constraints::BridgeState) -> Self { + use mullvad_types::relay_constraints::BridgeState; + Self { + state: i32::from(match state { + BridgeState::Auto => proto::bridge_state::State::Auto, + BridgeState::On => proto::bridge_state::State::On, + BridgeState::Off => proto::bridge_state::State::Off, + }), + } + } +} + +impl From<&mullvad_types::relay_constraints::ObfuscationSettings> for proto::ObfuscationSettings { + fn from(settings: &mullvad_types::relay_constraints::ObfuscationSettings) -> Self { + use mullvad_types::relay_constraints::SelectedObfuscation; + let selected_obfuscation = i32::from(match settings.selected_obfuscation { + SelectedObfuscation::Auto => proto::obfuscation_settings::SelectedObfuscation::Auto, + SelectedObfuscation::Off => proto::obfuscation_settings::SelectedObfuscation::Off, + SelectedObfuscation::Udp2Tcp => { + proto::obfuscation_settings::SelectedObfuscation::Udp2tcp + } + }); + Self { + selected_obfuscation, + udp2tcp: Some(proto::Udp2TcpObfuscationSettings::from(&settings.udp2tcp)), + } + } +} + +impl From<&mullvad_types::relay_constraints::Udp2TcpObfuscationSettings> + for proto::Udp2TcpObfuscationSettings +{ + fn from(settings: &mullvad_types::relay_constraints::Udp2TcpObfuscationSettings) -> Self { + Self { + port: u32::from(settings.port.unwrap_or(0)), + } + } +} + +impl From<mullvad_types::relay_constraints::BridgeSettings> for proto::BridgeSettings { + fn from(settings: mullvad_types::relay_constraints::BridgeSettings) -> Self { + use mullvad_types::relay_constraints::BridgeSettings as MullvadBridgeSettings; + use proto::bridge_settings; + use talpid_types::net as talpid_net; + + let settings = match settings { + MullvadBridgeSettings::Normal(constraints) => { + bridge_settings::Type::Normal(bridge_settings::BridgeConstraints { + location: constraints + .location + .clone() + .option() + .map(proto::RelayLocation::from), + providers: convert_providers_constraint(&constraints.providers), + ownership: convert_ownership_constraint(&constraints.ownership) as i32, + }) + } + MullvadBridgeSettings::Custom(proxy_settings) => match proxy_settings { + talpid_net::openvpn::ProxySettings::Local(proxy_settings) => { + bridge_settings::Type::Local(bridge_settings::LocalProxySettings { + port: u32::from(proxy_settings.port), + peer: proxy_settings.peer.to_string(), + }) + } + talpid_net::openvpn::ProxySettings::Remote(proxy_settings) => { + bridge_settings::Type::Remote(bridge_settings::RemoteProxySettings { + address: proxy_settings.address.to_string(), + auth: proxy_settings.auth.as_ref().map(|auth| { + bridge_settings::RemoteProxyAuth { + username: auth.username.clone(), + password: auth.password.clone(), + } + }), + }) + } + talpid_net::openvpn::ProxySettings::Shadowsocks(proxy_settings) => { + bridge_settings::Type::Shadowsocks(bridge_settings::ShadowsocksProxySettings { + peer: proxy_settings.peer.to_string(), + password: proxy_settings.password.clone(), + cipher: proxy_settings.cipher, + }) + } + }, + }; + + proto::BridgeSettings { + r#type: Some(settings), + } + } +} + +impl From<mullvad_types::relay_constraints::RelaySettings> for proto::RelaySettings { + fn from(settings: mullvad_types::relay_constraints::RelaySettings) -> Self { + use mullvad_types::relay_constraints::RelaySettings as MullvadRelaySettings; + use proto::relay_settings; + use talpid_types::net as talpid_net; + + let endpoint = match settings { + MullvadRelaySettings::CustomTunnelEndpoint(endpoint) => { + relay_settings::Endpoint::Custom(proto::CustomRelaySettings { + host: endpoint.host, + config: Some(proto::ConnectionConfig::from(endpoint.config)), + }) + } + MullvadRelaySettings::Normal(constraints) => { + relay_settings::Endpoint::Normal(proto::NormalRelaySettings { + location: constraints + .location + .option() + .map(proto::RelayLocation::from), + providers: convert_providers_constraint(&constraints.providers), + ownership: convert_ownership_constraint(&constraints.ownership) as i32, + tunnel_type: match constraints.tunnel_protocol { + Constraint::Any => None, + Constraint::Only(talpid_net::TunnelType::Wireguard) => { + Some(proto::TunnelType::Wireguard) + } + Constraint::Only(talpid_net::TunnelType::OpenVpn) => { + Some(proto::TunnelType::Openvpn) + } + } + .map(|tunnel_type| proto::TunnelTypeConstraint { + tunnel_type: i32::from(tunnel_type), + }), + + wireguard_constraints: Some(proto::WireguardConstraints { + port: u32::from(constraints.wireguard_constraints.port.unwrap_or(0)), + ip_version: constraints + .wireguard_constraints + .ip_version + .option() + .map(proto::IpVersion::from) + .map(proto::IpVersionConstraint::from), + use_multihop: constraints.wireguard_constraints.use_multihop, + entry_location: constraints + .wireguard_constraints + .entry_location + .option() + .map(proto::RelayLocation::from), + }), + + openvpn_constraints: Some(proto::OpenvpnConstraints { + port: constraints + .openvpn_constraints + .port + .option() + .map(proto::TransportPort::from), + }), + }) + } + }; + + Self { + endpoint: Some(endpoint), + } + } +} + +impl From<mullvad_types::relay_constraints::TransportPort> for proto::TransportPort { + fn from(port: mullvad_types::relay_constraints::TransportPort) -> Self { + proto::TransportPort { + protocol: proto::TransportProtocol::from(port.protocol) as i32, + port: port.port.map(u32::from).unwrap_or(0), + } + } +} + +impl + From< + mullvad_types::relay_constraints::Constraint< + mullvad_types::relay_constraints::LocationConstraint, + >, + > for proto::RelayLocation +{ + fn from( + location: mullvad_types::relay_constraints::Constraint< + mullvad_types::relay_constraints::LocationConstraint, + >, + ) -> Self { + location + .option() + .map(proto::RelayLocation::from) + .unwrap_or_default() + } +} + +impl From<mullvad_types::relay_constraints::LocationConstraint> for proto::RelayLocation { + fn from(location: mullvad_types::relay_constraints::LocationConstraint) -> Self { + use mullvad_types::relay_constraints::LocationConstraint; + + match location { + LocationConstraint::Country(country) => Self { + country, + ..Default::default() + }, + LocationConstraint::City(country, city) => Self { + country, + city, + ..Default::default() + }, + LocationConstraint::Hostname(country, city, hostname) => Self { + country, + city, + hostname, + }, + } + } +} + +impl From<proto::RelayLocation> + for Constraint<mullvad_types::relay_constraints::LocationConstraint> +{ + fn from(location: proto::RelayLocation) -> Self { + use mullvad_types::relay_constraints::LocationConstraint; + + if let Some(hostname) = option_from_proto_string(location.hostname) { + Constraint::Only(LocationConstraint::Hostname( + location.country, + location.city, + hostname, + )) + } else if let Some(city) = option_from_proto_string(location.city) { + Constraint::Only(LocationConstraint::City(location.country, city)) + } else if let Some(country) = option_from_proto_string(location.country) { + Constraint::Only(LocationConstraint::Country(country)) + } else { + Constraint::Any + } + } +} + +impl TryFrom<proto::BridgeSettings> for mullvad_types::relay_constraints::BridgeSettings { + type Error = FromProtobufTypeError; + + fn try_from(settings: proto::BridgeSettings) -> Result<Self, Self::Error> { + use mullvad_types::relay_constraints as mullvad_constraints; + use talpid_types::net as talpid_net; + + match settings + .r#type + .ok_or(FromProtobufTypeError::InvalidArgument( + "no settings provided", + ))? { + proto::bridge_settings::Type::Normal(constraints) => { + let location = match constraints.location { + None => Constraint::Any, + Some(location) => { + Constraint::<mullvad_constraints::LocationConstraint>::from(location) + } + }; + let providers = try_providers_constraint_from_proto(&constraints.providers)?; + let ownership = try_ownership_constraint_from_i32(constraints.ownership)?; + + Ok(mullvad_constraints::BridgeSettings::Normal( + mullvad_constraints::BridgeConstraints { + location, + providers, + ownership, + }, + )) + } + proto::bridge_settings::Type::Local(proxy_settings) => { + let peer = proxy_settings.peer.parse().map_err(|_| { + FromProtobufTypeError::InvalidArgument("failed to parse peer address") + })?; + let proxy_settings = talpid_net::openvpn::ProxySettings::Local( + talpid_net::openvpn::LocalProxySettings { + port: proxy_settings.port as u16, + peer, + }, + ); + Ok(mullvad_constraints::BridgeSettings::Custom(proxy_settings)) + } + proto::bridge_settings::Type::Remote(proxy_settings) => { + let address = proxy_settings.address.parse().map_err(|_| { + FromProtobufTypeError::InvalidArgument("failed to parse IP address") + })?; + let auth = proxy_settings + .auth + .map(|auth| talpid_net::openvpn::ProxyAuth { + username: auth.username, + password: auth.password, + }); + let proxy_settings = talpid_net::openvpn::ProxySettings::Remote( + talpid_net::openvpn::RemoteProxySettings { address, auth }, + ); + Ok(mullvad_constraints::BridgeSettings::Custom(proxy_settings)) + } + proto::bridge_settings::Type::Shadowsocks(proxy_settings) => { + let peer = proxy_settings.peer.parse().map_err(|_| { + FromProtobufTypeError::InvalidArgument("failed to parse peer address") + })?; + let proxy_settings = talpid_net::openvpn::ProxySettings::Shadowsocks( + talpid_net::openvpn::ShadowsocksProxySettings { + peer, + password: proxy_settings.password, + cipher: proxy_settings.cipher, + }, + ); + Ok(mullvad_constraints::BridgeSettings::Custom(proxy_settings)) + } + } + } +} + +impl TryFrom<proto::ObfuscationSettings> for mullvad_types::relay_constraints::ObfuscationSettings { + type Error = FromProtobufTypeError; + + fn try_from(settings: proto::ObfuscationSettings) -> Result<Self, Self::Error> { + use mullvad_types::relay_constraints::SelectedObfuscation; + use proto::obfuscation_settings::SelectedObfuscation as IpcSelectedObfuscation; + let selected_obfuscation = + match IpcSelectedObfuscation::from_i32(settings.selected_obfuscation) { + Some(IpcSelectedObfuscation::Auto) => SelectedObfuscation::Auto, + Some(IpcSelectedObfuscation::Off) => SelectedObfuscation::Off, + Some(IpcSelectedObfuscation::Udp2tcp) => SelectedObfuscation::Udp2Tcp, + None => { + return Err(FromProtobufTypeError::InvalidArgument( + "invalid selected obfuscator", + )); + } + }; + + let udp2tcp = match settings.udp2tcp { + Some(settings) => { + mullvad_types::relay_constraints::Udp2TcpObfuscationSettings::try_from(&settings)? + } + None => { + return Err(FromProtobufTypeError::InvalidArgument( + "invalid selected obfuscator", + )); + } + }; + + Ok(Self { + selected_obfuscation, + udp2tcp, + }) + } +} + +impl TryFrom<&proto::Udp2TcpObfuscationSettings> + for mullvad_types::relay_constraints::Udp2TcpObfuscationSettings +{ + type Error = FromProtobufTypeError; + + fn try_from(settings: &proto::Udp2TcpObfuscationSettings) -> Result<Self, Self::Error> { + Ok(Self { + port: if settings.port == 0 { + Constraint::Any + } else { + Constraint::Only(settings.port as u16) + }, + }) + } +} + +impl TryFrom<proto::BridgeState> for mullvad_types::relay_constraints::BridgeState { + type Error = FromProtobufTypeError; + + fn try_from(state: proto::BridgeState) -> Result<Self, Self::Error> { + match proto::bridge_state::State::from_i32(state.state) { + Some(proto::bridge_state::State::Auto) => { + Ok(mullvad_types::relay_constraints::BridgeState::Auto) + } + Some(proto::bridge_state::State::On) => { + Ok(mullvad_types::relay_constraints::BridgeState::On) + } + Some(proto::bridge_state::State::Off) => { + Ok(mullvad_types::relay_constraints::BridgeState::Off) + } + None => Err(FromProtobufTypeError::InvalidArgument( + "invalid bridge state", + )), + } + } +} + +impl TryFrom<proto::TransportPort> for mullvad_types::relay_constraints::TransportPort { + type Error = FromProtobufTypeError; + + fn try_from(port: proto::TransportPort) -> Result<Self, Self::Error> { + Ok(mullvad_types::relay_constraints::TransportPort { + protocol: super::net::try_transport_protocol_from_i32(port.protocol)?, + port: if port.port == 0 { + Constraint::Any + } else { + Constraint::Only(port.port as u16) + }, + }) + } +} + +pub fn try_providers_constraint_from_proto( + providers: &[String], +) -> Result<Constraint<mullvad_types::relay_constraints::Providers>, FromProtobufTypeError> { + if !providers.is_empty() { + Ok(Constraint::Only( + mullvad_types::relay_constraints::Providers::new(providers.iter().cloned()).map_err( + |_| FromProtobufTypeError::InvalidArgument("must specify at least one provider"), + )?, + )) + } else { + Ok(Constraint::Any) + } +} + +pub fn try_ownership_constraint_from_i32( + ownership: i32, +) -> Result<Constraint<mullvad_types::relay_constraints::Ownership>, FromProtobufTypeError> { + proto::Ownership::from_i32(ownership) + .map(ownership_constraint_from_proto) + .ok_or(FromProtobufTypeError::InvalidArgument( + "invalid ownership argument", + )) +} + +pub fn ownership_constraint_from_proto( + ownership: proto::Ownership, +) -> Constraint<mullvad_types::relay_constraints::Ownership> { + use mullvad_types::relay_constraints::Ownership as MullvadOwnership; + + match ownership { + proto::Ownership::Any => Constraint::Any, + proto::Ownership::MullvadOwned => Constraint::Only(MullvadOwnership::MullvadOwned), + proto::Ownership::Rented => Constraint::Only(MullvadOwnership::Rented), + } +} + +fn convert_providers_constraint( + providers: &Constraint<mullvad_types::relay_constraints::Providers>, +) -> Vec<String> { + match providers.as_ref() { + Constraint::Any => vec![], + Constraint::Only(providers) => Vec::from(providers.clone()), + } +} + +fn convert_ownership_constraint( + ownership: &Constraint<mullvad_types::relay_constraints::Ownership>, +) -> proto::Ownership { + use mullvad_types::relay_constraints::Ownership as MullvadOwnership; + + match ownership.as_ref() { + Constraint::Any => proto::Ownership::Any, + Constraint::Only(ownership) => match ownership { + MullvadOwnership::MullvadOwned => proto::Ownership::MullvadOwned, + MullvadOwnership::Rented => proto::Ownership::Rented, + }, + } +} diff --git a/mullvad-management-interface/src/types/conversions/relay_list.rs b/mullvad-management-interface/src/types/conversions/relay_list.rs new file mode 100644 index 0000000000..23f628ad9b --- /dev/null +++ b/mullvad-management-interface/src/types/conversions/relay_list.rs @@ -0,0 +1,205 @@ +use crate::types::{ + conversions::{bytes_to_pubkey, option_from_proto_string, to_proto_any, try_from_proto_any}, + proto, FromProtobufTypeError, +}; + +impl From<mullvad_types::relay_list::RelayList> for proto::RelayList { + fn from(relay_list: mullvad_types::relay_list::RelayList) -> Self { + let mut proto_list = proto::RelayList { + countries: vec![], + openvpn: Some(proto::OpenVpnEndpointData::from(relay_list.openvpn)), + bridge: Some(proto::BridgeEndpointData::from(relay_list.bridge)), + wireguard: Some(proto::WireguardEndpointData::from(relay_list.wireguard)), + }; + proto_list.countries = relay_list + .countries + .into_iter() + .map(proto::RelayListCountry::from) + .collect(); + proto_list + } +} + +impl From<mullvad_types::relay_list::OpenVpnEndpointData> for proto::OpenVpnEndpointData { + fn from(openvpn: mullvad_types::relay_list::OpenVpnEndpointData) -> Self { + proto::OpenVpnEndpointData { + endpoints: openvpn + .ports + .into_iter() + .map(|endpoint| proto::OpenVpnEndpoint { + port: u32::from(endpoint.port), + protocol: proto::TransportProtocol::from(endpoint.protocol) as i32, + }) + .collect(), + } + } +} + +impl From<mullvad_types::relay_list::BridgeEndpointData> for proto::BridgeEndpointData { + fn from(bridge: mullvad_types::relay_list::BridgeEndpointData) -> Self { + proto::BridgeEndpointData { + shadowsocks: bridge + .shadowsocks + .into_iter() + .map(|endpoint| proto::ShadowsocksEndpointData { + port: u32::from(endpoint.port), + cipher: endpoint.cipher, + password: endpoint.password, + protocol: proto::TransportProtocol::from(endpoint.protocol) as i32, + }) + .collect(), + } + } +} + +impl From<mullvad_types::relay_list::WireguardEndpointData> for proto::WireguardEndpointData { + fn from(wireguard: mullvad_types::relay_list::WireguardEndpointData) -> Self { + proto::WireguardEndpointData { + port_ranges: wireguard + .port_ranges + .into_iter() + .map(|(first, last)| proto::PortRange { + first: u32::from(first), + last: u32::from(last), + }) + .collect(), + ipv4_gateway: wireguard.ipv4_gateway.to_string(), + ipv6_gateway: wireguard.ipv6_gateway.to_string(), + udp2tcp_ports: wireguard.udp2tcp_ports.into_iter().map(u32::from).collect(), + } + } +} + +impl From<mullvad_types::relay_list::RelayListCountry> for proto::RelayListCountry { + fn from(country: mullvad_types::relay_list::RelayListCountry) -> Self { + let mut proto_country = proto::RelayListCountry { + name: country.name, + code: country.code, + cities: Vec::with_capacity(country.cities.len()), + }; + + for city in country.cities.into_iter() { + proto_country.cities.push(proto::RelayListCity { + name: city.name, + code: city.code, + latitude: city.latitude, + longitude: city.longitude, + relays: city.relays.into_iter().map(proto::Relay::from).collect(), + }); + } + + proto_country + } +} + +impl From<mullvad_types::relay_list::Relay> for proto::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(), + ipv6_addr_in: relay + .ipv6_addr_in + .map(|addr| addr.to_string()) + .unwrap_or_default(), + include_in_country: relay.include_in_country, + active: relay.active, + 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(), + }, + )), + _ => None, + }, + location: relay.location.map(|location| proto::Location { + country: location.country, + country_code: location.country_code, + city: location.city, + city_code: location.city_code, + latitude: location.latitude, + longitude: location.longitude, + }), + } + } +} + +impl TryFrom<proto::Relay> for mullvad_types::relay_list::Relay { + type Error = FromProtobufTypeError; + + fn try_from(relay: proto::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 == 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, + ) + .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 = option_from_proto_string(relay.ipv6_addr_in) + .map(|addr| { + addr.parse().map_err(|_err| { + FromProtobufTypeError::InvalidArgument("invalid relay IPv6 address") + }) + }) + .transpose()?; + + 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, + }), + }) + } +} diff --git a/mullvad-management-interface/src/types/conversions/settings.rs b/mullvad-management-interface/src/types/conversions/settings.rs new file mode 100644 index 0000000000..60805d5004 --- /dev/null +++ b/mullvad-management-interface/src/types/conversions/settings.rs @@ -0,0 +1,227 @@ +use crate::types::{proto, FromProtobufTypeError}; +use talpid_types::ErrorExt; + +impl From<&mullvad_types::settings::Settings> for proto::Settings { + fn from(settings: &mullvad_types::settings::Settings) -> Self { + #[cfg(windows)] + let split_tunnel = { + let mut converted_list = vec![]; + for path in settings.split_tunnel.apps.clone().iter() { + match path.as_path().as_os_str().to_str() { + Some(path) => converted_list.push(path.to_string()), + None => { + log::error!("failed to convert OS string: {:?}", path); + } + } + } + + Some(proto::SplitTunnelSettings { + enable_exclusions: settings.split_tunnel.enable_exclusions, + apps: converted_list, + }) + }; + #[cfg(not(windows))] + let split_tunnel = None; + + Self { + relay_settings: Some(proto::RelaySettings::from(settings.get_relay_settings())), + bridge_settings: Some(proto::BridgeSettings::from( + settings.bridge_settings.clone(), + )), + bridge_state: Some(proto::BridgeState::from(settings.get_bridge_state())), + allow_lan: settings.allow_lan, + block_when_disconnected: settings.block_when_disconnected, + auto_connect: settings.auto_connect, + tunnel_options: Some(proto::TunnelOptions::from(&settings.tunnel_options)), + show_beta_releases: settings.show_beta_releases, + obfuscation_settings: Some(proto::ObfuscationSettings::from( + &settings.obfuscation_settings, + )), + split_tunnel, + } + } +} + +impl From<&mullvad_types::settings::DnsOptions> for proto::DnsOptions { + fn from(options: &mullvad_types::settings::DnsOptions) -> Self { + use proto::dns_options; + + proto::DnsOptions { + state: match options.state { + mullvad_types::settings::DnsState::Default => dns_options::DnsState::Default as i32, + mullvad_types::settings::DnsState::Custom => dns_options::DnsState::Custom as i32, + }, + default_options: Some(proto::DefaultDnsOptions { + block_ads: options.default_options.block_ads, + block_trackers: options.default_options.block_trackers, + block_malware: options.default_options.block_malware, + block_adult_content: options.default_options.block_adult_content, + block_gambling: options.default_options.block_gambling, + }), + custom_options: Some(proto::CustomDnsOptions { + addresses: options + .custom_options + .addresses + .iter() + .map(|addr| addr.to_string()) + .collect(), + }), + } + } +} + +impl From<&mullvad_types::settings::TunnelOptions> for proto::TunnelOptions { + fn from(options: &mullvad_types::settings::TunnelOptions) -> Self { + Self { + openvpn: Some(proto::tunnel_options::OpenvpnOptions { + mssfix: u32::from(options.openvpn.mssfix.unwrap_or_default()), + }), + wireguard: Some(proto::tunnel_options::WireguardOptions { + mtu: u32::from(options.wireguard.options.mtu.unwrap_or_default()), + rotation_interval: options.wireguard.rotation_interval.map(|ivl| { + prost_types::Duration::try_from(std::time::Duration::from(ivl)) + .expect("Failed to convert std::time::Duration to prost_types::Duration for tunnel_options.wireguard.rotation_interval") + }), + #[cfg(windows)] + use_wireguard_nt: options.wireguard.options.use_wireguard_nt, + #[cfg(not(windows))] + use_wireguard_nt: false, + use_pq_safe_psk: options.wireguard.options.use_pq_safe_psk, + }), + generic: Some(proto::tunnel_options::GenericOptions { + enable_ipv6: options.generic.enable_ipv6, + }), + #[cfg(not(target_os = "android"))] + dns_options: Some(proto::DnsOptions::from(&options.dns_options)), + #[cfg(target_os = "android")] + dns_options: None, + } + } +} + +impl TryFrom<proto::TunnelOptions> for mullvad_types::settings::TunnelOptions { + type Error = FromProtobufTypeError; + + fn try_from(options: proto::TunnelOptions) -> Result<Self, Self::Error> { + use talpid_types::net; + + let openvpn_options = options + .openvpn + .ok_or(FromProtobufTypeError::InvalidArgument( + "missing openvpn tunnel options", + ))?; + let wireguard_options = options + .wireguard + .ok_or(FromProtobufTypeError::InvalidArgument( + "missing openvpn tunnel options", + ))?; + let generic_options = options + .generic + .ok_or(FromProtobufTypeError::InvalidArgument( + "missing generic tunnel options", + ))?; + let dns_options = options + .dns_options + .ok_or(FromProtobufTypeError::InvalidArgument( + "missing tunnel DNS options", + ))?; + + Ok(Self { + openvpn: net::openvpn::TunnelOptions { + mssfix: if openvpn_options.mssfix != 0 { + Some(openvpn_options.mssfix as u16) + } else { + None + }, + }, + wireguard: mullvad_types::wireguard::TunnelOptions { + options: net::wireguard::TunnelOptions { + mtu: if wireguard_options.mtu != 0 { + Some(wireguard_options.mtu as u16) + } else { + None + }, + use_pq_safe_psk: wireguard_options.use_pq_safe_psk, + #[cfg(windows)] + use_wireguard_nt: wireguard_options.use_wireguard_nt, + }, + rotation_interval: wireguard_options + .rotation_interval + .map(std::time::Duration::try_from) + .transpose() + .map_err(|_| FromProtobufTypeError::InvalidArgument("invalid duration"))? + .map(mullvad_types::wireguard::RotationInterval::try_from) + .transpose() + .map_err(|error: mullvad_types::wireguard::RotationIntervalError| { + log::error!( + "{}", + error.display_chain_with_msg("Invalid rotation interval") + ); + FromProtobufTypeError::InvalidArgument("invalid rotation interval") + })?, + }, + generic: net::GenericTunnelOptions { + enable_ipv6: generic_options.enable_ipv6, + }, + #[cfg(not(target_os = "android"))] + dns_options: mullvad_types::settings::DnsOptions::try_from(dns_options)?, + }) + } +} + +impl TryFrom<proto::DnsOptions> for mullvad_types::settings::DnsOptions { + type Error = FromProtobufTypeError; + + fn try_from(options: proto::DnsOptions) -> Result<Self, Self::Error> { + use mullvad_types::settings::{ + CustomDnsOptions as MullvadCustomDnsOptions, + DefaultDnsOptions as MullvadDefaultDnsOptions, DnsOptions as MullvadDnsOptions, + DnsState as MullvadDnsState, + }; + + let state = match proto::dns_options::DnsState::from_i32(options.state) { + Some(proto::dns_options::DnsState::Default) => MullvadDnsState::Default, + Some(proto::dns_options::DnsState::Custom) => MullvadDnsState::Custom, + None => { + return Err(FromProtobufTypeError::InvalidArgument( + "invalid DNS options state", + )) + } + }; + + let default_options = + options + .default_options + .ok_or(FromProtobufTypeError::InvalidArgument( + "missing default DNS options", + ))?; + let custom_options = + options + .custom_options + .ok_or(FromProtobufTypeError::InvalidArgument( + "missing default DNS options", + ))?; + + Ok(MullvadDnsOptions { + state, + default_options: MullvadDefaultDnsOptions { + block_ads: default_options.block_ads, + block_trackers: default_options.block_trackers, + block_malware: default_options.block_malware, + block_adult_content: default_options.block_adult_content, + block_gambling: default_options.block_gambling, + }, + custom_options: MullvadCustomDnsOptions { + addresses: custom_options + .addresses + .into_iter() + .map(|addr| { + addr.parse().map_err(|_| { + FromProtobufTypeError::InvalidArgument("invalid IP address") + }) + }) + .collect::<Result<Vec<_>, _>>()?, + }, + }) + } +} diff --git a/mullvad-management-interface/src/types/conversions/states.rs b/mullvad-management-interface/src/types/conversions/states.rs new file mode 100644 index 0000000000..c462e77aa6 --- /dev/null +++ b/mullvad-management-interface/src/types/conversions/states.rs @@ -0,0 +1,318 @@ +use crate::types::{conversions::option_from_proto_string, proto, FromProtobufTypeError}; + +impl From<mullvad_types::states::TunnelState> for proto::TunnelState { + fn from(state: mullvad_types::states::TunnelState) -> Self { + use mullvad_types::states::TunnelState as MullvadTunnelState; + use proto::error_state::{ + firewall_policy_error::ErrorType as PolicyErrorType, Cause, FirewallPolicyError, + GenerationError, + }; + + use talpid_types::tunnel as talpid_tunnel; + + let map_firewall_error = + |firewall_error: &talpid_tunnel::FirewallPolicyError| match firewall_error { + talpid_tunnel::FirewallPolicyError::Generic => FirewallPolicyError { + r#type: i32::from(PolicyErrorType::Generic), + ..Default::default() + }, + #[cfg(windows)] + talpid_tunnel::FirewallPolicyError::Locked(blocking_app) => { + let (lock_pid, lock_name) = match blocking_app { + Some(app) => (app.pid, app.name.clone()), + None => (0, "".to_string()), + }; + + FirewallPolicyError { + r#type: i32::from(PolicyErrorType::Locked), + lock_pid, + lock_name, + } + } + }; + + let state = match state { + MullvadTunnelState::Disconnected => { + proto::tunnel_state::State::Disconnected(proto::tunnel_state::Disconnected {}) + } + MullvadTunnelState::Connecting { endpoint, location } => { + proto::tunnel_state::State::Connecting(proto::tunnel_state::Connecting { + relay_info: Some(proto::TunnelStateRelayInfo { + tunnel_endpoint: Some(proto::TunnelEndpoint::from(endpoint)), + location: location.map(proto::GeoIpLocation::from), + }), + }) + } + MullvadTunnelState::Connected { endpoint, location } => { + proto::tunnel_state::State::Connected(proto::tunnel_state::Connected { + relay_info: Some(proto::TunnelStateRelayInfo { + tunnel_endpoint: Some(proto::TunnelEndpoint::from(endpoint)), + location: location.map(proto::GeoIpLocation::from), + }), + }) + } + MullvadTunnelState::Disconnecting(after_disconnect) => { + proto::tunnel_state::State::Disconnecting(proto::tunnel_state::Disconnecting { + after_disconnect: match after_disconnect { + talpid_tunnel::ActionAfterDisconnect::Nothing => { + i32::from(proto::AfterDisconnect::Nothing) + } + talpid_tunnel::ActionAfterDisconnect::Block => { + i32::from(proto::AfterDisconnect::Block) + } + talpid_tunnel::ActionAfterDisconnect::Reconnect => { + i32::from(proto::AfterDisconnect::Reconnect) + } + }, + }) + } + MullvadTunnelState::Error(error_state) => { + proto::tunnel_state::State::Error(proto::tunnel_state::Error { + error_state: Some(proto::ErrorState { + cause: match error_state.cause() { + talpid_tunnel::ErrorStateCause::AuthFailed(_) => { + i32::from(Cause::AuthFailed) + } + talpid_tunnel::ErrorStateCause::Ipv6Unavailable => { + i32::from(Cause::Ipv6Unavailable) + } + talpid_tunnel::ErrorStateCause::SetFirewallPolicyError(_) => { + i32::from(Cause::SetFirewallPolicyError) + } + talpid_tunnel::ErrorStateCause::SetDnsError => { + i32::from(Cause::SetDnsError) + } + talpid_tunnel::ErrorStateCause::StartTunnelError => { + i32::from(Cause::StartTunnelError) + } + talpid_tunnel::ErrorStateCause::TunnelParameterError(_) => { + i32::from(Cause::TunnelParameterError) + } + talpid_tunnel::ErrorStateCause::IsOffline => { + i32::from(Cause::IsOffline) + } + #[cfg(target_os = "android")] + talpid_tunnel::ErrorStateCause::VpnPermissionDenied => { + i32::from(Cause::VpnPermissionDenied) + } + #[cfg(target_os = "windows")] + talpid_tunnel::ErrorStateCause::SplitTunnelError => { + i32::from(Cause::SplitTunnelError) + } + }, + blocking_error: error_state.block_failure().map(map_firewall_error), + auth_fail_reason: if let talpid_tunnel::ErrorStateCause::AuthFailed( + reason, + ) = error_state.cause() + { + reason.clone().unwrap_or_default() + } else { + "".to_string() + }, + parameter_error: + if let talpid_tunnel::ErrorStateCause::TunnelParameterError(reason) = + error_state.cause() + { + match reason { + talpid_tunnel::ParameterGenerationError::NoMatchingRelay => { + i32::from(GenerationError::NoMatchingRelay) + } + talpid_tunnel::ParameterGenerationError::NoMatchingBridgeRelay => { + i32::from(GenerationError::NoMatchingBridgeRelay) + } + talpid_tunnel::ParameterGenerationError::NoWireguardKey => { + i32::from(GenerationError::NoWireguardKey) + } + talpid_tunnel::ParameterGenerationError::CustomTunnelHostResultionError => { + i32::from(GenerationError::CustomTunnelHostResolutionError) + } + } + } else { + 0 + }, + policy_error: + if let talpid_tunnel::ErrorStateCause::SetFirewallPolicyError(reason) = + error_state.cause() + { + Some(map_firewall_error(reason)) + } else { + None + }, + }), + }) + } + }; + + proto::TunnelState { state: Some(state) } + } +} + +impl TryFrom<proto::TunnelState> for mullvad_types::states::TunnelState { + type Error = FromProtobufTypeError; + + fn try_from(state: proto::TunnelState) -> Result<Self, FromProtobufTypeError> { + use mullvad_types::states::TunnelState as MullvadState; + use talpid_types::{net as talpid_net, tunnel as talpid_tunnel}; + + let state = match state.state { + Some(proto::tunnel_state::State::Disconnected(_)) => MullvadState::Disconnected, + Some(proto::tunnel_state::State::Connecting(proto::tunnel_state::Connecting { + relay_info: + Some(proto::TunnelStateRelayInfo { + tunnel_endpoint: Some(tunnel_endpoint), + location, + }), + })) => MullvadState::Connecting { + endpoint: talpid_net::TunnelEndpoint::try_from(tunnel_endpoint)?, + location: location + .map(mullvad_types::location::GeoIpLocation::try_from) + .transpose()?, + }, + Some(proto::tunnel_state::State::Connected(proto::tunnel_state::Connected { + relay_info: + Some(proto::TunnelStateRelayInfo { + tunnel_endpoint: Some(tunnel_endpoint), + location, + }), + })) => MullvadState::Connected { + endpoint: talpid_net::TunnelEndpoint::try_from(tunnel_endpoint)?, + location: location + .map(mullvad_types::location::GeoIpLocation::try_from) + .transpose()?, + }, + Some(proto::tunnel_state::State::Disconnecting( + proto::tunnel_state::Disconnecting { after_disconnect }, + )) => MullvadState::Disconnecting( + match proto::AfterDisconnect::from_i32(after_disconnect) { + Some(proto::AfterDisconnect::Nothing) => { + talpid_tunnel::ActionAfterDisconnect::Nothing + } + Some(proto::AfterDisconnect::Block) => { + talpid_tunnel::ActionAfterDisconnect::Block + } + Some(proto::AfterDisconnect::Reconnect) => { + talpid_tunnel::ActionAfterDisconnect::Reconnect + } + _ => { + return Err(FromProtobufTypeError::InvalidArgument( + "invalid \"after_disconnect\" action", + )) + } + }, + ), + Some(proto::tunnel_state::State::Error(proto::tunnel_state::Error { + error_state: + Some(proto::ErrorState { + cause, + blocking_error, + auth_fail_reason, + parameter_error, + policy_error, + }), + })) => { + let cause = match proto::error_state::Cause::from_i32(cause) { + Some(proto::error_state::Cause::AuthFailed) => { + talpid_tunnel::ErrorStateCause::AuthFailed(option_from_proto_string( + auth_fail_reason, + )) + } + Some(proto::error_state::Cause::Ipv6Unavailable) => { + talpid_tunnel::ErrorStateCause::Ipv6Unavailable + } + Some(proto::error_state::Cause::IsOffline) => { + talpid_tunnel::ErrorStateCause::IsOffline + } + Some(proto::error_state::Cause::SetDnsError) => { + talpid_tunnel::ErrorStateCause::SetDnsError + } + Some(proto::error_state::Cause::SetFirewallPolicyError) => { + let policy_error = policy_error.ok_or( + FromProtobufTypeError::InvalidArgument("missing firewall policy error"), + )?; + let policy_error = try_firewall_policy_error_from_i32( + policy_error.r#type, + policy_error.lock_pid, + policy_error.lock_name, + )?; + talpid_tunnel::ErrorStateCause::SetFirewallPolicyError(policy_error) + } + Some(proto::error_state::Cause::StartTunnelError) => { + talpid_tunnel::ErrorStateCause::StartTunnelError + } + Some(proto::error_state::Cause::TunnelParameterError) => { + let parameter_error = match proto::error_state::GenerationError::from_i32(parameter_error) { + Some(proto::error_state::GenerationError::CustomTunnelHostResolutionError) => talpid_tunnel::ParameterGenerationError::CustomTunnelHostResultionError, + Some(proto::error_state::GenerationError::NoMatchingBridgeRelay) => talpid_tunnel::ParameterGenerationError::NoMatchingBridgeRelay, + Some(proto::error_state::GenerationError::NoMatchingRelay) => talpid_tunnel::ParameterGenerationError::NoMatchingRelay, + Some(proto::error_state::GenerationError::NoWireguardKey) => talpid_tunnel::ParameterGenerationError::NoWireguardKey, + _ => return Err(FromProtobufTypeError::InvalidArgument( + "invalid parameter error", + )), + }; + talpid_tunnel::ErrorStateCause::TunnelParameterError(parameter_error) + } + #[cfg(target_os = "android")] + Some(proto::error_state::Cause::VpnPermissionDenied) => { + talpid_tunnel::ErrorStateCause::VpnPermissionDenied + } + #[cfg(target_os = "windows")] + Some(proto::error_state::Cause::SplitTunnelError) => { + talpid_tunnel::ErrorStateCause::SplitTunnelError + } + _ => { + return Err(FromProtobufTypeError::InvalidArgument( + "invalid error cause", + )) + } + }; + + let block_failure = blocking_error + .map(|blocking_error| { + try_firewall_policy_error_from_i32( + blocking_error.r#type, + blocking_error.lock_pid, + blocking_error.lock_name, + ) + }) + .transpose()?; + + MullvadState::Error(talpid_tunnel::ErrorState::new(cause, block_failure)) + } + _ => { + return Err(FromProtobufTypeError::InvalidArgument( + "invalid tunnel state", + )) + } + }; + + Ok(state) + } +} + +#[cfg_attr(not(target_os = "windows"), allow(unused_variables))] +fn try_firewall_policy_error_from_i32( + policy_error: i32, + lock_pid: u32, + lock_name: String, +) -> Result<talpid_types::tunnel::FirewallPolicyError, FromProtobufTypeError> { + match proto::error_state::firewall_policy_error::ErrorType::from_i32(policy_error) { + Some(proto::error_state::firewall_policy_error::ErrorType::Generic) => { + Ok(talpid_types::tunnel::FirewallPolicyError::Generic) + } + #[cfg(windows)] + Some(proto::error_state::firewall_policy_error::ErrorType::Locked) => { + let blocking_app = option_from_proto_string(lock_name).map(|name| { + talpid_types::tunnel::BlockingApplication { + pid: lock_pid, + name, + } + }); + Ok(talpid_types::tunnel::FirewallPolicyError::Locked( + blocking_app, + )) + } + _ => Err(FromProtobufTypeError::InvalidArgument( + "invalid firewall policy error", + )), + } +} diff --git a/mullvad-management-interface/src/types/conversions/version.rs b/mullvad-management-interface/src/types/conversions/version.rs new file mode 100644 index 0000000000..0a21667695 --- /dev/null +++ b/mullvad-management-interface/src/types/conversions/version.rs @@ -0,0 +1,12 @@ +use crate::types::proto; + +impl From<mullvad_types::version::AppVersionInfo> for proto::AppVersionInfo { + fn from(version_info: mullvad_types::version::AppVersionInfo) -> Self { + Self { + supported: version_info.supported, + latest_stable: version_info.latest_stable, + latest_beta: version_info.latest_beta, + suggested_upgrade: version_info.suggested_upgrade.unwrap_or_default(), + } + } +} diff --git a/mullvad-management-interface/src/types/conversions/wireguard.rs b/mullvad-management-interface/src/types/conversions/wireguard.rs new file mode 100644 index 0000000000..e90e728c2f --- /dev/null +++ b/mullvad-management-interface/src/types/conversions/wireguard.rs @@ -0,0 +1,14 @@ +use crate::types::proto; +use prost_types::Timestamp; + +impl From<mullvad_types::wireguard::PublicKey> for proto::PublicKey { + fn from(public_key: mullvad_types::wireguard::PublicKey) -> Self { + proto::PublicKey { + key: public_key.key.as_bytes().to_vec(), + created: Some(Timestamp { + seconds: public_key.created.timestamp(), + nanos: 0, + }), + } + } +} diff --git a/mullvad-management-interface/src/types/mod.rs b/mullvad-management-interface/src/types/mod.rs new file mode 100644 index 0000000000..31b87aa732 --- /dev/null +++ b/mullvad-management-interface/src/types/mod.rs @@ -0,0 +1,10 @@ +#[allow(clippy::derive_partial_eq_without_eq)] +mod proto { + tonic::include_proto!("mullvad_daemon.management_interface"); +} +mod conversions; + +pub use prost_types::{Duration, Timestamp}; + +pub use conversions::*; +pub use proto::*; diff --git a/mullvad-types/src/relay_constraints.rs b/mullvad-types/src/relay_constraints.rs index 5119bc72b8..bf1c7c4e43 100644 --- a/mullvad-types/src/relay_constraints.rs +++ b/mullvad-types/src/relay_constraints.rs @@ -390,6 +390,10 @@ impl Providers { } Ok(providers) } + + pub fn into_vec(self) -> Vec<Provider> { + self.providers.into_iter().collect() + } } impl Match<Relay> for Providers { diff --git a/talpid-types/src/net/mod.rs b/talpid-types/src/net/mod.rs index 20ef780f5c..7e2ef2d808 100644 --- a/talpid-types/src/net/mod.rs +++ b/talpid-types/src/net/mod.rs @@ -179,10 +179,9 @@ pub enum ObfuscationType { impl fmt::Display for ObfuscationType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - let obfuscation = match self { - ObfuscationType::Udp2Tcp => "Udp2Tcp", - }; - write!(f, "{}", obfuscation) + match self { + ObfuscationType::Udp2Tcp => "Udp2Tcp".fmt(f), + } } } @@ -214,8 +213,7 @@ impl From<&ObfuscatorConfig> for ObfuscationEndpoint { impl fmt::Display for ObfuscationEndpoint { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "{} {}", self.obfuscation_type, self.endpoint)?; - Ok(()) + write!(f, "{} {}", self.obfuscation_type, self.endpoint) } } |
