summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2025-05-02 16:42:56 +0200
committerDavid Lönnhager <david.l@mullvad.net>2025-05-02 16:42:56 +0200
commitd75791d60f333f002920af80bd0164089f377931 (patch)
tree5b545c3f80bb1116f5af3facd2b07174abbaa660
parentdc9b3f9292c31a1d90d68ab830fe9af41cd24ffa (diff)
parent60d2962541ddab1543033a4aea2b0ef51dd42f4f (diff)
downloadmullvadvpn-d75791d60f333f002920af80bd0164089f377931.tar.xz
mullvadvpn-d75791d60f333f002920af80bd0164089f377931.zip
Merge branch 'tunnel-obfuscation-add-quic'
-rw-r--r--Cargo.lock1
-rw-r--r--ios/MullvadRustRuntime/TunnelObfuscator.swift4
-rw-r--r--ios/MullvadRustRuntime/include/mullvad_rust_runtime.h1
-rw-r--r--ios/PacketTunnelCore/Actor/ProtocolObfuscator.swift10
-rw-r--r--mullvad-ios/src/tunnel_obfuscator_proxy/ffi.rs1
-rw-r--r--mullvad-ios/src/tunnel_obfuscator_proxy/mod.rs10
-rw-r--r--tunnel-obfuscation/Cargo.toml1
-rw-r--r--tunnel-obfuscation/src/lib.rs12
-rw-r--r--tunnel-obfuscation/src/quic.rs88
9 files changed, 123 insertions, 5 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 24d4ca3ad8..2e9cbf5864 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5712,6 +5712,7 @@ version = "0.0.0"
dependencies = [
"async-trait",
"log",
+ "mullvad-masque-proxy",
"nix 0.23.2",
"shadowsocks",
"thiserror 2.0.9",
diff --git a/ios/MullvadRustRuntime/TunnelObfuscator.swift b/ios/MullvadRustRuntime/TunnelObfuscator.swift
index 091a2e0e03..495930b222 100644
--- a/ios/MullvadRustRuntime/TunnelObfuscator.swift
+++ b/ios/MullvadRustRuntime/TunnelObfuscator.swift
@@ -14,6 +14,7 @@ import Network
public enum TunnelObfuscationProtocol {
case udpOverTcp
case shadowsocks
+ case quic
}
public protocol TunnelObfuscation {
@@ -53,6 +54,8 @@ public final class TunnelObfuscator: TunnelObfuscation {
.tcp
case .shadowsocks:
.udp
+ case .quic:
+ .udp
}
}
@@ -74,6 +77,7 @@ public final class TunnelObfuscator: TunnelObfuscation {
let obfuscationProtocol = switch obfuscationProtocol {
case .udpOverTcp: TunnelObfuscatorProtocol(0)
case .shadowsocks: TunnelObfuscatorProtocol(1)
+ case .quic: TunnelObfuscatorProtocol(2)
}
let result = withUnsafeMutablePointer(to: &proxyHandle) { proxyHandlePointer in
diff --git a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
index b4f4a65e2f..fa24141e42 100644
--- a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
+++ b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
@@ -23,6 +23,7 @@ typedef uint8_t SwiftAccessMethodKind;
enum TunnelObfuscatorProtocol {
UdpOverTcp = 0,
Shadowsocks,
+ Quic,
};
typedef uint8_t TunnelObfuscatorProtocol;
diff --git a/ios/PacketTunnelCore/Actor/ProtocolObfuscator.swift b/ios/PacketTunnelCore/Actor/ProtocolObfuscator.swift
index 51070accf8..b6588d6de6 100644
--- a/ios/PacketTunnelCore/Actor/ProtocolObfuscator.swift
+++ b/ios/PacketTunnelCore/Actor/ProtocolObfuscator.swift
@@ -61,13 +61,15 @@ public class ProtocolObfuscator<Obfuscator: TunnelObfuscation>: ProtocolObfuscat
}
#if DEBUG
- // TODO: Revisit this when QUIC obfuscation is available to use, use shadowsocks over 443 for the time being
+ let obfuscationProtocol: TunnelObfuscationProtocol = switch obfuscationMethod {
+ case .shadowsocks: .shadowsocks
+ case .quic: .quic
+ default: .udpOverTcp
+ }
let obfuscator = Obfuscator(
remoteAddress: endpoint.ipv4Relay.ip,
tcpPort: remotePort,
- obfuscationProtocol: (obfuscationMethod == .shadowsocks || obfuscationMethod == .quic)
- ? .shadowsocks
- : .udpOverTcp
+ obfuscationProtocol: obfuscationProtocol
)
#else
// At this point, the only possible obfuscation methods should be either `.udpOverTcp` or `.shadowsocks`
diff --git a/mullvad-ios/src/tunnel_obfuscator_proxy/ffi.rs b/mullvad-ios/src/tunnel_obfuscator_proxy/ffi.rs
index d92ce28a42..732d889e89 100644
--- a/mullvad-ios/src/tunnel_obfuscator_proxy/ffi.rs
+++ b/mullvad-ios/src/tunnel_obfuscator_proxy/ffi.rs
@@ -11,6 +11,7 @@ static INIT_LOGGING: Once = Once::new();
pub enum TunnelObfuscatorProtocol {
UdpOverTcp = 0,
Shadowsocks,
+ Quic,
}
#[unsafe(no_mangle)]
diff --git a/mullvad-ios/src/tunnel_obfuscator_proxy/mod.rs b/mullvad-ios/src/tunnel_obfuscator_proxy/mod.rs
index f33d6fd6ee..bb208db57e 100644
--- a/mullvad-ios/src/tunnel_obfuscator_proxy/mod.rs
+++ b/mullvad-ios/src/tunnel_obfuscator_proxy/mod.rs
@@ -5,7 +5,7 @@ use std::{
};
use tokio::task::JoinHandle;
use tunnel_obfuscation::{
- create_obfuscator, shadowsocks, udp2tcp, Settings as ObfuscationSettings,
+ create_obfuscator, quic, shadowsocks, udp2tcp, Settings as ObfuscationSettings,
};
mod ffi;
@@ -28,6 +28,14 @@ impl TunnelObfuscatorRuntime {
wireguard_endpoint: SocketAddr::from((Ipv4Addr::LOCALHOST, 51820)),
})
}
+ TunnelObfuscatorProtocol::Quic => {
+ ObfuscationSettings::Quic(quic::Settings {
+ quic_endpoint: peer,
+ wireguard_endpoint: SocketAddr::from((Ipv4Addr::LOCALHOST, 51820)),
+ // TODO: fetch the real hostname from the relay list
+ hostname: "www.mullvad.net".to_string(),
+ })
+ }
};
Self { settings }
diff --git a/tunnel-obfuscation/Cargo.toml b/tunnel-obfuscation/Cargo.toml
index 2a984c0104..5ebd2c8d4e 100644
--- a/tunnel-obfuscation/Cargo.toml
+++ b/tunnel-obfuscation/Cargo.toml
@@ -17,6 +17,7 @@ thiserror = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "macros", "net", "io-util"] }
udp-over-tcp = { git = "https://github.com/mullvad/udp-over-tcp", rev = "87936ac29b68b902565955f138ab02294bcc8593" }
shadowsocks = { workspace = true }
+mullvad-masque-proxy = { path = "../mullvad-masque-proxy" }
[target.'cfg(target_os="linux")'.dependencies]
nix = "0.23"
diff --git a/tunnel-obfuscation/src/lib.rs b/tunnel-obfuscation/src/lib.rs
index 9b4ca08d48..30a19642b3 100644
--- a/tunnel-obfuscation/src/lib.rs
+++ b/tunnel-obfuscation/src/lib.rs
@@ -1,6 +1,7 @@
use async_trait::async_trait;
use std::net::SocketAddr;
+pub mod quic;
pub mod shadowsocks;
pub mod udp2tcp;
@@ -19,6 +20,12 @@ pub enum Error {
#[error("Failed to run Shadowsocks")]
RunShadowsocksObfuscator(#[source] shadowsocks::Error),
+
+ #[error("Failed to initialize Quic")]
+ CreateQuicObfuscator(#[source] quic::Error),
+
+ #[error("Failed to run Quic")]
+ RunQuicObfuscator(#[source] quic::Error),
}
#[async_trait]
@@ -42,6 +49,7 @@ pub trait Obfuscator: Send {
pub enum Settings {
Udp2Tcp(udp2tcp::Settings),
Shadowsocks(shadowsocks::Settings),
+ Quic(quic::Settings),
}
pub async fn create_obfuscator(settings: &Settings) -> Result<Box<dyn Obfuscator>> {
@@ -54,6 +62,10 @@ pub async fn create_obfuscator(settings: &Settings) -> Result<Box<dyn Obfuscator
.await
.map(box_obfuscator)
.map_err(Error::CreateShadowsocksObfuscator),
+ Settings::Quic(s) => quic::Quic::new(s)
+ .await
+ .map(box_obfuscator)
+ .map_err(Error::CreateQuicObfuscator),
}
}
diff --git a/tunnel-obfuscation/src/quic.rs b/tunnel-obfuscation/src/quic.rs
new file mode 100644
index 0000000000..11a315554d
--- /dev/null
+++ b/tunnel-obfuscation/src/quic.rs
@@ -0,0 +1,88 @@
+//! Quic obfuscation
+
+use async_trait::async_trait;
+use mullvad_masque_proxy::client::{Client, ClientConfig};
+use std::{
+ io,
+ net::{Ipv4Addr, SocketAddr},
+};
+use tokio::net::UdpSocket;
+
+use crate::Obfuscator;
+
+type Result<T> = std::result::Result<T, Error>;
+
+#[derive(thiserror::Error, Debug)]
+pub enum Error {
+ #[error("Failed to bind UDP socket")]
+ BindError(#[source] io::Error),
+ #[error("Masque proxy error")]
+ MasqueProxyError(#[source] mullvad_masque_proxy::client::Error),
+}
+
+pub struct Quic {
+ local_endpoint: SocketAddr,
+ task: tokio::task::JoinHandle<Result<()>>,
+}
+
+#[derive(Debug)]
+pub struct Settings {
+ /// Remote Quic endpoint
+ pub quic_endpoint: SocketAddr,
+ /// Remote Wireguard endpoint
+ pub wireguard_endpoint: SocketAddr,
+ /// Hostname to use for QUIC
+ pub hostname: String,
+}
+
+impl Quic {
+ pub(crate) async fn new(settings: &Settings) -> Result<Self> {
+ let local_socket = UdpSocket::bind(SocketAddr::from((Ipv4Addr::LOCALHOST, 0)))
+ .await
+ .map_err(Error::BindError)?;
+
+ let local_endpoint = local_socket.local_addr().unwrap();
+
+ let config_builder = ClientConfig::builder()
+ .client_socket(local_socket)
+ .local_addr((Ipv4Addr::UNSPECIFIED, 0).into())
+ .server_addr(settings.quic_endpoint)
+ .server_host(settings.hostname.clone())
+ .target_addr(settings.wireguard_endpoint);
+
+ let task = tokio::spawn(async move {
+ let client = Client::connect(config_builder.build())
+ .await
+ .map_err(Error::MasqueProxyError)?;
+ client.run().await.map_err(Error::MasqueProxyError)
+ });
+
+ Ok(Quic {
+ local_endpoint,
+ task,
+ })
+ }
+}
+
+#[async_trait]
+impl Obfuscator for Quic {
+ fn endpoint(&self) -> SocketAddr {
+ self.local_endpoint
+ }
+
+ async fn run(self: Box<Self>) -> crate::Result<()> {
+ self.task
+ .await
+ .unwrap()
+ .map_err(crate::Error::RunQuicObfuscator)
+ }
+
+ fn packet_overhead(&self) -> u16 {
+ 0 // FIXME
+ }
+
+ #[cfg(target_os = "android")]
+ fn remote_socket_fd(&self) -> std::os::unix::io::RawFd {
+ unimplemented!()
+ }
+}