diff options
| author | David Lönnhager <david.l@mullvad.net> | 2025-04-24 16:16:33 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2025-05-08 09:23:55 +0200 |
| commit | f98465aef1ae5c857332110db6a9de8f573cde6c (patch) | |
| tree | c6fe53d63eaf1676bcb02732b4ca8f8862513cd1 | |
| parent | cc5ab4dcb8fb7aaf7afb806de6e1fce5eeda9666 (diff) | |
| download | mullvadvpn-f98465aef1ae5c857332110db6a9de8f573cde6c.tar.xz mullvadvpn-f98465aef1ae5c857332110db6a9de8f573cde6c.zip | |
Add QUIC obfuscation to mullvad daemon and management interface
16 files changed, 95 insertions, 11 deletions
diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt index eab0bc60a9..db6e21a586 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 @@ -215,6 +215,8 @@ internal fun ManagementInterface.ObfuscationEndpoint.ObfuscationType.toDomain(): ManagementInterface.ObfuscationEndpoint.ObfuscationType.UDP2TCP -> ObfuscationType.Udp2Tcp ManagementInterface.ObfuscationEndpoint.ObfuscationType.SHADOWSOCKS -> ObfuscationType.Shadowsocks + ManagementInterface.ObfuscationEndpoint.ObfuscationType.QUIC -> + throw IllegalArgumentException("Unsupported obfuscation type") ManagementInterface.ObfuscationEndpoint.ObfuscationType.UNRECOGNIZED -> throw IllegalArgumentException("Unrecognized obfuscation type") } @@ -419,6 +421,8 @@ internal fun ManagementInterface.ObfuscationSettings.SelectedObfuscation.toDomai ObfuscationMode.Udp2Tcp ManagementInterface.ObfuscationSettings.SelectedObfuscation.SHADOWSOCKS -> ObfuscationMode.Shadowsocks + ManagementInterface.ObfuscationSettings.SelectedObfuscation.QUIC -> + throw IllegalArgumentException("Unsupported obfuscation type") ManagementInterface.ObfuscationSettings.SelectedObfuscation.UNRECOGNIZED -> throw IllegalArgumentException("Unrecognized selected obfuscation") } diff --git a/desktop/packages/mullvad-vpn/src/main/daemon-rpc.ts b/desktop/packages/mullvad-vpn/src/main/daemon-rpc.ts index 9c9321c61d..d5a8ab7f51 100644 --- a/desktop/packages/mullvad-vpn/src/main/daemon-rpc.ts +++ b/desktop/packages/mullvad-vpn/src/main/daemon-rpc.ts @@ -305,6 +305,11 @@ export class DaemonRpc extends GrpcClient { grpcTypes.ObfuscationSettings.SelectedObfuscation.UDP2TCP, ); break; + case ObfuscationType.quic: + grpcObfuscationSettings.setSelectedObfuscation( + grpcTypes.ObfuscationSettings.SelectedObfuscation.QUIC, + ); + break; } if (obfuscationSettings.udp2tcpSettings) { diff --git a/desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts b/desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts index 2b2e5ab27b..2a12a06a30 100644 --- a/desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts +++ b/desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts @@ -461,6 +461,7 @@ export enum ObfuscationType { off, udp2tcp, shadowsocks, + quic, } export type ObfuscationSettings = { diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index add5ad1c87..2b7e02f9d2 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -296,6 +296,7 @@ message ObfuscationEndpoint { enum ObfuscationType { UDP2TCP = 0; SHADOWSOCKS = 1; + QUIC = 2; } string address = 1; @@ -390,6 +391,7 @@ message ObfuscationSettings { OFF = 1; UDP2TCP = 2; SHADOWSOCKS = 3; + QUIC = 4; } SelectedObfuscation selected_obfuscation = 1; Udp2TcpObfuscationSettings udp2tcp = 2; diff --git a/mullvad-management-interface/src/types/conversions/net.rs b/mullvad-management-interface/src/types/conversions/net.rs index 249ca5f406..0a84db01bf 100644 --- a/mullvad-management-interface/src/types/conversions/net.rs +++ b/mullvad-management-interface/src/types/conversions/net.rs @@ -39,6 +39,9 @@ impl From<talpid_types::net::TunnelEndpoint> for proto::TunnelEndpoint { net::ObfuscationType::Shadowsocks => { i32::from(proto::obfuscation_endpoint::ObfuscationType::Shadowsocks) } + net::ObfuscationType::Quic => { + i32::from(proto::obfuscation_endpoint::ObfuscationType::Quic) + } }, } }), @@ -123,6 +126,9 @@ impl TryFrom<proto::TunnelEndpoint> for talpid_types::net::TunnelEndpoint { Ok(proto::obfuscation_endpoint::ObfuscationType::Shadowsocks) => { talpid_net::ObfuscationType::Shadowsocks } + Ok(proto::obfuscation_endpoint::ObfuscationType::Quic) => { + talpid_net::ObfuscationType::Quic + } Err(_) => { return Err(FromProtobufTypeError::InvalidArgument( "unknown obfuscation type", diff --git a/mullvad-management-interface/src/types/conversions/relay_constraints.rs b/mullvad-management-interface/src/types/conversions/relay_constraints.rs index aebaede49a..3330493c90 100644 --- a/mullvad-management-interface/src/types/conversions/relay_constraints.rs +++ b/mullvad-management-interface/src/types/conversions/relay_constraints.rs @@ -151,6 +151,7 @@ impl From<&mullvad_types::relay_constraints::ObfuscationSettings> for proto::Obf SelectedObfuscation::Shadowsocks => { proto::obfuscation_settings::SelectedObfuscation::Shadowsocks } + SelectedObfuscation::Quic => proto::obfuscation_settings::SelectedObfuscation::Quic, }); Self { selected_obfuscation, @@ -437,6 +438,7 @@ impl TryFrom<proto::ObfuscationSettings> for mullvad_types::relay_constraints::O Ok(IpcSelectedObfuscation::Off) => SelectedObfuscation::Off, Ok(IpcSelectedObfuscation::Udp2tcp) => SelectedObfuscation::Udp2Tcp, Ok(IpcSelectedObfuscation::Shadowsocks) => SelectedObfuscation::Shadowsocks, + Ok(IpcSelectedObfuscation::Quic) => SelectedObfuscation::Quic, Err(_) => { return Err(FromProtobufTypeError::InvalidArgument( "invalid obfuscation settings", diff --git a/mullvad-masque-proxy/examples/masque-client.rs b/mullvad-masque-proxy/examples/masque-client.rs index 776afa8abb..205c198c62 100644 --- a/mullvad-masque-proxy/examples/masque-client.rs +++ b/mullvad-masque-proxy/examples/masque-client.rs @@ -38,7 +38,7 @@ pub struct ClientArgs { /// fwmark to use for the `server_addr` connection #[cfg(target_os = "linux")] #[arg(long)] - fwmark: Option<u16>, + fwmark: Option<u32>, /// Maximum duration of inactivity (in seconds) until the tunnel times out. /// Inactivity happens when no data is sent over the proxy. diff --git a/mullvad-masque-proxy/src/client/mod.rs b/mullvad-masque-proxy/src/client/mod.rs index 7bdea3a9e6..d2b56d12cc 100644 --- a/mullvad-masque-proxy/src/client/mod.rs +++ b/mullvad-masque-proxy/src/client/mod.rs @@ -140,7 +140,7 @@ pub struct ClientConfig { /// Optional fwmark to set on the QUIC endpoint socket #[cfg(target_os = "linux")] #[builder(default)] - pub fwmark: Option<u16>, + pub fwmark: Option<u32>, /// Optional timeout when no data is sent in the proxy. #[builder(default)] @@ -224,7 +224,7 @@ impl Client { fn setup_quic_endpoint( local_addr: SocketAddr, max_udp_payload_size: u16, - #[cfg(target_os = "linux")] fwmark: Option<u16>, + #[cfg(target_os = "linux")] fwmark: Option<u32>, ) -> Result<Endpoint> { let local_socket = socket2::Socket::new( socket2::Domain::IPV4, @@ -235,9 +235,7 @@ impl Client { #[cfg(target_os = "linux")] if let Some(fwmark) = fwmark { - local_socket - .set_mark(u32::from(fwmark)) - .map_err(Error::Fwmark)?; + local_socket.set_mark(fwmark).map_err(Error::Fwmark)?; } local_socket.bind(&local_addr.into()).map_err(Error::Bind)?; diff --git a/mullvad-relay-selector/src/relay_selector/mod.rs b/mullvad-relay-selector/src/relay_selector/mod.rs index e55db12669..68f11cf125 100644 --- a/mullvad-relay-selector/src/relay_selector/mod.rs +++ b/mullvad-relay-selector/src/relay_selector/mod.rs @@ -912,6 +912,18 @@ impl RelaySelector { Ok(Some(obfuscation)) } + ObfuscationQuery::Quic => { + let obfuscator = SelectedObfuscator { + config: ObfuscatorConfig::Quic { + // TODO: do not hardcode port + endpoint: std::net::SocketAddr::from((endpoint.peer.endpoint.ip(), 443)), + // TODO: do not hardcode + hostname: "test.mullvad.net".to_owned(), + }, + relay: obfuscator_relay, + }; + Ok(Some(obfuscator)) + } } } diff --git a/mullvad-relay-selector/src/relay_selector/query.rs b/mullvad-relay-selector/src/relay_selector/query.rs index 8c44e643e4..2ec2338ffc 100644 --- a/mullvad-relay-selector/src/relay_selector/query.rs +++ b/mullvad-relay-selector/src/relay_selector/query.rs @@ -283,6 +283,7 @@ pub enum ObfuscationQuery { Auto, Udp2tcp(Udp2TcpObfuscationSettings), Shadowsocks(ShadowsocksSettings), + Quic, } impl ObfuscationQuery { @@ -306,6 +307,10 @@ impl ObfuscationQuery { shadowsocks: settings, ..Default::default() }, + ObfuscationQuery::Quic => ObfuscationSettings { + selected_obfuscation: SelectedObfuscation::Quic, + ..Default::default() + }, } } } @@ -323,6 +328,7 @@ impl From<ObfuscationSettings> for ObfuscationQuery { SelectedObfuscation::Shadowsocks => { ObfuscationQuery::Shadowsocks(obfuscation.shadowsocks) } + SelectedObfuscation::Quic => ObfuscationQuery::Quic, } } } diff --git a/mullvad-relay-selector/tests/relay_selector.rs b/mullvad-relay-selector/tests/relay_selector.rs index 9e66d6a1ba..91cadb88a6 100644 --- a/mullvad-relay-selector/tests/relay_selector.rs +++ b/mullvad-relay-selector/tests/relay_selector.rs @@ -424,8 +424,9 @@ fn test_wireguard_retry_order() { assert!(match &query.wireguard_constraints().obfuscation { ObfuscationQuery::Auto => true, ObfuscationQuery::Off => obfuscator.is_none(), - ObfuscationQuery::Udp2tcp(_) | ObfuscationQuery::Shadowsocks(_) => - obfuscator.is_some(), + ObfuscationQuery::Quic + | ObfuscationQuery::Udp2tcp(_) + | ObfuscationQuery::Shadowsocks(_) => obfuscator.is_some(), }); } _ => unreachable!(), diff --git a/mullvad-types/src/relay_constraints.rs b/mullvad-types/src/relay_constraints.rs index fb0c932286..5009355635 100644 --- a/mullvad-types/src/relay_constraints.rs +++ b/mullvad-types/src/relay_constraints.rs @@ -509,6 +509,10 @@ pub enum SelectedObfuscation { #[cfg_attr(feature = "clap", clap(name = "udp2tcp"))] Udp2Tcp, Shadowsocks, + // TODO: Remove 'debug_assertions' condition + // See https://linear.app/mullvad/issue/DES-2105 + #[cfg_attr(all(feature = "clap", not(debug_assertions)), value(skip))] + Quic, } impl Intersection for SelectedObfuscation { @@ -533,6 +537,7 @@ impl fmt::Display for SelectedObfuscation { SelectedObfuscation::Off => "off".fmt(f), SelectedObfuscation::Udp2Tcp => "udp2tcp".fmt(f), SelectedObfuscation::Shadowsocks => "shadowsocks".fmt(f), + SelectedObfuscation::Quic => "quic".fmt(f), } } } diff --git a/talpid-types/src/net/mod.rs b/talpid-types/src/net/mod.rs index 56934a10d3..fc89cdc43f 100644 --- a/talpid-types/src/net/mod.rs +++ b/talpid-types/src/net/mod.rs @@ -239,6 +239,7 @@ pub enum ObfuscationType { #[serde(rename = "udp2tcp")] Udp2Tcp, Shadowsocks, + Quic, } impl fmt::Display for ObfuscationType { @@ -246,6 +247,7 @@ impl fmt::Display for ObfuscationType { match self { ObfuscationType::Udp2Tcp => "Udp2Tcp".fmt(f), ObfuscationType::Shadowsocks => "Shadowsocks".fmt(f), + ObfuscationType::Quic => "QUIC".fmt(f), } } } @@ -274,6 +276,16 @@ impl From<&ObfuscatorConfig> for ObfuscationEndpoint { }, ObfuscationType::Shadowsocks, ), + ObfuscatorConfig::Quic { + hostname: _, + endpoint, + } => ( + Endpoint { + address: *endpoint, + protocol: TransportProtocol::Udp, + }, + ObfuscationType::Quic, + ), }; ObfuscationEndpoint { diff --git a/talpid-types/src/net/obfuscation.rs b/talpid-types/src/net/obfuscation.rs index f05d82da9c..d2735c6a75 100644 --- a/talpid-types/src/net/obfuscation.rs +++ b/talpid-types/src/net/obfuscation.rs @@ -5,8 +5,16 @@ use super::{Endpoint, TransportProtocol}; #[derive(Clone, Eq, PartialEq, Deserialize, Serialize, Debug)] pub enum ObfuscatorConfig { - Udp2Tcp { endpoint: SocketAddr }, - Shadowsocks { endpoint: SocketAddr }, + Udp2Tcp { + endpoint: SocketAddr, + }, + Shadowsocks { + endpoint: SocketAddr, + }, + Quic { + hostname: String, + endpoint: SocketAddr, + }, } impl ObfuscatorConfig { @@ -20,6 +28,13 @@ impl ObfuscatorConfig { address: *endpoint, protocol: TransportProtocol::Udp, }, + ObfuscatorConfig::Quic { + hostname: _, + endpoint, + } => Endpoint { + address: *endpoint, + protocol: TransportProtocol::Udp, + }, } } } diff --git a/talpid-wireguard/src/obfuscation.rs b/talpid-wireguard/src/obfuscation.rs index 7317c4bfaf..fafe8c8860 100644 --- a/talpid-wireguard/src/obfuscation.rs +++ b/talpid-wireguard/src/obfuscation.rs @@ -13,7 +13,7 @@ use talpid_tunnel::tun_provider::TunProvider; use talpid_types::{net::obfuscation::ObfuscatorConfig, ErrorExt}; use tunnel_obfuscation::{ - create_obfuscator, shadowsocks, udp2tcp, Settings as ObfuscationSettings, + create_obfuscator, quic, shadowsocks, udp2tcp, Settings as ObfuscationSettings, }; /// Begin running obfuscation machine, if configured. This function will patch `config`'s endpoint @@ -96,6 +96,15 @@ fn settings_from_config( fwmark, }) } + ObfuscatorConfig::Quic { hostname, endpoint } => { + ObfuscationSettings::Quic(quic::Settings { + quic_endpoint: *endpoint, + wireguard_endpoint: SocketAddr::from((Ipv4Addr::LOCALHOST, 51820)), + hostname: hostname.to_owned(), + #[cfg(target_os = "linux")] + fwmark, + }) + } } } diff --git a/tunnel-obfuscation/src/quic.rs b/tunnel-obfuscation/src/quic.rs index c72a3b84fd..1230e53ad1 100644 --- a/tunnel-obfuscation/src/quic.rs +++ b/tunnel-obfuscation/src/quic.rs @@ -36,6 +36,9 @@ pub struct Settings { pub wireguard_endpoint: SocketAddr, /// Hostname to use for QUIC pub hostname: String, + /// fwmark to apply to use for the QUIC connection + #[cfg(target_os = "linux")] + pub fwmark: Option<u32>, } impl Quic { @@ -54,6 +57,9 @@ impl Quic { .target_addr(settings.wireguard_endpoint) .auth_header(Some(AUTH_HEADER.to_owned())); + #[cfg(target_os = "linux")] + let config_builder = config_builder.fwmark(settings.fwmark); + let task = tokio::spawn(async move { let client = Client::connect(config_builder.build()) .await |
