diff options
| -rw-r--r-- | Cargo.lock | 44 | ||||
| -rw-r--r-- | talpid-tunnel/src/tun_provider/unix.rs | 7 | ||||
| -rw-r--r-- | talpid-wireguard/Cargo.toml | 4 | ||||
| -rw-r--r-- | talpid-wireguard/src/boringtun/mod.rs | 372 | ||||
| -rw-r--r-- | talpid-wireguard/src/lib.rs | 36 |
5 files changed, 371 insertions, 92 deletions
diff --git a/Cargo.lock b/Cargo.lock index 741615496b..74c8ccd761 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -233,9 +233,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", @@ -444,12 +444,15 @@ dependencies = [ [[package]] name = "boringtun" version = "0.6.0" -source = "git+https://github.com/mullvad/boringtun?rev=c29681df54e1c10ba06d9ca7ff98722c9dcb5a81#c29681df54e1c10ba06d9ca7ff98722c9dcb5a81" +source = "git+https://github.com/mullvad/boringtun?rev=9194fbfd5ba578408887429c59c3b9d9171722fa#9194fbfd5ba578408887429c59c3b9d9171722fa" dependencies = [ "aead", + "async-trait", "base64 0.13.1", "blake2", + "bytes", "chacha20poly1305", + "either", "eyre", "hex", "hmac", @@ -457,8 +460,9 @@ dependencies = [ "ip_network_table", "libc", "log", - "nix 0.25.1", + "nix 0.30.1", "parking_lot", + "pnet_packet 0.35.0", "rand_core 0.6.4", "ring", "socket2 0.4.10", @@ -466,9 +470,10 @@ dependencies = [ "tokio", "tracing", "tun 0.7.13", - "typed-builder 0.20.1", + "typed-builder 0.21.0", "untrusted", "x25519-dalek", + "zerocopy", ] [[package]] @@ -491,9 +496,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "c2rust-bitfields" @@ -1154,9 +1159,9 @@ dependencies = [ [[package]] name = "either" -version = "1.11.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "elliptic-curve" @@ -3425,19 +3430,6 @@ dependencies = [ [[package]] name = "nix" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.6.5", -] - -[[package]] -name = "nix" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" @@ -7156,18 +7148,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.24" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.24" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", diff --git a/talpid-tunnel/src/tun_provider/unix.rs b/talpid-tunnel/src/tun_provider/unix.rs index 82b87fc666..70aad330a8 100644 --- a/talpid-tunnel/src/tun_provider/unix.rs +++ b/talpid-tunnel/src/tun_provider/unix.rs @@ -285,6 +285,7 @@ mod tun07_imp { builder.name(name); } } + builder.mtu(self.config.mtu); builder.create()? }; @@ -349,6 +350,12 @@ mod tun07_imp { self } + /// Set tunnel device MTU. + pub fn mtu(&mut self, mtu: u16) -> &mut Self { + self.config.mtu(mtu); + self + } + /// Enable packet information. /// When enabled the first 4 bytes of each packet is a header with flags and protocol type. #[cfg(target_os = "linux")] diff --git a/talpid-wireguard/Cargo.toml b/talpid-wireguard/Cargo.toml index 8784837888..87372c49b3 100644 --- a/talpid-wireguard/Cargo.toml +++ b/talpid-wireguard/Cargo.toml @@ -44,9 +44,9 @@ tokio-stream = { version = "0.1", features = ["io-util"] } [dependencies.boringtun] optional = true -features = ["device"] +features = ["device", "tun"] git = "https://github.com/mullvad/boringtun" -rev = "c29681df54e1c10ba06d9ca7ff98722c9dcb5a81" +rev = "9194fbfd5ba578408887429c59c3b9d9171722fa" [target.'cfg(unix)'.dependencies] nix = { workspace = true, features = ["fs"] } diff --git a/talpid-wireguard/src/boringtun/mod.rs b/talpid-wireguard/src/boringtun/mod.rs index 273f13b77f..93da4b141e 100644 --- a/talpid-wireguard/src/boringtun/mod.rs +++ b/talpid-wireguard/src/boringtun/mod.rs @@ -3,34 +3,90 @@ use crate::{ config::Config, stats::{Stats, StatsMap}, }; -use boringtun::device::{ - DeviceConfig, DeviceHandle, - api::{ApiClient, ApiServer, command::*}, - peer::AllowedIP, +#[cfg(target_os = "android")] +use boringtun::udp::UdpTransportFactory; +use boringtun::{ + device::{ + DeviceConfig, DeviceHandle, + api::{ApiClient, ApiServer, command::*}, + peer::AllowedIP, + }, + udp::{UdpSocketFactory, channel::PacketChannel}, }; - #[cfg(not(target_os = "android"))] use ipnetwork::IpNetwork; #[cfg(target_os = "android")] use std::os::fd::IntoRawFd; use std::{ future::Future, + net::{IpAddr, Ipv4Addr, Ipv6Addr}, ops::Deref, sync::{Arc, Mutex}, }; use talpid_tunnel::tun_provider::{self, Tun, TunProvider}; use talpid_tunnel_config_client::DaitaSettings; -use tun07::AbstractDevice; +use tun07::{AbstractDevice, AsyncDevice}; + +#[cfg(target_os = "android")] +type UdpFactory = AndroidUdpSocketFactory; + +#[cfg(not(target_os = "android"))] +type UdpFactory = UdpSocketFactory; + +type SinglehopDevice = DeviceHandle<(UdpFactory, Arc<tun07::AsyncDevice>, Arc<tun07::AsyncDevice>)>; +type EntryDevice = DeviceHandle<(UdpFactory, PacketChannel, PacketChannel)>; +type ExitDevice = DeviceHandle<(PacketChannel, Arc<AsyncDevice>, Arc<AsyncDevice>)>; + +const PACKET_CHANNEL_CAPACITY: usize = 100; pub struct BoringTun { - device_handle: DeviceHandle, - config_tx: ApiClient, + /// Device handles + devices: Devices, + + /// Tunnel config config: Config, /// Name of the tun interface. interface_name: String, } +enum Devices { + Singlehop { + device: SinglehopDevice, + api: ApiClient, + }, + + Multihop { + entry_device: EntryDevice, + entry_api: ApiClient, + + exit_device: ExitDevice, + exit_api: ApiClient, + }, +} + +#[cfg(target_os = "android")] +struct AndroidUdpSocketFactory { + pub tun: Tun, +} + +#[cfg(target_os = "android")] +impl UdpTransportFactory for AndroidUdpSocketFactory { + type Transport = <UdpSocketFactory as UdpTransportFactory>::Transport; + + async fn bind( + &mut self, + params: &boringtun::udp::UdpTransportFactoryParams, + ) -> std::io::Result<(Arc<Self::Transport>, Arc<Self::Transport>)> { + let (udp_v4, udp_v6) = UdpSocketFactory.bind(params).await?; + + self.tun.bypass(&udp_v4).unwrap(); + self.tun.bypass(&udp_v6).unwrap(); + + Ok((udp_v4, udp_v6)) + } +} + /// Configure and start a boringtun tunnel. pub async fn open_boringtun_tunnel( config: &Config, @@ -55,21 +111,15 @@ pub async fn open_boringtun_tunnel( } }; - let (mut config_tx, config_rx) = ApiServer::new(); - - let boringtun_config = DeviceConfig { - n_threads: 4, - api: Some(config_rx), - on_bind: None, + let (entry_api, entry_api_server) = ApiServer::new(); + let boringtun_entry_config = DeviceConfig { + api: Some(entry_api_server), }; #[cfg(target_os = "android")] - let mut boringtun_config = boringtun_config; - - #[cfg(target_os = "android")] - let async_tun = { + let (tun, async_tun) = { let _ = routes; // TODO: do we need this? - let (mut tun, fd) = get_tunnel_for_userspace(Arc::clone(&tun_provider), config)?; + let (tun, fd) = get_tunnel_for_userspace(Arc::clone(&tun_provider), config)?; let is_new_tunnel = tun.is_new; // TODO We should also wait for routes before sending any ping / connectivity check @@ -87,42 +137,113 @@ pub async fn open_boringtun_tunnel( .map_err(|e| TunnelError::RecoverableStartWireguardError(Box::new(e)))?; } - let mut config = tun07::Configuration::default(); - config.raw_fd(fd); + let mut tun_config = tun07::Configuration::default(); + tun_config.raw_fd(fd); - boringtun_config.on_bind = Some(Box::new(move |socket| tun.bypass(socket).unwrap())); + let device = tun07::Device::new(&tun_config).unwrap(); - let device = tun07::Device::new(&config).unwrap(); - tun07::AsyncDevice::new(device).unwrap() + (tun, tun07::AsyncDevice::new(device).unwrap()) }; let interface_name = async_tun.deref().tun_name().unwrap(); log::info!("passing tunnel dev to boringtun"); - let device_handle: DeviceHandle = DeviceHandle::new(async_tun, boringtun_config) + let async_tun = Arc::new(async_tun); + + let mut boringtun = if config.exit_peer.is_some() { + // multihop + + let source_v4 = config.tunnel.addresses.iter().find_map(|ip| match ip { + &IpAddr::V4(ipv4_addr) => Some(ipv4_addr), + IpAddr::V6(..) => None, + }); + + let source_v6 = config.tunnel.addresses.iter().find_map(|ip| match ip { + &IpAddr::V6(ipv6_addr) => Some(ipv6_addr), + IpAddr::V4(..) => None, + }); + + let channel = PacketChannel::new( + PACKET_CHANNEL_CAPACITY, + source_v4.unwrap_or(Ipv4Addr::UNSPECIFIED), // HACK: unwrap_or + source_v6.unwrap_or(Ipv6Addr::UNSPECIFIED), // HACK: unwrap_or + ); + + let (exit_api, exit_api_server) = ApiServer::new(); + let exit_device = DeviceHandle::<(PacketChannel, Arc<AsyncDevice>, Arc<AsyncDevice>)>::new( + channel.clone(), + async_tun.clone(), + async_tun, + DeviceConfig { + api: Some(exit_api_server), + }, + ) + .await + .map_err(TunnelError::BoringTunDevice)?; + + #[cfg(target_os = "android")] + let factory = AndroidUdpSocketFactory { tun }; + + #[cfg(not(target_os = "android"))] + let factory = UdpSocketFactory; + + let entry_device = + EntryDevice::new(factory, channel.clone(), channel, boringtun_entry_config) + .await + .map_err(TunnelError::BoringTunDevice)?; + + BoringTun { + config: config.clone(), + interface_name, + devices: Devices::Multihop { + entry_device, + entry_api, + exit_device, + exit_api, + }, + } + } else { + #[cfg(target_os = "android")] + let factory = AndroidUdpSocketFactory { tun }; + + #[cfg(not(target_os = "android"))] + let factory = UdpSocketFactory; + + let device = SinglehopDevice::new( + factory, + async_tun.clone(), + async_tun, + boringtun_entry_config, + ) .await .map_err(TunnelError::BoringTunDevice)?; - set_boringtun_config(&mut config_tx, config).await?; + BoringTun { + devices: Devices::Singlehop { + device, + api: entry_api, + }, + config: config.clone(), + interface_name, + } + }; + + // FIXME: double clone + boringtun.set_config(config.clone()).await?; log::info!( "This tunnel was brought to you by... -......................................................... -..*...*.. .--. .---. ..*....*. -...*..... | ) o | ......*.. -.*..*..*. |--: .-. .--.. .--. .-..|. . .--. ...*..... -...*..... | )( )| | | |( ||| | | | .*.....*. -*.....*.. '--' `-' ' -' `-' `-`-`|'`--`-' `- .....*... -......... ._.' ..*...*.. -..*...*.............................................*...." + ......................................................... + ..*...*.. .--. .---. ..*....*. + ...*..... | ) o | ......*.. + .*..*..*. |--: .-. .--.. .--. .-..|. . .--. ...*..... + ...*..... | )( )| | | |( ||| | | | .*.....*. + *.....*.. '--' `-' ' -' `-' `-`-`|'`--`-' `- .....*... + ......... ._.' ..*...*.. + ..*...*.............................................*...." ); - Ok(BoringTun { - device_handle, - config: config.clone(), - config_tx, - interface_name, - }) + Ok(boringtun) } #[async_trait::async_trait] @@ -133,31 +254,56 @@ impl Tunnel for BoringTun { fn stop(self: Box<Self>) -> Result<(), TunnelError> { log::info!("BoringTun::stop"); // remove me - tokio::runtime::Handle::current().block_on(self.device_handle.stop()); + tokio::runtime::Handle::current().block_on(async { + match self.devices { + Devices::Singlehop { device, .. } => { + device.stop().await; + } + Devices::Multihop { + entry_device, + exit_device, + .. + } => { + exit_device.stop().await; + entry_device.stop().await; + } + } + }); + Ok(()) } async fn get_tunnel_stats(&self) -> Result<StatsMap, TunnelError> { - let response = self - .config_tx - .send(Get::default()) - .await - .expect("Failed to get peers"); + let mut stats = StatsMap::default(); - let Response::Get(response) = response else { - return Err(TunnelError::GetConfigError); + let apis = match &self.devices { + Devices::Singlehop { api, .. } => [Some(api), None], + Devices::Multihop { + entry_api, + exit_api, + .. + } => [Some(entry_api), Some(exit_api)], }; - Ok(StatsMap::from_iter(response.peers.into_iter().map( - |peer| { - ( + + for api in apis.into_iter().flatten() { + let response = api.send(Get::default()).await.expect("Failed to get peers"); + + let Response::Get(response) = response else { + return Err(TunnelError::GetConfigError); + }; + + for peer in response.peers { + stats.insert( peer.peer.public_key.0, Stats { tx_bytes: peer.tx_bytes.unwrap_or_default(), rx_bytes: peer.rx_bytes.unwrap_or_default(), }, - ) - }, - ))) + ); + } + } + + Ok(stats) } fn set_config<'a>( @@ -166,7 +312,27 @@ impl Tunnel for BoringTun { ) -> std::pin::Pin<Box<dyn Future<Output = Result<(), TunnelError>> + Send + 'a>> { Box::pin(async move { self.config = config; - set_boringtun_config(&mut self.config_tx, &self.config).await?; + match &mut self.devices { + Devices::Singlehop { api, .. } => { + assert!( + self.config.exit_peer.is_none(), + "todo: support switching between single and multihop" + ); + set_boringtun_config(api, &self.config).await?; + } + Devices::Multihop { + entry_api, + exit_api, + .. + } => { + assert!( + self.config.exit_peer.is_some(), + "todo: support switching between single and multihop" + ); + set_boringtun_entry_config(entry_api, &self.config).await?; + set_boringtun_exit_config(exit_api, &self.config).await?; + } + } Ok(()) }) } @@ -224,6 +390,100 @@ async fn set_boringtun_config( Ok(()) } +async fn set_boringtun_entry_config( + tx: &mut ApiClient, + config: &Config, +) -> Result<(), crate::TunnelError> { + log::info!("configuring boringtun device"); + let mut set_cmd = Set::builder() + .private_key(config.tunnel.private_key.to_bytes()) + .listen_port(0u16) + .replace_peers() + .build(); + + #[cfg(target_os = "linux")] + { + set_cmd.fwmark = config.fwmark; + } + + let peer = &config.entry_peer; + let mut boring_peer = Peer::builder() + .public_key(*peer.public_key.as_bytes()) + .endpoint(peer.endpoint) + .allowed_ip( + peer.allowed_ips + .iter() + .map(|net| AllowedIP { + addr: net.ip(), + cidr: net.prefix(), + }) + .collect(), + ) + .build(); + + if let Some(psk) = &peer.psk { + boring_peer.preshared_key = Some(SetUnset::Set((*psk.as_bytes()).into())); + } + + let boring_peer = SetPeer::builder().peer(boring_peer).build(); + + set_cmd.peers.push(boring_peer); + + tx.send(set_cmd).await.map_err(|err| { + log::error!("Failed to set boringtun config: {err:#}"); + TunnelError::SetConfigError + })?; + Ok(()) +} + +async fn set_boringtun_exit_config( + tx: &mut ApiClient, + config: &Config, +) -> Result<(), crate::TunnelError> { + log::info!("configuring boringtun device"); + let mut set_cmd = Set::builder() + .private_key(config.tunnel.private_key.to_bytes()) + .listen_port(0u16) + .replace_peers() + .build(); + + #[cfg(target_os = "linux")] + { + set_cmd.fwmark = config.fwmark; + } + + // TODO: don't unwrap + let peer = config.exit_peer.as_ref().unwrap(); + + let mut boring_peer = Peer::builder() + .public_key(*peer.public_key.as_bytes()) + .endpoint(peer.endpoint) + .allowed_ip( + peer.allowed_ips + .iter() + .map(|net| AllowedIP { + addr: net.ip(), + cidr: net.prefix(), + }) + .collect(), + ) + .build(); + + if let Some(psk) = &peer.psk { + boring_peer.preshared_key = Some(SetUnset::Set((*psk.as_bytes()).into())); + } + + let boring_peer = SetPeer::builder().peer(boring_peer).build(); + + set_cmd.peers.push(boring_peer); + + tx.send(set_cmd).await.map_err(|err| { + log::error!("Failed to set boringtun config: {err:#}"); + TunnelError::SetConfigError + })?; + Ok(()) +} + #[cfg(target_os = "windows")] fn get_tunnel_for_userspace( tun_provider: Arc<Mutex<TunProvider>>, @@ -238,6 +498,8 @@ fn get_tunnel_for_userspace( tun_config.ipv6_gateway = config.ipv6_gateway; tun_config.mtu = config.mtu; + // FIXME: mtu is not set + let _ = routes; #[cfg(windows)] diff --git a/talpid-wireguard/src/lib.rs b/talpid-wireguard/src/lib.rs index d593b69b72..5e265e4dd5 100644 --- a/talpid-wireguard/src/lib.rs +++ b/talpid-wireguard/src/lib.rs @@ -263,7 +263,7 @@ impl WireguardMonitor { .map_err(Error::SetupRoutingError) .map_err(CloseMsg::SetupError)?; - let routes = Self::get_pre_tunnel_routes(&iface_name, &config) + let routes = Self::get_pre_tunnel_routes(&iface_name, &config, userspace_wireguard) .chain(Self::get_endpoint_routes(&endpoint_addrs)) .collect(); @@ -354,7 +354,10 @@ impl WireguardMonitor { // Add any default route(s) that may exist. args.route_manager - .add_routes(Self::get_post_tunnel_routes(&iface_name, &config).collect()) + .add_routes( + Self::get_post_tunnel_routes(&iface_name, &config, userspace_wireguard) + .collect(), + ) .await .map_err(Error::SetupRoutingError) .map_err(CloseMsg::SetupError)?; @@ -877,6 +880,7 @@ impl WireguardMonitor { fn get_pre_tunnel_routes<'a>( iface_name: &str, config: &'a Config, + #[allow(unused_variables)] userspace_wireguard: bool, ) -> impl Iterator<Item = RequiredRoute> + 'a { // e.g. utun4 let gateway_node = talpid_routing::Node::device(iface_name.to_string()); @@ -895,8 +899,9 @@ impl WireguardMonitor { let (node_v4, node_v6) = Self::get_tunnel_nodes(iface_name, config); #[cfg(any(target_os = "linux", target_os = "macos"))] - let gateway_routes = - gateway_routes.map(|route| Self::apply_route_mtu_for_multihop(route, config)); + let gateway_routes = gateway_routes.map(move |route| { + Self::apply_route_mtu_for_multihop(route, config, userspace_wireguard) + }); gateway_routes.chain( config @@ -917,6 +922,7 @@ impl WireguardMonitor { fn get_post_tunnel_routes<'a>( iface_name: &str, config: &'a Config, + #[allow(unused_variables)] userspace_wireguard: bool, ) -> impl Iterator<Item = RequiredRoute> + 'a { let (node_v4, node_v6) = Self::get_tunnel_nodes(iface_name, config); let iter = config @@ -935,19 +941,31 @@ impl WireguardMonitor { #[cfg(target_os = "linux")] return iter .map(|route| route.use_main_table(false)) - .map(|route| Self::apply_route_mtu_for_multihop(route, config)); + .map(move |route| { + Self::apply_route_mtu_for_multihop(route, config, userspace_wireguard) + }); #[cfg(target_os = "macos")] - iter.map(|route| Self::apply_route_mtu_for_multihop(route, config)) + iter.map(move |route| { + Self::apply_route_mtu_for_multihop(route, config, userspace_wireguard) + }) } #[cfg(any(target_os = "linux", target_os = "macos"))] - fn apply_route_mtu_for_multihop(route: RequiredRoute, config: &Config) -> RequiredRoute { - use talpid_tunnel::{IPV4_HEADER_SIZE, IPV6_HEADER_SIZE, WIREGUARD_HEADER_SIZE}; + fn apply_route_mtu_for_multihop( + route: RequiredRoute, + config: &Config, + userspace_wireguard: bool, + ) -> RequiredRoute { + // For userspace multihop, per-route MTU is unnecessary. Packets are not sent back to + // the tunnel interface, so we're not constrained by its MTU. + let using_boringtun = userspace_wireguard && cfg!(feature = "boringtun"); - if !config.is_multihop() { + if !config.is_multihop() || using_boringtun { route } else { + use talpid_tunnel::{IPV4_HEADER_SIZE, IPV6_HEADER_SIZE, WIREGUARD_HEADER_SIZE}; + // Set route MTU by subtracting the WireGuard overhead from the tunnel MTU. Plus // some margin to make room for padding bytes. let ip_overhead = match route.prefix.is_ipv4() { |
