diff options
| author | David Lönnhager <david.l@mullvad.net> | 2024-04-19 18:51:15 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2024-04-30 16:22:52 +0200 |
| commit | 5e947ca34ca69e234f8de240a321aac0b27f4419 (patch) | |
| tree | 1de534c6e6f664750337328af035ce39db1076a2 | |
| parent | 9c257c424a5ed4d531fec8e813f44a4ede0baaee (diff) | |
| download | mullvadvpn-5e947ca34ca69e234f8de240a321aac0b27f4419.tar.xz mullvadvpn-5e947ca34ca69e234f8de240a321aac0b27f4419.zip | |
Refactor tun module
| -rw-r--r-- | talpid-core/src/split_tunnel/macos/mod.rs | 4 | ||||
| -rw-r--r-- | talpid-core/src/split_tunnel/macos/tun.rs | 94 | ||||
| -rw-r--r-- | talpid-routing/src/unix/macos/interface.rs | 3 |
3 files changed, 63 insertions, 38 deletions
diff --git a/talpid-core/src/split_tunnel/macos/mod.rs b/talpid-core/src/split_tunnel/macos/mod.rs index f7039a0eaf..77a81af627 100644 --- a/talpid-core/src/split_tunnel/macos/mod.rs +++ b/talpid-core/src/split_tunnel/macos/mod.rs @@ -415,7 +415,7 @@ impl State { let result = tun::create_split_tunnel( default_interface, new_vpn_interface.clone(), - move |packet| { + Box::new(move |packet| { match states.get_process_status(packet.header.pth_pid as u32) { ExclusionStatus::Excluded => tun::RoutingDecision::DefaultInterface, ExclusionStatus::Included => tun::RoutingDecision::VpnTunnel, @@ -424,7 +424,7 @@ impl State { tun::RoutingDecision::Drop } } - }, + }), ) .await; diff --git a/talpid-core/src/split_tunnel/macos/tun.rs b/talpid-core/src/split_tunnel/macos/tun.rs index 1121bdb2df..3f70e1c655 100644 --- a/talpid-core/src/split_tunnel/macos/tun.rs +++ b/talpid-core/src/split_tunnel/macos/tun.rs @@ -168,28 +168,39 @@ impl SplitTunnelHandle { pub async fn create_split_tunnel( default_interface: DefaultInterface, vpn_interface: Option<VpnInterface>, - classify: impl Fn(&PktapPacket) -> RoutingDecision + Send + 'static, + classify: ClassifyFn, ) -> Result<SplitTunnelHandle, Error> { + let tun_device = create_utun().await?; + redirect_packets(tun_device, default_interface, vpn_interface, classify) +} + +/// Create a utun device for split tunneling, and configure its IP addresses. +async fn create_utun() -> Result<tun::AsyncDevice, Error> { let mut tun_config = tun::configure(); tun_config.address(ST_IFACE_IPV4).up(); let tun_device = tun::create_as_async(&tun_config).map_err(Error::CreateSplitTunnelInterface)?; let tun_name = tun_device.get_ref().name().to_owned(); + add_ipv6_address(&tun_name, ST_IFACE_IPV6).await?; + Ok(tun_device) +} - // Add IPv6 address +/// Set the given IPv6 address `addr` as an IP address for the interface `iface`. +async fn add_ipv6_address(iface: &str, addr: Ipv6Addr) -> Result<(), Error> { let output = tokio::process::Command::new("ifconfig") - .args([&tun_name, "inet6", &ST_IFACE_IPV6.to_string(), "alias"]) + .args([iface, "inet6", &addr.to_string(), "alias"]) .output() .await .map_err(Error::AddIpv6Address)?; if !output.status.success() { return Err(Error::AddIpv6Address(io::Error::other("ifconfig failed"))); } - - redirect_packets(tun_device, default_interface, vpn_interface, classify) + Ok(()) } type PktapStream = std::pin::Pin<Box<dyn Stream<Item = Result<PktapPacket, Error>> + Send>>; +/// A function that is used to classify whether packets should be VPN-tunneled or excluded +type ClassifyFn = Box<dyn Fn(&PktapPacket) -> RoutingDecision + Send>; /// Monitor outgoing traffic on `st_tun_device` using a pktap. A routing decision is /// made for each packet using `classify`. Based on this, a packet is forced out on either @@ -203,7 +214,7 @@ fn redirect_packets( st_tun_device: tun::AsyncDevice, default_interface: DefaultInterface, vpn_interface: Option<VpnInterface>, - classify: impl Fn(&PktapPacket) -> RoutingDecision + Send + 'static, + classify: ClassifyFn, ) -> Result<SplitTunnelHandle, Error> { let pktap_stream = capture_outbound_packets(st_tun_device.get_ref().name())?; redirect_packets_for_pktap_stream( @@ -228,28 +239,12 @@ fn redirect_packets_for_pktap_stream( pktap_stream: PktapStream, default_interface: DefaultInterface, vpn_interface: Option<VpnInterface>, - classify: Box<dyn Fn(&PktapPacket) -> RoutingDecision + Send>, + classify: ClassifyFn, ) -> Result<SplitTunnelHandle, Error> { - let default_dev = bpf::Bpf::open().map_err(Error::CreateDefaultBpf)?; - let read_buffer_size = default_dev - .set_buffer_size(DEFAULT_BUFFER_SIZE) - .map_err(Error::ConfigDefaultBpf)?; - default_dev - .set_interface(&default_interface.name) - .map_err(Error::ConfigDefaultBpf)?; - default_dev - .set_immediate(true) - .map_err(Error::ConfigDefaultBpf)?; - default_dev - .set_see_sent(false) - .map_err(Error::ConfigDefaultBpf)?; + 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(); - let (default_read, default_write) = default_dev.split().map_err(Error::ConfigDefaultBpf)?; - let default_stream = - bpf::BpfStream::from_read_half(default_read).map_err(Error::CreateDefaultBpf)?; - let (abort_tx, abort_rx) = broadcast::channel(1); let ingress_task: tokio::task::JoinHandle<tun::AsyncDevice> = tokio::spawn(run_ingress_task( @@ -278,6 +273,32 @@ fn redirect_packets_for_pktap_stream( }) } +/// Open a BPF device for the specified default interface. Return a read and write half, and the buffer size. +fn open_default_bpf( + default_interface: &DefaultInterface, +) -> Result<(bpf::BpfStream, bpf::WriteHalf, usize), Error> { + let default_dev = bpf::Bpf::open().map_err(Error::CreateDefaultBpf)?; + let read_buffer_size = default_dev + .set_buffer_size(DEFAULT_BUFFER_SIZE) + .map_err(Error::ConfigDefaultBpf)?; + default_dev + .set_interface(&default_interface.name) + .map_err(Error::ConfigDefaultBpf)?; + default_dev + .set_immediate(true) + .map_err(Error::ConfigDefaultBpf)?; + default_dev + .set_see_sent(false) + .map_err(Error::ConfigDefaultBpf)?; + + // Split the default device BPF handle into a read and write half + let (default_read, default_write) = default_dev.split().map_err(Error::ConfigDefaultBpf)?; + let default_stream = + bpf::BpfStream::from_read_half(default_read).map_err(Error::CreateDefaultBpf)?; + + Ok((default_stream, default_write, read_buffer_size)) +} + /// Read incoming packets on the default interface and send them back to the ST utun. async fn run_ingress_task( st_tun_device: tun::AsyncDevice, @@ -345,27 +366,21 @@ async fn run_ingress_task( /// Arguments to `run_egress_task` that are returned when the function succeeds struct EgressResult { pktap_stream: PktapStream, - classify: Box<dyn Fn(&PktapPacket) -> RoutingDecision + Send>, + classify: ClassifyFn, } /// Read outgoing packets and send them out on either the default interface or VPN interface, /// based on the result of `classify`. async fn run_egress_task( mut pktap_stream: PktapStream, - classify: Box<dyn Fn(&PktapPacket) -> RoutingDecision + Send>, + classify: ClassifyFn, default_interface: DefaultInterface, mut default_write: bpf::WriteHalf, vpn_interface: Option<VpnInterface>, mut abort_rx: broadcast::Receiver<()>, ) -> Result<EgressResult, Error> { let mut vpn_dev = if let Some(ref vpn_interface) = vpn_interface { - let vpn_dev = bpf::Bpf::open().map_err(Error::CreateVpnBpf)?; - vpn_dev - .set_interface(&vpn_interface.name) - .map_err(Error::ConfigVpnBpf)?; - vpn_dev.set_immediate(true).map_err(Error::ConfigVpnBpf)?; - vpn_dev.set_see_sent(false).map_err(Error::ConfigVpnBpf)?; - Some(vpn_dev) + Some(open_vpn_bpf(vpn_interface)?) } else { None }; @@ -394,8 +409,19 @@ async fn run_egress_task( } } +/// Open a BPF device for the specified VPN interface +fn open_vpn_bpf(vpn_interface: &VpnInterface) -> Result<bpf::Bpf, Error> { + let vpn_dev = bpf::Bpf::open().map_err(Error::CreateVpnBpf)?; + vpn_dev + .set_interface(&vpn_interface.name) + .map_err(Error::ConfigVpnBpf)?; + vpn_dev.set_immediate(true).map_err(Error::ConfigVpnBpf)?; + vpn_dev.set_see_sent(false).map_err(Error::ConfigVpnBpf)?; + Ok(vpn_dev) +} + fn classify_and_send( - classify: &(dyn Fn(&PktapPacket) -> RoutingDecision), + classify: &ClassifyFn, packet: &mut PktapPacket, default_interface: &DefaultInterface, default_write: &mut bpf::WriteHalf, diff --git a/talpid-routing/src/unix/macos/interface.rs b/talpid-routing/src/unix/macos/interface.rs index c117a7bed3..25e3d2e3cf 100644 --- a/talpid-routing/src/unix/macos/interface.rs +++ b/talpid-routing/src/unix/macos/interface.rs @@ -169,12 +169,11 @@ impl PrimaryInterfaceMonitor { .into_iter() .filter_map(|iface| { let index = if_nametoindex(iface.name.as_str()) - .map_err(|error| { + .inspect_err(|error| { log::error!( "Failed to retrieve interface index for \"{}\": {error}", iface.name ); - error }) .ok()?; Some((iface, index)) |
