summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2024-09-21 22:29:42 +0200
committerDavid Lönnhager <david.l@mullvad.net>2024-09-24 15:54:39 +0200
commitc59ab3229032f07be27f5824cad27e76d5ad0bfb (patch)
treeff6b0b7651b26071f14ed9d8e96e98e6f200515e
parent55f9b3a706c8134d2a92ec7b02c42a405455a3e0 (diff)
downloadmullvadvpn-c59ab3229032f07be27f5824cad27e76d5ad0bfb.tar.xz
mullvadvpn-c59ab3229032f07be27f5824cad27e76d5ad0bfb.zip
Lower MTU during ephemeral peer negotiation on Windows
-rw-r--r--talpid-tunnel-config-client/src/lib.rs60
-rw-r--r--talpid-tunnel-config-client/src/socket.rs92
-rw-r--r--talpid-wireguard/src/lib.rs27
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}")
+ }
+ }
+}