diff options
| author | David Lönnhager <david.l@mullvad.net> | 2024-09-21 22:29:42 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2024-09-24 15:54:39 +0200 |
| commit | c59ab3229032f07be27f5824cad27e76d5ad0bfb (patch) | |
| tree | ff6b0b7651b26071f14ed9d8e96e98e6f200515e | |
| parent | 55f9b3a706c8134d2a92ec7b02c42a405455a3e0 (diff) | |
| download | mullvadvpn-c59ab3229032f07be27f5824cad27e76d5ad0bfb.tar.xz mullvadvpn-c59ab3229032f07be27f5824cad27e76d5ad0bfb.zip | |
Lower MTU during ephemeral peer negotiation on Windows
| -rw-r--r-- | talpid-tunnel-config-client/src/lib.rs | 60 | ||||
| -rw-r--r-- | talpid-tunnel-config-client/src/socket.rs | 92 | ||||
| -rw-r--r-- | talpid-wireguard/src/lib.rs | 27 |
3 files changed, 122 insertions, 57 deletions
diff --git a/talpid-tunnel-config-client/src/lib.rs b/talpid-tunnel-config-client/src/lib.rs index 1ebb7fef5a..d40245ffdb 100644 --- a/talpid-tunnel-config-client/src/lib.rs +++ b/talpid-tunnel-config-client/src/lib.rs @@ -5,8 +5,6 @@ use std::net::SocketAddr; #[cfg(not(target_os = "ios"))] use std::net::{IpAddr, Ipv4Addr}; use talpid_types::net::wireguard::{PresharedKey, PublicKey}; -#[cfg(not(target_os = "ios"))] -use tokio::net::TcpSocket; use tonic::transport::Channel; #[cfg(not(target_os = "ios"))] use tonic::transport::Endpoint; @@ -16,21 +14,14 @@ use zeroize::Zeroize; mod classic_mceliece; mod kyber; +#[cfg(not(target_os = "ios"))] +mod socket; #[allow(clippy::derive_partial_eq_without_eq)] mod proto { tonic::include_proto!("ephemeralpeer"); } -#[cfg(not(any(target_os = "windows", target_os = "ios")))] -mod sys { - pub use libc::{setsockopt, socklen_t, IPPROTO_TCP, TCP_MAXSEG}; - pub use std::os::fd::{AsRawFd, RawFd}; -} - -#[cfg(not(any(target_os = "windows", target_os = "ios")))] -use sys::*; - #[derive(Debug)] pub enum Error { GrpcConnectError(tonic::transport::Error), @@ -93,14 +84,6 @@ pub type RelayConfigService = proto::ephemeral_peer_client::EphemeralPeerClient< /// Port used by the tunnel config service. pub const CONFIG_SERVICE_PORT: u16 = 1337; -/// MTU to set on the tunnel config client socket. We want a low value to prevent fragmentation. -/// Especially on Android, we've found that the real MTU is often lower than the default MTU, and -/// we cannot lower it further. This causes the outer packets to be dropped. Also, MTU detection -/// will likely occur after the PQ handshake, so we cannot assume that the MTU is already -/// correctly configured. -#[cfg(not(any(target_os = "windows", target_os = "ios")))] -const CONFIG_CLIENT_MTU: u16 = 576; - pub struct EphemeralPeer { pub psk: Option<PresharedKey>, } @@ -239,11 +222,7 @@ async fn new_client(addr: Ipv4Addr) -> Result<RelayConfigService, Error> { let conn = endpoint .connect_with_connector(service_fn(move |_| async move { - let sock = TcpSocket::new_v4()?; - - #[cfg(not(target_os = "windows"))] - try_set_tcp_sock_mtu(&addr, sock.as_raw_fd(), CONFIG_CLIENT_MTU); - + let sock = socket::TcpSocket::new()?; sock.connect(SocketAddr::new(addr, CONFIG_SERVICE_PORT)) .await })) @@ -252,36 +231,3 @@ async fn new_client(addr: Ipv4Addr) -> Result<RelayConfigService, Error> { Ok(RelayConfigService::new(conn)) } - -#[cfg(not(any(target_os = "windows", target_os = "ios")))] -fn try_set_tcp_sock_mtu(dest: &IpAddr, sock: RawFd, mut mtu: u16) { - const IPV4_HEADER_SIZE: u16 = 20; - const IPV6_HEADER_SIZE: u16 = 40; - const MAX_TCP_HEADER_SIZE: u16 = 60; - - if dest.is_ipv4() { - mtu = mtu.saturating_sub(IPV4_HEADER_SIZE); - } else { - mtu = mtu.saturating_sub(IPV6_HEADER_SIZE); - } - - let mss = u32::from(mtu.saturating_sub(MAX_TCP_HEADER_SIZE)); - - log::debug!("Config client socket MSS: {mss}"); - - let result = unsafe { - setsockopt( - sock, - IPPROTO_TCP, - TCP_MAXSEG, - &mss as *const _ as _, - socklen_t::try_from(std::mem::size_of_val(&mss)).unwrap(), - ) - }; - if result != 0 { - log::error!( - "Failed to set MSS on config client socket: {}", - std::io::Error::last_os_error() - ); - } -} diff --git a/talpid-tunnel-config-client/src/socket.rs b/talpid-tunnel-config-client/src/socket.rs new file mode 100644 index 0000000000..87dbe23a40 --- /dev/null +++ b/talpid-tunnel-config-client/src/socket.rs @@ -0,0 +1,92 @@ +//! A TCP stream with a low MSS set. This prevents incorrectly configured MTU from causing +//! fragmentation/packet loss. This is only supported on non-Windows targets. + +use std::io; +use std::net::SocketAddr; +use tokio::net::TcpSocket as StdTcpSocket; +use tokio::net::TcpStream; + +#[cfg(unix)] +mod sys { + use super::*; + + pub use libc::{setsockopt, socklen_t, IPPROTO_TCP, TCP_MAXSEG}; + pub use std::os::fd::{AsRawFd, RawFd}; + use std::pin::Pin; + use std::task::{Context, Poll}; + use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; + + /// MTU to set on the tunnel config client socket. We want a low value to prevent fragmentation. + /// Especially on Android, we've found that the real MTU is often lower than the default MTU, and + /// we cannot lower it further. This causes the outer packets to be dropped. Also, MTU detection + /// will likely occur after the PQ handshake, so we cannot assume that the MTU is already + /// correctly configured. + const CONFIG_CLIENT_MTU: u16 = 576; + + pub struct TcpSocket { + socket: StdTcpSocket, + } + + impl TcpSocket { + pub fn new() -> io::Result<Self> { + let socket = StdTcpSocket::new_v4()?; + try_set_tcp_sock_mtu(socket.as_raw_fd()); + Ok(Self { socket }) + } + + pub async fn connect(self, addr: SocketAddr) -> io::Result<TcpStream> { + self.socket.connect(addr).await + } + } + + fn try_set_tcp_sock_mtu(sock: RawFd) { + let mss = desired_mss(); + log::debug!("Tunnel config TCP socket MSS: {mss}"); + + let result = unsafe { + setsockopt( + sock, + IPPROTO_TCP, + TCP_MAXSEG, + &mss as *const _ as _, + socklen_t::try_from(std::mem::size_of_val(&mss)).unwrap(), + ) + }; + if result != 0 { + log::error!( + "Failed to set MSS on tunnel config TCP socket: {}", + std::io::Error::last_os_error() + ); + } + } + + const fn desired_mss() -> u32 { + const IPV4_HEADER_SIZE: u16 = 20; + const MAX_TCP_HEADER_SIZE: u16 = 60; + let mtu = CONFIG_CLIENT_MTU.saturating_sub(IPV4_HEADER_SIZE); + mtu.saturating_sub(MAX_TCP_HEADER_SIZE) as u32 + } +} + +#[cfg(windows)] +mod sys { + use super::*; + + pub struct TcpSocket { + socket: StdTcpSocket, + } + + impl TcpSocket { + pub fn new() -> io::Result<Self> { + Ok(Self { + socket: StdTcpSocket::new_v4()?, + }) + } + + pub async fn connect(self, addr: SocketAddr) -> io::Result<TcpStream> { + self.socket.connect(addr).await + } + } +} + +pub use sys::*; diff --git a/talpid-wireguard/src/lib.rs b/talpid-wireguard/src/lib.rs index ea258a456b..a021cb3959 100644 --- a/talpid-wireguard/src/lib.rs +++ b/talpid-wireguard/src/lib.rs @@ -253,6 +253,12 @@ impl WireguardMonitor { let ephemeral_obfs_sender = close_obfs_sender.clone(); if config.quantum_resistant || config.daita { + #[cfg(windows)] + { + log::trace!("Temporarily lowering tunnel MTU before ephemeral peer config"); + try_set_ipv4_mtu(&iface_name, talpid_tunnel::MIN_IPV4_MTU); + } + Self::config_ephemeral_peers( &tunnel, &mut config, @@ -262,6 +268,12 @@ impl WireguardMonitor { ) .await?; + #[cfg(windows)] + { + log::trace!("Resetting tunnel MTU"); + try_set_ipv4_mtu(&iface_name, config.mtu); + } + let metadata = Self::tunnel_metadata(&iface_name, &config); (on_event)(TunnelEvent::InterfaceUp( metadata, @@ -1220,3 +1232,18 @@ fn will_nm_manage_dns() -> bool { }) .unwrap_or(false) } + +#[cfg(windows)] +fn try_set_ipv4_mtu(alias: &str, mtu: u16) { + use talpid_windows::net::*; + match luid_from_alias(alias) { + Ok(luid) => { + if let Err(error) = set_mtu(u32::from(mtu), luid, AddressFamily::Ipv4) { + log::error!("Failed to set tunnel interface MTU: {error}"); + } + } + Err(error) => { + log::error!("Failed to obtain tunnel interface LUID: {error}") + } + } +} |
