From 65e05d96446ab7aa49573a3a43d7843af81b23c9 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Fri, 18 Oct 2024 09:09:10 +0200 Subject: [PATCH 01/12] Wire up Mullvad Encrypted DNS proxy in `mullvad-api` --- Cargo.lock | 1 + mullvad-api/Cargo.toml | 1 + mullvad-api/src/https_client_with_sni.rs | 21 +++++++++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index acec671f715a..88d6cb9df6a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2399,6 +2399,7 @@ dependencies = [ "ipnetwork", "libc", "log", + "mullvad-encrypted-dns-proxy", "mullvad-fs", "mullvad-types", "rustls-pemfile 2.1.3", diff --git a/mullvad-api/Cargo.toml b/mullvad-api/Cargo.toml index ebb456ad6fca..e617d942b522 100644 --- a/mullvad-api/Cargo.toml +++ b/mullvad-api/Cargo.toml @@ -33,6 +33,7 @@ tokio-rustls = { version = "0.26.0", features = ["logging", "tls12", "ring"], de tokio-socks = "0.5.1" rustls-pemfile = "2.1.3" +mullvad-encrypted-dns-proxy = { path = "../mullvad-encrypted-dns-proxy" } mullvad-fs = { path = "../mullvad-fs" } mullvad-types = { path = "../mullvad-types" } talpid-types = { path = "../talpid-types" } diff --git a/mullvad-api/src/https_client_with_sni.rs b/mullvad-api/src/https_client_with_sni.rs index 574d0a63098a..b8b908784cb2 100644 --- a/mullvad-api/src/https_client_with_sni.rs +++ b/mullvad-api/src/https_client_with_sni.rs @@ -13,6 +13,9 @@ use hyper_util::{ client::legacy::connect::dns::{GaiResolver, Name}, rt::TokioIo, }; +use mullvad_encrypted_dns_proxy::{ + config::ProxyConfig as EncryptedDNSConfig, Forwarder as EncryptedDNSForwarder, +}; use shadowsocks::{ config::ServerType, context::{Context as SsContext, SharedContext}, @@ -78,6 +81,10 @@ enum InnerConnectionMode { Shadowsocks(ShadowsocksConfig), /// Connect to the destination via a Socks proxy. Socks5(SocksConfig), + /// Connect to the destination via Mullvad Encrypted DNS proxy. + /// See [`mullvad-encrypted-dns-proxy`] for how the proxy works. + #[allow(dead_code)] // TODO: Remove this allow + EncryptedDnsProxy(EncryptedDNSConfig), } impl InnerConnectionMode { @@ -153,6 +160,20 @@ impl InnerConnectionMode { ) .await } + InnerConnectionMode::EncryptedDnsProxy(proxy_config) => { + let first_hop = SocketAddr::V4(proxy_config.addr); + let make_proxy_stream = |tcp_stream| async { + EncryptedDNSForwarder::from_stream(&proxy_config, tcp_stream) + }; + Self::connect_proxied( + first_hop, + hostname, + make_proxy_stream, + #[cfg(target_os = "android")] + socket_bypass_tx, + ) + .await + } } } From 4d51fe6c0df45ee6ec911a67421d7994d9993cc5 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Fri, 18 Oct 2024 09:59:15 +0200 Subject: [PATCH 02/12] Define the built-in access method "Encrypted DNS proxy" --- mullvad-daemon/src/api.rs | 3 +++ .../proto/management_interface.proto | 7 ++++-- .../src/types/conversions/access_method.rs | 23 +++++++++++++++++++ mullvad-types/src/access_method.rs | 20 +++++++++++++--- 4 files changed, 48 insertions(+), 5 deletions(-) diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs index ac54382a57eb..930bba42c6c5 100644 --- a/mullvad-daemon/src/api.rs +++ b/mullvad-daemon/src/api.rs @@ -536,6 +536,9 @@ fn resolve_connection_mode( ); ApiConnectionMode::Direct }), + AccessMethod::BuiltIn(BuiltInAccessMethod::EncryptedDnsProxy) => { + todo!("Implement me!") + } AccessMethod::Custom(config) => ApiConnectionMode::Proxied(ProxyConfig::from(config)), } } diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index b57d63dcf3bd..62740438fbfa 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -411,10 +411,12 @@ message CustomProxy { message AccessMethod { message Direct {} message Bridges {} + message EncryptedDnsProxy {} oneof access_method { Direct direct = 1; Bridges bridges = 2; - CustomProxy custom = 3; + EncryptedDnsProxy encrypted_dns_proxy = 3; + CustomProxy custom = 4; } } @@ -434,7 +436,8 @@ message NewAccessMethodSetting { message ApiAccessMethodSettings { AccessMethodSetting direct = 1; AccessMethodSetting mullvad_bridges = 2; - repeated AccessMethodSetting custom = 3; + AccessMethodSetting encrypted_dns_proxy = 3; + repeated AccessMethodSetting custom = 4; } message Settings { diff --git a/mullvad-management-interface/src/types/conversions/access_method.rs b/mullvad-management-interface/src/types/conversions/access_method.rs index 9f45957db6b8..be3b4c20a801 100644 --- a/mullvad-management-interface/src/types/conversions/access_method.rs +++ b/mullvad-management-interface/src/types/conversions/access_method.rs @@ -10,6 +10,7 @@ mod settings { Self { direct: Some(settings.direct().clone().into()), mullvad_bridges: Some(settings.mullvad_bridges().clone().into()), + encrypted_dns_proxy: Some(settings.encrypted_dns_proxy().clone().into()), custom: settings .iter_custom() .cloned() @@ -37,6 +38,13 @@ mod settings { )) .and_then(access_method::AccessMethodSetting::try_from)?; + let encrypted_dns_proxy = settings + .encrypted_dns_proxy + .ok_or(FromProtobufTypeError::InvalidArgument( + "Could not deserialize Encrypted DNS proxy Access Method from protobuf", + )) + .and_then(access_method::AccessMethodSetting::try_from)?; + let custom = settings .custom .iter() @@ -46,6 +54,7 @@ mod settings { Ok(access_method::Settings::new( direct, mullvad_bridges, + encrypted_dns_proxy, custom, )) } @@ -118,6 +127,9 @@ mod data { Ok(match access_method { proto::access_method::AccessMethod::Direct(direct) => AccessMethod::from(direct), proto::access_method::AccessMethod::Bridges(bridge) => AccessMethod::from(bridge), + proto::access_method::AccessMethod::EncryptedDnsProxy(proxy) => { + AccessMethod::from(proxy) + } proto::access_method::AccessMethod::Custom(custom) => { CustomProxy::try_from(custom).map(AccessMethod::from)? } @@ -146,6 +158,12 @@ mod data { } } + impl From for AccessMethod { + fn from(_value: proto::access_method::EncryptedDnsProxy) -> Self { + AccessMethod::from(BuiltInAccessMethod::EncryptedDnsProxy) + } + } + impl TryFrom for AccessMethod { type Error = FromProtobufTypeError; @@ -187,6 +205,11 @@ mod data { mullvad_types::access_method::BuiltInAccessMethod::Bridge => { proto::access_method::AccessMethod::Bridges(proto::access_method::Bridges {}) } + mullvad_types::access_method::BuiltInAccessMethod::EncryptedDnsProxy => { + proto::access_method::AccessMethod::EncryptedDnsProxy( + proto::access_method::EncryptedDnsProxy {}, + ) + } } } } diff --git a/mullvad-types/src/access_method.rs b/mullvad-types/src/access_method.rs index 2b375efb920e..1f2e9a85afd3 100644 --- a/mullvad-types/src/access_method.rs +++ b/mullvad-types/src/access_method.rs @@ -6,6 +6,7 @@ use talpid_types::net::proxy::{CustomProxy, Shadowsocks, Socks5Local, Socks5Remo pub struct Settings { direct: AccessMethodSetting, mullvad_bridges: AccessMethodSetting, + encrypted_dns_proxy: AccessMethodSetting, /// Custom API access methods. custom: Vec, } @@ -14,11 +15,13 @@ impl Settings { pub fn new( direct: AccessMethodSetting, mullvad_bridges: AccessMethodSetting, + encrypted_dns_proxy: AccessMethodSetting, custom: Vec, ) -> Settings { Settings { direct, mullvad_bridges, + encrypted_dns_proxy, custom, } } @@ -93,6 +96,7 @@ impl Settings { use std::iter::once; once(&self.direct) .chain(once(&self.mullvad_bridges)) + .chain(once(&self.encrypted_dns_proxy)) .chain(&self.custom) } @@ -112,9 +116,7 @@ impl Settings { /// Return the total number of access methods. /// This counts both enabled and disabled [`AccessMethodSetting`]s. pub fn cardinality(&self) -> usize { - 1 + // 'Direct' - 1 + // 'Mullvad bridges' - self.custom.len() + self.iter().count() } pub fn direct(&self) -> &AccessMethodSetting { @@ -125,6 +127,10 @@ impl Settings { &self.mullvad_bridges } + pub fn encrypted_dns_proxy(&self) -> &AccessMethodSetting { + &self.encrypted_dns_proxy + } + fn create_direct() -> AccessMethodSetting { let method = BuiltInAccessMethod::Direct; AccessMethodSetting::new(method.canonical_name(), true, AccessMethod::from(method)) @@ -134,6 +140,11 @@ impl Settings { let method = BuiltInAccessMethod::Bridge; AccessMethodSetting::new(method.canonical_name(), true, AccessMethod::from(method)) } + + fn create_encrypted_dns_proxy() -> AccessMethodSetting { + let method = BuiltInAccessMethod::EncryptedDnsProxy; + AccessMethodSetting::new(method.canonical_name(), true, AccessMethod::from(method)) + } } impl Default for Settings { @@ -141,6 +152,7 @@ impl Default for Settings { Self { direct: Settings::create_direct(), mullvad_bridges: Settings::create_mullvad_bridges(), + encrypted_dns_proxy: Settings::create_encrypted_dns_proxy(), custom: vec![], } } @@ -271,6 +283,7 @@ impl AccessMethodSetting { pub enum BuiltInAccessMethod { Direct, Bridge, + EncryptedDnsProxy, } impl AccessMethod { @@ -287,6 +300,7 @@ impl BuiltInAccessMethod { match self { BuiltInAccessMethod::Direct => "Direct".to_string(), BuiltInAccessMethod::Bridge => "Mullvad Bridges".to_string(), + BuiltInAccessMethod::EncryptedDnsProxy => "Encrypted DNS proxy".to_string(), } } } From 9e99d2aa2d9df2d39002c15e75ccf44ccb7ead20 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Fri, 18 Oct 2024 11:08:31 +0200 Subject: [PATCH 03/12] Enable Encrypted DNS proxy access method in the daemon --- Cargo.lock | 2 + mullvad-api/src/https_client_with_sni.rs | 4 +- mullvad-api/src/proxy.rs | 9 +++ mullvad-daemon/Cargo.toml | 1 + mullvad-daemon/src/api.rs | 55 ++++++++++++++++--- mullvad-encrypted-dns-proxy/Cargo.toml | 1 + mullvad-encrypted-dns-proxy/src/config/mod.rs | 5 +- mullvad-encrypted-dns-proxy/src/config/xor.rs | 4 +- mullvad-types/src/access_method.rs | 1 + 9 files changed, 70 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 88d6cb9df6a7..08ef9d489709 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2455,6 +2455,7 @@ dependencies = [ "log", "log-panics", "mullvad-api", + "mullvad-encrypted-dns-proxy", "mullvad-fs", "mullvad-management-interface", "mullvad-paths", @@ -2491,6 +2492,7 @@ dependencies = [ "hickory-resolver", "log", "rustls 0.21.11", + "serde", "tokio", "webpki-roots", ] diff --git a/mullvad-api/src/https_client_with_sni.rs b/mullvad-api/src/https_client_with_sni.rs index b8b908784cb2..898927513f96 100644 --- a/mullvad-api/src/https_client_with_sni.rs +++ b/mullvad-api/src/https_client_with_sni.rs @@ -83,7 +83,6 @@ enum InnerConnectionMode { Socks5(SocksConfig), /// Connect to the destination via Mullvad Encrypted DNS proxy. /// See [`mullvad-encrypted-dns-proxy`] for how the proxy works. - #[allow(dead_code)] // TODO: Remove this allow EncryptedDnsProxy(EncryptedDNSConfig), } @@ -277,6 +276,9 @@ impl TryFrom for InnerConnectionMode { peer: config.endpoint, authentication: config.auth, }), + ProxyConfig::EncryptedDnsProxy(config) => { + InnerConnectionMode::EncryptedDnsProxy(config) + } }, }) } diff --git a/mullvad-api/src/proxy.rs b/mullvad-api/src/proxy.rs index 2fb2c4bf357e..3cf38acab07d 100644 --- a/mullvad-api/src/proxy.rs +++ b/mullvad-api/src/proxy.rs @@ -2,6 +2,7 @@ use hyper_util::client::legacy::connect::{Connected, Connection}; use serde::{Deserialize, Serialize}; use std::{ fmt, io, + net::SocketAddr, path::Path, pin::Pin, task::{self, Poll}, @@ -74,6 +75,7 @@ pub enum ProxyConfig { Shadowsocks(proxy::Shadowsocks), Socks5Local(proxy::Socks5Local), Socks5Remote(proxy::Socks5Remote), + EncryptedDnsProxy(mullvad_encrypted_dns_proxy::config::ProxyConfig), } impl ProxyConfig { @@ -87,6 +89,10 @@ impl ProxyConfig { ProxyConfig::Socks5Remote(remote) => { Endpoint::from_socket_address(remote.endpoint, TransportProtocol::Tcp) } + ProxyConfig::EncryptedDnsProxy(proxy) => { + let addr = SocketAddr::V4(proxy.addr); + Endpoint::from_socket_address(addr, TransportProtocol::Tcp) + } } } } @@ -100,6 +106,9 @@ impl fmt::Display for ProxyConfig { ProxyConfig::Socks5Local(local) => { write!(f, "Socks5 {} via localhost:{}", endpoint, local.local_port) } + ProxyConfig::EncryptedDnsProxy(proxy) => { + write!(f, "ApiSusan {}", proxy.addr) + } } } } diff --git a/mullvad-daemon/Cargo.toml b/mullvad-daemon/Cargo.toml index 9342062d36ee..778ef02d7f9f 100644 --- a/mullvad-daemon/Cargo.toml +++ b/mullvad-daemon/Cargo.toml @@ -31,6 +31,7 @@ tokio-stream = "0.1" mullvad-relay-selector = { path = "../mullvad-relay-selector" } mullvad-types = { path = "../mullvad-types" } mullvad-api = { path = "../mullvad-api" } +mullvad-encrypted-dns-proxy = { path = "../mullvad-encrypted-dns-proxy" } mullvad-fs = { path = "../mullvad-fs" } mullvad-paths = { path = "../mullvad-paths" } mullvad-version = { path = "../mullvad-version" } diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs index 930bba42c6c5..87bf660a7976 100644 --- a/mullvad-daemon/src/api.rs +++ b/mullvad-daemon/src/api.rs @@ -15,6 +15,7 @@ use mullvad_api::{ proxy::{ApiConnectionMode, ConnectionModeProvider, ProxyConfig}, AddressCache, }; +use mullvad_encrypted_dns_proxy::state::EncryptedDnsProxyState; use mullvad_relay_selector::RelaySelector; use mullvad_types::access_method::{ AccessMethod, AccessMethodSetting, BuiltInAccessMethod, Id, Settings, @@ -239,6 +240,8 @@ pub struct AccessModeSelector { cache_dir: PathBuf, /// Used for selecting a Bridge when the `Mullvad Bridges` access method is used. relay_selector: RelaySelector, + /// Used for selecting a config for the 'Encrypted DNS proxy' access method. + encrypted_dns_proxy_cache: EncryptedDnsProxyState, access_method_settings: Settings, address_cache: AddressCache, access_method_event_sender: DaemonEventSender<(AccessMethodEvent, oneshot::Sender<()>)>, @@ -267,10 +270,26 @@ impl AccessModeSelector { } } + // Initialize the Encrypted DNS cache + let mut encrypted_dns_proxy_cache = { + // Initialize an empty cache + let mut cache = EncryptedDnsProxyState::default(); + // Hydrate the cache by fetching new proxy configs. + if let Err(_error) = cache.fetch_configs().await { + // TODO: What should we do if we initially fail to fetch configs? Handle later. + } + cache + }; + // Always start looking from the position of `Direct`. let (index, next) = Self::find_next_active(0, &access_method_settings); - let initial_connection_mode = - Self::resolve_inner(next, &relay_selector, &address_cache).await; + let initial_connection_mode = Self::resolve_inner( + next, + &relay_selector, + &mut encrypted_dns_proxy_cache, + &address_cache, + ) + .await; let (change_tx, change_rx) = mpsc::unbounded(); @@ -280,6 +299,7 @@ impl AccessModeSelector { cmd_rx, cache_dir, relay_selector, + encrypted_dns_proxy_cache, access_method_settings, address_cache, access_method_event_sender, @@ -496,16 +516,28 @@ impl AccessModeSelector { } async fn resolve(&mut self, access_method: AccessMethodSetting) -> ResolvedConnectionMode { - Self::resolve_inner(access_method, &self.relay_selector, &self.address_cache).await + // TODO: Should we fetch new configs here everytime? + // self.encrypted_dns_proxy_cache.fetch_configs().await; + Self::resolve_inner( + access_method, + &self.relay_selector, + &mut self.encrypted_dns_proxy_cache, + &self.address_cache, + ) + .await } async fn resolve_inner( access_method: AccessMethodSetting, relay_selector: &RelaySelector, + encrypted_dns_proxy_cache: &mut EncryptedDnsProxyState, address_cache: &AddressCache, ) -> ResolvedConnectionMode { - let connection_mode = - resolve_connection_mode(access_method.access_method.clone(), relay_selector); + let connection_mode = resolve_connection_mode( + access_method.access_method.clone(), + relay_selector, + encrypted_dns_proxy_cache, + ); let endpoint = resolve_allowed_endpoint(&connection_mode, address_cache.get_address().await); ResolvedConnectionMode { @@ -523,6 +555,7 @@ impl AccessModeSelector { fn resolve_connection_mode( access_method: AccessMethod, relay_selector: &RelaySelector, + encrypted_dns_proxy_cache: &mut EncryptedDnsProxyState, ) -> ApiConnectionMode { match access_method { AccessMethod::BuiltIn(BuiltInAccessMethod::Direct) => ApiConnectionMode::Direct, @@ -536,9 +569,15 @@ fn resolve_connection_mode( ); ApiConnectionMode::Direct }), - AccessMethod::BuiltIn(BuiltInAccessMethod::EncryptedDnsProxy) => { - todo!("Implement me!") - } + AccessMethod::BuiltIn(BuiltInAccessMethod::EncryptedDnsProxy) => encrypted_dns_proxy_cache + .next_configuration() + .map(ProxyConfig::EncryptedDnsProxy) + .map(ApiConnectionMode::Proxied) + .unwrap_or_else(|| { + log::error!("Could not select next Encrypted DNS proxy config"); + log::error!("Defaulting to direct API connection"); + ApiConnectionMode::Direct + }), AccessMethod::Custom(config) => ApiConnectionMode::Proxied(ProxyConfig::from(config)), } } diff --git a/mullvad-encrypted-dns-proxy/Cargo.toml b/mullvad-encrypted-dns-proxy/Cargo.toml index 4f101e3ed075..da2a9c15524a 100644 --- a/mullvad-encrypted-dns-proxy/Cargo.toml +++ b/mullvad-encrypted-dns-proxy/Cargo.toml @@ -14,6 +14,7 @@ workspace = true tokio = { workspace = true, features = [ "macros" ] } log = { workspace = true } hickory-resolver = { version = "0.24.1", features = [ "dns-over-https-rustls" ]} +serde = { workspace = true } webpki-roots = "0.25.0" rustls = "0.21" diff --git a/mullvad-encrypted-dns-proxy/src/config/mod.rs b/mullvad-encrypted-dns-proxy/src/config/mod.rs index bd75fd25eb04..70f3b62af4dd 100644 --- a/mullvad-encrypted-dns-proxy/src/config/mod.rs +++ b/mullvad-encrypted-dns-proxy/src/config/mod.rs @@ -7,6 +7,7 @@ use std::net::{Ipv6Addr, SocketAddrV4}; mod plain; mod xor; +use serde::{Deserialize, Serialize}; pub use xor::XorKey; /// All the errors that can happen during deserialization of a [`ProxyConfig`]. @@ -67,7 +68,7 @@ pub trait Obfuscator: Send { /// Represents a Mullvad Encrypted DNS proxy configuration. Created by parsing /// the config out of an IPv6 address resolved over DoH. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] +#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] pub struct ProxyConfig { /// The remote address to connect to the proxy over. This is the address /// on the internet where the proxy is listening. @@ -77,7 +78,7 @@ pub struct ProxyConfig { pub obfuscation: Option, } -#[derive(Debug, Clone, Eq, PartialEq, Hash)] +#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] pub enum ObfuscationConfig { XorV2(xor::XorKey), } diff --git a/mullvad-encrypted-dns-proxy/src/config/xor.rs b/mullvad-encrypted-dns-proxy/src/config/xor.rs index b4dd2dbdf21b..eb4f314e13c8 100644 --- a/mullvad-encrypted-dns-proxy/src/config/xor.rs +++ b/mullvad-encrypted-dns-proxy/src/config/xor.rs @@ -1,6 +1,8 @@ use core::fmt; use std::net::{Ipv4Addr, SocketAddrV4}; +use serde::{Deserialize, Serialize}; + /// Parse a proxy config that XORs all traffic with the given key. /// /// A Xor configuration is represented by the proxy type `ProxyType::XorV2`. There used to be a `XorV1`, but it @@ -37,7 +39,7 @@ pub fn parse_xor(data: [u8; 12]) -> Result { /// A bunch of bytes, representing a "key" Simply meaning a slice of bytes that the data /// will be XORed with. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] pub struct XorKey { data: [u8; 6], len: usize, diff --git a/mullvad-types/src/access_method.rs b/mullvad-types/src/access_method.rs index 1f2e9a85afd3..6ef3de0ea090 100644 --- a/mullvad-types/src/access_method.rs +++ b/mullvad-types/src/access_method.rs @@ -105,6 +105,7 @@ impl Settings { use std::iter::once; once(&mut self.direct) .chain(once(&mut self.mullvad_bridges)) + .chain(once(&mut self.encrypted_dns_proxy)) .chain(&mut self.custom) } From a9d5994b0fb425c5a3eabc0591e341be308bdfb7 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Mon, 21 Oct 2024 13:14:26 +0200 Subject: [PATCH 04/12] Do not implement `std::fmt::Display` for `ProxyConfig` Use the Debug implementation in the one case where the Display implementation was used. --- mullvad-api/src/proxy.rs | 27 +-------------------------- mullvad-daemon/src/api.rs | 9 +++------ 2 files changed, 4 insertions(+), 32 deletions(-) diff --git a/mullvad-api/src/proxy.rs b/mullvad-api/src/proxy.rs index 3cf38acab07d..279a28928926 100644 --- a/mullvad-api/src/proxy.rs +++ b/mullvad-api/src/proxy.rs @@ -1,7 +1,7 @@ use hyper_util::client::legacy::connect::{Connected, Connection}; use serde::{Deserialize, Serialize}; use std::{ - fmt, io, + io, net::SocketAddr, path::Path, pin::Pin, @@ -61,15 +61,6 @@ pub enum ApiConnectionMode { Proxied(ProxyConfig), } -impl fmt::Display for ApiConnectionMode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - match self { - ApiConnectionMode::Direct => write!(f, "unproxied"), - ApiConnectionMode::Proxied(settings) => settings.fmt(f), - } - } -} - #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub enum ProxyConfig { Shadowsocks(proxy::Shadowsocks), @@ -97,22 +88,6 @@ impl ProxyConfig { } } -impl fmt::Display for ProxyConfig { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - let endpoint = self.get_endpoint(); - match self { - ProxyConfig::Shadowsocks(_) => write!(f, "Shadowsocks {}", endpoint), - ProxyConfig::Socks5Remote(_) => write!(f, "Socks5 {}", endpoint), - ProxyConfig::Socks5Local(local) => { - write!(f, "Socks5 {} via localhost:{}", endpoint, local.local_port) - } - ProxyConfig::EncryptedDnsProxy(proxy) => { - write!(f, "ApiSusan {}", proxy.addr) - } - } - } -} - impl From for ProxyConfig { fn from(value: proxy::CustomProxy) -> Self { match value { diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs index 87bf660a7976..f9e7fb12c662 100644 --- a/mullvad-daemon/src/api.rs +++ b/mullvad-daemon/src/api.rs @@ -425,13 +425,10 @@ impl AccessModeSelector { // Save the new connection mode to cache! let cache_dir = self.cache_dir.clone(); - let new_connection_mode = resolved.connection_mode.clone(); + let connection_mode = resolved.connection_mode.clone(); tokio::spawn(async move { - if new_connection_mode.save(&cache_dir).await.is_err() { - log::warn!( - "Failed to save {connection_mode} to cache", - connection_mode = new_connection_mode - ) + if connection_mode.save(&cache_dir).await.is_err() { + log::warn!("Failed to save {connection_mode:#?} to cache") } }); From 6a149da48d8831f7f6990e0ff2d361f3f835a8df Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Tue, 22 Oct 2024 15:11:15 +0200 Subject: [PATCH 05/12] Fetch new Encrypted DNS configs when the access method is engaged --- mullvad-daemon/src/api.rs | 83 ++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 50 deletions(-) diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs index f9e7fb12c662..622c9e8c8de4 100644 --- a/mullvad-daemon/src/api.rs +++ b/mullvad-daemon/src/api.rs @@ -271,15 +271,7 @@ impl AccessModeSelector { } // Initialize the Encrypted DNS cache - let mut encrypted_dns_proxy_cache = { - // Initialize an empty cache - let mut cache = EncryptedDnsProxyState::default(); - // Hydrate the cache by fetching new proxy configs. - if let Err(_error) = cache.fetch_configs().await { - // TODO: What should we do if we initially fail to fetch configs? Handle later. - } - cache - }; + let mut encrypted_dns_proxy_cache = EncryptedDnsProxyState::default(); // Always start looking from the position of `Direct`. let (index, next) = Self::find_next_active(0, &access_method_settings); @@ -513,8 +505,6 @@ impl AccessModeSelector { } async fn resolve(&mut self, access_method: AccessMethodSetting) -> ResolvedConnectionMode { - // TODO: Should we fetch new configs here everytime? - // self.encrypted_dns_proxy_cache.fetch_configs().await; Self::resolve_inner( access_method, &self.relay_selector, @@ -530,11 +520,38 @@ impl AccessModeSelector { encrypted_dns_proxy_cache: &mut EncryptedDnsProxyState, address_cache: &AddressCache, ) -> ResolvedConnectionMode { - let connection_mode = resolve_connection_mode( - access_method.access_method.clone(), - relay_selector, - encrypted_dns_proxy_cache, - ); + let connection_mode = { + let access_method = access_method.access_method.clone(); + match access_method { + AccessMethod::BuiltIn(BuiltInAccessMethod::Direct) => ApiConnectionMode::Direct, + AccessMethod::BuiltIn(BuiltInAccessMethod::Bridge) => relay_selector + .get_bridge_forced() + .map(ProxyConfig::from) + .map(ApiConnectionMode::Proxied) + .unwrap_or_else(|| { + log::warn!( + "Received unexpected proxy settings type. Defaulting to direct API connection" + ); + log::debug!("Defaulting to direct API connection"); + ApiConnectionMode::Direct + }), + AccessMethod::BuiltIn(BuiltInAccessMethod::EncryptedDnsProxy) => { + if let Err(error) = encrypted_dns_proxy_cache.fetch_configs().await { + log::warn!("Failed to fetch new Encrypted DNS Proxy configurations"); + log::debug!("{error:#?}"); + } + encrypted_dns_proxy_cache + .next_configuration() + .map(ProxyConfig::EncryptedDnsProxy) + .map(ApiConnectionMode::Proxied) + .unwrap_or_else(|| { + log::warn!("Could not select next Encrypted DNS proxy config"); + log::debug!("Defaulting to direct API connection"); + ApiConnectionMode::Direct + })}, + AccessMethod::Custom(config) => ApiConnectionMode::Proxied(ProxyConfig::from(config)), + } + }; let endpoint = resolve_allowed_endpoint(&connection_mode, address_cache.get_address().await); ResolvedConnectionMode { @@ -545,40 +562,6 @@ impl AccessModeSelector { } } -/// Ad-hoc version of [`std::convert::From::from`], but since some -/// [`ApiConnectionMode`]s require extra logic/data from [`RelaySelector`] to be -/// instantiated the standard [`std::convert::From`] trait can not be -/// implemented. -fn resolve_connection_mode( - access_method: AccessMethod, - relay_selector: &RelaySelector, - encrypted_dns_proxy_cache: &mut EncryptedDnsProxyState, -) -> ApiConnectionMode { - match access_method { - AccessMethod::BuiltIn(BuiltInAccessMethod::Direct) => ApiConnectionMode::Direct, - AccessMethod::BuiltIn(BuiltInAccessMethod::Bridge) => relay_selector - .get_bridge_forced() - .map(ProxyConfig::from) - .map(ApiConnectionMode::Proxied) - .unwrap_or_else(|| { - log::error!( - "Received unexpected proxy settings type. Defaulting to direct API connection" - ); - ApiConnectionMode::Direct - }), - AccessMethod::BuiltIn(BuiltInAccessMethod::EncryptedDnsProxy) => encrypted_dns_proxy_cache - .next_configuration() - .map(ProxyConfig::EncryptedDnsProxy) - .map(ApiConnectionMode::Proxied) - .unwrap_or_else(|| { - log::error!("Could not select next Encrypted DNS proxy config"); - log::error!("Defaulting to direct API connection"); - ApiConnectionMode::Direct - }), - AccessMethod::Custom(config) => ApiConnectionMode::Proxied(ProxyConfig::from(config)), - } -} - pub fn resolve_allowed_endpoint( connection_mode: &ApiConnectionMode, fallback: SocketAddr, From 2ba727134060eb434c8335c0ae0970ba3e4f21cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20G=C3=B6ransson?= Date: Tue, 22 Oct 2024 14:45:39 +0200 Subject: [PATCH 06/12] Add support Encrypted DNS Proxy --- .../mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt | 10 +++++++--- .../mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt | 8 +++++++- .../mullvad/mullvadvpn/lib/model/ApiAccessMethod.kt | 2 ++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt index 817418e1fc72..622e95d9dd0d 100644 --- a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt +++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt @@ -188,7 +188,11 @@ internal fun ApiAccessMethod.fromDomain(): ManagementInterface.AccessMethod = it.setDirect(ManagementInterface.AccessMethod.Direct.getDefaultInstance()) ApiAccessMethod.Bridges -> it.setBridges(ManagementInterface.AccessMethod.Bridges.getDefaultInstance()) - is ApiAccessMethod.CustomProxy -> it.setCustom(this.fromDomain()) + is ApiAccessMethod.CustomProxy -> it.setCustom(fromDomain()) + is ApiAccessMethod.EncryptedDns -> + it.setEncryptedDnsProxy( + ManagementInterface.AccessMethod.EncryptedDnsProxy.getDefaultInstance() + ) } } .build() @@ -197,8 +201,8 @@ internal fun ApiAccessMethod.CustomProxy.fromDomain(): ManagementInterface.Custo ManagementInterface.CustomProxy.newBuilder() .let { when (this) { - is ApiAccessMethod.CustomProxy.Shadowsocks -> it.setShadowsocks(this.fromDomain()) - is ApiAccessMethod.CustomProxy.Socks5Remote -> it.setSocks5Remote(this.fromDomain()) + is ApiAccessMethod.CustomProxy.Shadowsocks -> it.setShadowsocks(fromDomain()) + is ApiAccessMethod.CustomProxy.Socks5Remote -> it.setSocks5Remote(fromDomain()) } } .build() diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt index bb7fb83fa607..fc4c64942f25 100644 --- a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt +++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt @@ -557,7 +557,12 @@ internal fun ManagementInterface.PlayPurchasePaymentToken.toDomain(): PlayPurcha PlayPurchasePaymentToken(value = token) internal fun ManagementInterface.ApiAccessMethodSettings.toDomain(): List = - listOf(direct.toDomain(), mullvadBridges.toDomain()).plus(customList.map { it.toDomain() }) + buildList { + add(direct.toDomain()) + add(mullvadBridges.toDomain()) + add(encryptedDnsProxy.toDomain()) + addAll(customList.map { it.toDomain() }) + } internal fun ManagementInterface.AccessMethodSetting.toDomain(): ApiAccessMethodSetting = ApiAccessMethodSetting( @@ -571,6 +576,7 @@ internal fun ManagementInterface.AccessMethod.toDomain(): ApiAccessMethod = when { hasDirect() -> ApiAccessMethod.Direct hasBridges() -> ApiAccessMethod.Bridges + hasEncryptedDnsProxy() -> ApiAccessMethod.EncryptedDns hasCustom() -> custom.toDomain() else -> error("Type not found") } diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ApiAccessMethod.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ApiAccessMethod.kt index 3fdcf8c73009..dde768cfea8b 100644 --- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ApiAccessMethod.kt +++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ApiAccessMethod.kt @@ -8,6 +8,8 @@ sealed interface ApiAccessMethod : Parcelable { @Parcelize data object Bridges : ApiAccessMethod + @Parcelize data object EncryptedDns : ApiAccessMethod + sealed interface CustomProxy : ApiAccessMethod { @Parcelize data class Socks5Remote(val ip: String, val port: Port, val auth: SocksAuth?) : CustomProxy From b83716e758ec7cd0153d2b30749da57e80b24132 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Fri, 18 Oct 2024 12:50:35 +0200 Subject: [PATCH 07/12] Add 'Encrypted DNS proxy' built-in access method to GUI --- gui/src/main/default-settings.ts | 6 ++++++ gui/src/main/grpc-type-convertions.ts | 21 ++++++++++++++++++- .../renderer/components/ApiAccessMethods.tsx | 20 +++++++++++++++++- gui/src/shared/daemon-rpc-types.ts | 4 +++- 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/gui/src/main/default-settings.ts b/gui/src/main/default-settings.ts index 46355dd439db..bebc5b9a4e38 100644 --- a/gui/src/main/default-settings.ts +++ b/gui/src/main/default-settings.ts @@ -104,6 +104,12 @@ export function getDefaultApiAccessMethods(): ApiAccessMethodSettings { enabled: false, type: 'bridges', }, + encryptedDnsProxy: { + id: '', + name: 'Encrypted DNS Proxy', + enabled: false, + type: 'encrypted-dns-proxy', + }, custom: [], }; } diff --git a/gui/src/main/grpc-type-convertions.ts b/gui/src/main/grpc-type-convertions.ts index 6511f10c6735..37e5c7fcf533 100644 --- a/gui/src/main/grpc-type-convertions.ts +++ b/gui/src/main/grpc-type-convertions.ts @@ -16,6 +16,7 @@ import { DeviceEvent, DeviceState, DirectMethod, + EncryptedDnsProxy, EndpointObfuscationType, ErrorStateCause, ErrorStateDetails, @@ -1097,6 +1098,11 @@ function fillApiAccessMethodSetting( accessMethod.setBridges(bridges); break; } + case 'encrypted-dns-proxy': { + const encryptedDnsProxy = new grpcTypes.AccessMethod.EncryptedDnsProxy(); + accessMethod.setEncryptedDnsProxy(encryptedDnsProxy); + break; + } default: accessMethod.setCustom(convertToCustomProxy(method)); } @@ -1160,6 +1166,12 @@ function convertFromApiAccessMethodSettings( const bridges = convertFromApiAccessMethodSetting( ensureExists(accessMethods.getMullvadBridges(), "no 'Mullvad Bridges' access method was found"), ) as AccessMethodSetting; + const encryptedDnsProxy = convertFromApiAccessMethodSetting( + ensureExists( + accessMethods.getEncryptedDnsProxy(), + "no 'Encrypted DNS proxy' access method was found", + ), + ) as AccessMethodSetting; const custom = accessMethods .getCustomList() .filter((setting) => setting.hasId() && setting.hasAccessMethod()) @@ -1170,6 +1182,7 @@ function convertFromApiAccessMethodSettings( return { direct, mullvadBridges: bridges, + encryptedDnsProxy, custom, }; } @@ -1177,7 +1190,11 @@ function convertFromApiAccessMethodSettings( function isCustomProxy( accessMethod: AccessMethodSetting, ): accessMethod is AccessMethodSetting { - return accessMethod.type !== 'direct' && accessMethod.type !== 'bridges'; + return ( + accessMethod.type !== 'direct' && + accessMethod.type !== 'bridges' && + accessMethod.type !== 'encrypted-dns-proxy' + ); } export function convertFromApiAccessMethodSetting( @@ -1200,6 +1217,8 @@ function convertFromAccessMethod(method: grpcTypes.AccessMethod): AccessMethod { return { type: 'direct' }; case grpcTypes.AccessMethod.AccessMethodCase.BRIDGES: return { type: 'bridges' }; + case grpcTypes.AccessMethod.AccessMethodCase.ENCRYPTED_DNS_PROXY: + return { type: 'encrypted-dns-proxy' }; case grpcTypes.AccessMethod.AccessMethodCase.CUSTOM: { return convertFromCustomProxy(method.getCustom()!); } diff --git a/gui/src/renderer/components/ApiAccessMethods.tsx b/gui/src/renderer/components/ApiAccessMethods.tsx index 57668df787c6..8b88e97edce0 100644 --- a/gui/src/renderer/components/ApiAccessMethods.tsx +++ b/gui/src/renderer/components/ApiAccessMethods.tsx @@ -133,6 +133,10 @@ export default function ApiAccessMethods() { method={methods.mullvadBridges} inUse={methods.mullvadBridges.id === currentMethod?.id} /> + {methods.custom.map((method) => ( )} + {props.method.type === 'encrypted-dns-proxy' && ( + + )} = T & { name: string }; @@ -540,6 +541,7 @@ export type AccessMethodSetting = export type ApiAccessMethodSettings = { direct: AccessMethodSetting; mullvadBridges: AccessMethodSetting; + encryptedDnsProxy: AccessMethodSetting; custom: Array>; }; From 83d070a2bbe66a7a3c8180169e9c5b235baa2e4d Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Fri, 18 Oct 2024 13:29:59 +0200 Subject: [PATCH 08/12] Update messages.pot --- gui/locales/messages.pot | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gui/locales/messages.pot b/gui/locales/messages.pot index 3850b7f23971..ec571e5364e7 100644 --- a/gui/locales/messages.pot +++ b/gui/locales/messages.pot @@ -466,6 +466,10 @@ msgctxt "api-access-methods-view" msgid "Enter port" msgstr "" +msgctxt "api-access-methods-view" +msgid "If you are not connected to our VPN, then the Encrypted DNS proxy will use your own non-VPN IP when connecting. The DoH servers are hosted by one of the following providers: Quad 9, CloudFlare, or Google." +msgstr "" + msgctxt "api-access-methods-view" msgid "In use" msgstr "" @@ -566,6 +570,10 @@ msgctxt "api-access-methods-view" msgid "With the “Direct” method, the app communicates with a Mullvad API server directly without any intermediate proxies." msgstr "" +msgctxt "api-access-methods-view" +msgid "With the “Encrypted DNS proxy” method, the app will communicate with our Mullvad API through a proxy address. It does this by retrieving an address from a DNS over HTTPS (DoH) server and then using that to reach our API servers." +msgstr "" + msgctxt "api-access-methods-view" msgid "With the “Mullvad bridges” method, the app communicates with a Mullvad API server via a Mullvad bridge server. It does this by sending the traffic obfuscated by Shadowsocks." msgstr "" From 2a20e5a4c72fd3ed7c28febe087d0a3856b84056 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Fri, 18 Oct 2024 14:38:04 +0200 Subject: [PATCH 09/12] Update API access method GUI e2e tests to include Encrypted DNS proxy --- .../installed/state-dependent/api-access-methods.spec.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/gui/test/e2e/installed/state-dependent/api-access-methods.spec.ts b/gui/test/e2e/installed/state-dependent/api-access-methods.spec.ts index d17611261b64..662ac4bf2a9b 100644 --- a/gui/test/e2e/installed/state-dependent/api-access-methods.spec.ts +++ b/gui/test/e2e/installed/state-dependent/api-access-methods.spec.ts @@ -15,6 +15,7 @@ import { startInstalledApp } from '../installed-utils'; const DIRECT_NAME = 'Direct'; const BRIDGES_NAME = 'Mullvad Bridges'; +const ENCRYPTED_DNS_PROXY_NAME = 'Encrypted DNS proxy'; const IN_USE_LABEL = 'In use'; const FUNCTIONING_METHOD_NAME = 'Test method'; const NON_FUNCTIONING_METHOD_NAME = 'Non functioning test method'; @@ -42,12 +43,14 @@ test('App should display access methods', async () => { await navigateToAccessMethods(); const accessMethods = page.getByTestId('access-method'); - await expect(accessMethods).toHaveCount(2); + await expect(accessMethods).toHaveCount(3); const direct = accessMethods.first(); - const bridges = accessMethods.last(); + const bridges = accessMethods.nth(1); + const encryptedDnsProxy = accessMethods.nth(2); await expect(direct).toContainText(DIRECT_NAME); await expect(bridges).toContainText(BRIDGES_NAME); + await expect(encryptedDnsProxy).toContainText(ENCRYPTED_DNS_PROXY_NAME); await expect(page.getByText(IN_USE_LABEL)).toHaveCount(1); }); @@ -144,6 +147,7 @@ test('App should use valid method', async () => { const direct = accessMethods.first(); const bridges = accessMethods.nth(1); + const encryptedDnsProxy = accessMethods.nth(2); const functioningTestMethod = accessMethods.last(); await expect(page.getByText(IN_USE_LABEL)).toHaveCount(1); @@ -154,6 +158,7 @@ test('App should use valid method', async () => { await functioningTestMethod.getByText('Use').click(); await expect(direct).not.toContainText(IN_USE_LABEL); await expect(bridges).not.toContainText(IN_USE_LABEL); + await expect(encryptedDnsProxy).not.toContainText(IN_USE_LABEL); await expect(functioningTestMethod).toContainText('API reachable'); await expect(functioningTestMethod).toContainText(IN_USE_LABEL); }); From 33ee250cceb51e1b862bcf04c63a1cdb615b559f Mon Sep 17 00:00:00 2001 From: Oskar Date: Fri, 18 Oct 2024 15:32:02 +0200 Subject: [PATCH 10/12] Fix API access method cell items alignment --- gui/src/renderer/components/ApiAccessMethods.tsx | 2 ++ gui/src/renderer/components/ContextMenu.tsx | 2 ++ gui/src/renderer/components/cell/Label.tsx | 6 +++++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/gui/src/renderer/components/ApiAccessMethods.tsx b/gui/src/renderer/components/ApiAccessMethods.tsx index 8b88e97edce0..f12e4835f16c 100644 --- a/gui/src/renderer/components/ApiAccessMethods.tsx +++ b/gui/src/renderer/components/ApiAccessMethods.tsx @@ -36,6 +36,8 @@ import { StyledContent, StyledNavigationScrollbars, StyledSettingsContent } from import { SmallButton, SmallButtonColor, SmallButtonGroup } from './SmallButton'; const StyledContextMenuButton = styled(Cell.Icon)({ + alignItems: 'center', + justifyContent: 'center', marginRight: '8px', }); diff --git a/gui/src/renderer/components/ContextMenu.tsx b/gui/src/renderer/components/ContextMenu.tsx index fd84a8f0d921..2e01c9375f6e 100644 --- a/gui/src/renderer/components/ContextMenu.tsx +++ b/gui/src/renderer/components/ContextMenu.tsx @@ -36,6 +36,8 @@ const menuContext = React.createContext({ const StyledMenuContainer = styled.div({ position: 'relative', padding: '8px 4px', + display: 'flex', + justifyContent: 'center', }); export function ContextMenuContainer(props: React.PropsWithChildren) { diff --git a/gui/src/renderer/components/cell/Label.tsx b/gui/src/renderer/components/cell/Label.tsx index 1115edc82945..b2b37c1e4ce3 100644 --- a/gui/src/renderer/components/cell/Label.tsx +++ b/gui/src/renderer/components/cell/Label.tsx @@ -15,11 +15,15 @@ const StyledLabel = styled.div<{ disabled: boolean }>(buttonText, (props) => ({ textAlign: 'left', [`${LabelContainer} &&`]: { - marginTop: '5px', + marginTop: '0px', marginBottom: 0, height: '20px', lineHeight: '20px', }, + + [`${LabelContainer}:has(${StyledSubLabel}) &&`]: { + marginTop: '5px', + }, })); const StyledSubText = styled.span<{ disabled: boolean }>(tinyText, (props) => ({ From 983e0d7dc1467b0dd1df7d0fb61c58fec11b5999 Mon Sep 17 00:00:00 2001 From: Oskar Date: Fri, 18 Oct 2024 15:48:06 +0200 Subject: [PATCH 11/12] Handle API access method name overflow --- gui/src/renderer/components/ApiAccessMethods.tsx | 1 + gui/src/renderer/components/InfoButton.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/gui/src/renderer/components/ApiAccessMethods.tsx b/gui/src/renderer/components/ApiAccessMethods.tsx index f12e4835f16c..68b873bd643f 100644 --- a/gui/src/renderer/components/ApiAccessMethods.tsx +++ b/gui/src/renderer/components/ApiAccessMethods.tsx @@ -52,6 +52,7 @@ const StyledSpinner = styled(ImageView)({ }); const StyledNameLabel = styled(Cell.Label)({ + display: 'block', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', diff --git a/gui/src/renderer/components/InfoButton.tsx b/gui/src/renderer/components/InfoButton.tsx index 4ef2aa3c6eda..7f77f115e74d 100644 --- a/gui/src/renderer/components/InfoButton.tsx +++ b/gui/src/renderer/components/InfoButton.tsx @@ -8,7 +8,7 @@ import ImageView from './ImageView'; import { ModalAlert, ModalAlertType } from './Modal'; const StyledInfoButton = styled.button({ - margin: '0 16px 0 0', + margin: '0 16px 0 8px', borderWidth: 0, padding: 0, cursor: 'default', From c018d97ac770bfbd1169e544ea63359c9b932afc Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Wed, 23 Oct 2024 13:42:11 +0200 Subject: [PATCH 12/12] Add default `serde` value for encrypted DNS proxy setting --- mullvad-types/src/access_method.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mullvad-types/src/access_method.rs b/mullvad-types/src/access_method.rs index 6ef3de0ea090..4f1229d1268f 100644 --- a/mullvad-types/src/access_method.rs +++ b/mullvad-types/src/access_method.rs @@ -4,8 +4,11 @@ use talpid_types::net::proxy::{CustomProxy, Shadowsocks, Socks5Local, Socks5Remo /// Settings for API access methods. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Settings { + #[serde(default = "Settings::create_direct")] direct: AccessMethodSetting, + #[serde(default = "Settings::create_mullvad_bridges")] mullvad_bridges: AccessMethodSetting, + #[serde(default = "Settings::create_encrypted_dns_proxy")] encrypted_dns_proxy: AccessMethodSetting, /// Custom API access methods. custom: Vec,