summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2024-08-19 12:02:26 +0200
committerDavid Lönnhager <david.l@mullvad.net>2024-08-20 12:03:02 +0200
commitc1c00a6ee3c3a55f79c484456b09061c835ca49a (patch)
tree20b2302ebfe39459449e496d6604c2ff63a482d9
parentba6d56a7c5bcbfb8bb35adc50abfc94daf898671 (diff)
downloadmullvadvpn-c1c00a6ee3c3a55f79c484456b09061c835ca49a.tar.xz
mullvadvpn-c1c00a6ee3c3a55f79c484456b09061c835ca49a.zip
Synchronize split tunnel interface MTU with VPN tunnel MTU
-rw-r--r--Cargo.lock1
-rw-r--r--talpid-core/Cargo.toml1
-rw-r--r--talpid-core/src/split_tunnel/macos/mod.rs1
-rw-r--r--talpid-core/src/split_tunnel/macos/tun.rs93
4 files changed, 93 insertions, 3 deletions
diff --git a/Cargo.lock b/Cargo.lock
index f9fa547879..26f0e8ac3d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3903,6 +3903,7 @@ dependencies = [
"subslice",
"system-configuration",
"talpid-dbus",
+ "talpid-net",
"talpid-openvpn",
"talpid-platform-metadata",
"talpid-routing",
diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml
index 22515b3cc2..20b69e8afc 100644
--- a/talpid-core/Cargo.toml
+++ b/talpid-core/Cargo.toml
@@ -60,6 +60,7 @@ tun = { version = "0.5.5", features = ["async"] }
nix = { version = "0.28", features = ["socket"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
+talpid-net = { path = "../talpid-net" }
[target.'cfg(windows)'.dependencies]
bitflags = "1.2"
diff --git a/talpid-core/src/split_tunnel/macos/mod.rs b/talpid-core/src/split_tunnel/macos/mod.rs
index d5c7a508c3..0ef879842d 100644
--- a/talpid-core/src/split_tunnel/macos/mod.rs
+++ b/talpid-core/src/split_tunnel/macos/mod.rs
@@ -516,6 +516,7 @@ impl State {
let result = tun::create_split_tunnel(
default_interface,
new_vpn_interface.clone(),
+ route_manager.clone(),
Box::new(move |packet| {
match states.get_process_status(packet.header.pth_pid as u32) {
ExclusionStatus::Excluded => tun::RoutingDecision::DefaultInterface,
diff --git a/talpid-core/src/split_tunnel/macos/tun.rs b/talpid-core/src/split_tunnel/macos/tun.rs
index f5f7211499..b1a7cf53cf 100644
--- a/talpid-core/src/split_tunnel/macos/tun.rs
+++ b/talpid-core/src/split_tunnel/macos/tun.rs
@@ -10,6 +10,7 @@ use super::{
};
use futures::{Stream, StreamExt};
use libc::{AF_INET, AF_INET6};
+use nix::net::if_::if_nametoindex;
use pcap::PacketCodec;
use pnet_packet::{
ethernet::{EtherTypes, MutableEthernetPacket},
@@ -26,6 +27,7 @@ use std::{
net::{Ipv4Addr, Ipv6Addr},
ptr::NonNull,
};
+use talpid_routing::RouteManagerHandle;
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
sync::broadcast,
@@ -114,11 +116,15 @@ pub struct SplitTunnelHandle {
/// Task that handles outgoing packets. On completion, it returns a handle for the pktap, as
/// well as the function used to classify packets
egress_task: tokio::task::JoinHandle<Result<EgressResult, Error>>,
+ /// Task that synchronizes the ST tunnel MTU with the VPN tunnel MTU
+ mtu_listener: Option<tokio::task::JoinHandle<()>>,
+ route_manager: RouteManagerHandle,
}
impl SplitTunnelHandle {
- pub async fn shutdown(self) -> Result<(), Error> {
+ pub async fn shutdown(mut self) -> Result<(), Error> {
log::debug!("Shutting down split tunnel");
+ self.abort_mtu_listener().await;
let _ = self.abort_tx.send(());
let _ = self.ingress_task.await.map_err(|_| Error::StopRedirect)?;
let _ = self.egress_task.await.map_err(|_| Error::StopRedirect)??;
@@ -131,12 +137,14 @@ impl SplitTunnelHandle {
}
pub async fn set_interfaces(
- self,
+ mut self,
default_interface: DefaultInterface,
vpn_interface: Option<VpnInterface>,
) -> Result<Self, Error> {
let _ = self.abort_tx.send(());
+ self.abort_mtu_listener().await;
+
let st_utun = self.ingress_task.await.map_err(|_| Error::StopRedirect)?;
let egress_completion = self.egress_task.await.map_err(|_| Error::StopRedirect)??;
@@ -146,9 +154,17 @@ impl SplitTunnelHandle {
egress_completion.pktap_stream,
default_interface,
vpn_interface,
+ self.route_manager,
egress_completion.classify,
)
}
+
+ async fn abort_mtu_listener(&mut self) {
+ if let Some(mtu_listener) = self.mtu_listener.take() {
+ mtu_listener.abort();
+ let _ = mtu_listener.await;
+ }
+ }
}
/// Create split tunnel device and handle all packets using `classify`. Handle any changes to the
@@ -161,10 +177,17 @@ impl SplitTunnelHandle {
pub async fn create_split_tunnel(
default_interface: DefaultInterface,
vpn_interface: Option<VpnInterface>,
+ route_manager: RouteManagerHandle,
classify: ClassifyFn,
) -> Result<SplitTunnelHandle, Error> {
let tun_device = create_utun().await?;
- redirect_packets(tun_device, default_interface, vpn_interface, classify)
+ redirect_packets(
+ tun_device,
+ default_interface,
+ vpn_interface,
+ route_manager,
+ classify,
+ )
}
/// Create a utun device for split tunneling, and configure its IP addresses.
@@ -207,6 +230,7 @@ fn redirect_packets(
st_tun_device: tun::AsyncDevice,
default_interface: DefaultInterface,
vpn_interface: Option<VpnInterface>,
+ route_manager: RouteManagerHandle,
classify: ClassifyFn,
) -> Result<SplitTunnelHandle, Error> {
let pktap_stream = capture_outbound_packets(st_tun_device.get_ref().name())?;
@@ -215,6 +239,7 @@ fn redirect_packets(
Box::pin(pktap_stream),
default_interface,
vpn_interface,
+ route_manager,
Box::new(classify),
)
}
@@ -232,8 +257,17 @@ fn redirect_packets_for_pktap_stream(
pktap_stream: PktapStream,
default_interface: DefaultInterface,
vpn_interface: Option<VpnInterface>,
+ route_manager: RouteManagerHandle,
classify: ClassifyFn,
) -> Result<SplitTunnelHandle, Error> {
+ let mtu_listener = vpn_interface.as_ref().map(|vpn_interface| {
+ tokio::spawn(mtu_updater(
+ st_tun_device.get_ref().name().to_owned(),
+ vpn_interface.name.clone(),
+ route_manager.clone(),
+ ))
+ });
+
let (default_stream, default_write, read_buffer_size) = open_default_bpf(&default_interface)?;
let st_utun_name = st_tun_device.get_ref().name().to_owned();
@@ -265,9 +299,62 @@ fn redirect_packets_for_pktap_stream(
abort_tx,
ingress_task,
egress_task,
+ mtu_listener,
+ route_manager,
})
}
+/// Listen for changes to VPN interface MTU and apply them to the ST utun accordingly
+async fn mtu_updater(
+ st_interface_name: String,
+ vpn_interface_name: String,
+ route_manager: RouteManagerHandle,
+) {
+ let vpn_tun_index = match if_nametoindex(vpn_interface_name.as_str()) {
+ Ok(index) => u16::try_from(index).unwrap(),
+ Err(error) => {
+ log::error!("Failed to obtain VPN utun index: {error}");
+ return;
+ }
+ };
+ let mut current_mtu = match talpid_net::unix::get_mtu(&vpn_interface_name) {
+ Ok(mtu) => mtu,
+ Err(error) => {
+ log::error!("Failed to fetch current VPN tunnel MTU: {error}");
+ return;
+ }
+ };
+
+ try_update_mtu(&st_interface_name, current_mtu);
+
+ let mut listener = match route_manager.interface_change_listener().await {
+ Ok(listener) => listener,
+ Err(error) => {
+ log::warn!("Failed to start interface listener: {error}");
+ return;
+ }
+ };
+ while let Some(details) = listener.next().await {
+ if details.interface_index != vpn_tun_index || details.mtu == current_mtu {
+ continue;
+ }
+ current_mtu = details.mtu;
+ try_update_mtu(&st_interface_name, current_mtu);
+ }
+}
+
+/// Try to update the MTU of `st_iface_name`, and log if this fails
+fn try_update_mtu(st_iface_name: &str, mtu: u16) {
+ match talpid_net::unix::set_mtu(st_iface_name, mtu) {
+ Ok(()) => {
+ log::debug!("ST interface MTU: {mtu}");
+ }
+ Err(error) => {
+ log::error!("Failed to set MTU of {st_iface_name} to {mtu}: {error}");
+ }
+ }
+}
+
/// Open a BPF device for the specified default interface. Return a read and write half, and the
/// buffer size.
fn open_default_bpf(