diff options
| author | David Lönnhager <david.l@mullvad.net> | 2025-05-02 16:42:56 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2025-05-02 16:42:56 +0200 |
| commit | d75791d60f333f002920af80bd0164089f377931 (patch) | |
| tree | 5b545c3f80bb1116f5af3facd2b07174abbaa660 | |
| parent | dc9b3f9292c31a1d90d68ab830fe9af41cd24ffa (diff) | |
| parent | 60d2962541ddab1543033a4aea2b0ef51dd42f4f (diff) | |
| download | mullvadvpn-d75791d60f333f002920af80bd0164089f377931.tar.xz mullvadvpn-d75791d60f333f002920af80bd0164089f377931.zip | |
Merge branch 'tunnel-obfuscation-add-quic'
| -rw-r--r-- | Cargo.lock | 1 | ||||
| -rw-r--r-- | ios/MullvadRustRuntime/TunnelObfuscator.swift | 4 | ||||
| -rw-r--r-- | ios/MullvadRustRuntime/include/mullvad_rust_runtime.h | 1 | ||||
| -rw-r--r-- | ios/PacketTunnelCore/Actor/ProtocolObfuscator.swift | 10 | ||||
| -rw-r--r-- | mullvad-ios/src/tunnel_obfuscator_proxy/ffi.rs | 1 | ||||
| -rw-r--r-- | mullvad-ios/src/tunnel_obfuscator_proxy/mod.rs | 10 | ||||
| -rw-r--r-- | tunnel-obfuscation/Cargo.toml | 1 | ||||
| -rw-r--r-- | tunnel-obfuscation/src/lib.rs | 12 | ||||
| -rw-r--r-- | tunnel-obfuscation/src/quic.rs | 88 |
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!() + } +} |
