summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2025-04-24 16:16:33 +0200
committerDavid Lönnhager <david.l@mullvad.net>2025-05-08 09:23:55 +0200
commitf98465aef1ae5c857332110db6a9de8f573cde6c (patch)
treec6fe53d63eaf1676bcb02732b4ca8f8862513cd1
parentcc5ab4dcb8fb7aaf7afb806de6e1fce5eeda9666 (diff)
downloadmullvadvpn-f98465aef1ae5c857332110db6a9de8f573cde6c.tar.xz
mullvadvpn-f98465aef1ae5c857332110db6a9de8f573cde6c.zip
Add QUIC obfuscation to mullvad daemon and management interface
-rw-r--r--android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt4
-rw-r--r--desktop/packages/mullvad-vpn/src/main/daemon-rpc.ts5
-rw-r--r--desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts1
-rw-r--r--mullvad-management-interface/proto/management_interface.proto2
-rw-r--r--mullvad-management-interface/src/types/conversions/net.rs6
-rw-r--r--mullvad-management-interface/src/types/conversions/relay_constraints.rs2
-rw-r--r--mullvad-masque-proxy/examples/masque-client.rs2
-rw-r--r--mullvad-masque-proxy/src/client/mod.rs8
-rw-r--r--mullvad-relay-selector/src/relay_selector/mod.rs12
-rw-r--r--mullvad-relay-selector/src/relay_selector/query.rs6
-rw-r--r--mullvad-relay-selector/tests/relay_selector.rs5
-rw-r--r--mullvad-types/src/relay_constraints.rs5
-rw-r--r--talpid-types/src/net/mod.rs12
-rw-r--r--talpid-types/src/net/obfuscation.rs19
-rw-r--r--talpid-wireguard/src/obfuscation.rs11
-rw-r--r--tunnel-obfuscation/src/quic.rs6
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