summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2024-04-19 18:51:15 +0200
committerDavid Lönnhager <david.l@mullvad.net>2024-04-30 16:22:52 +0200
commit5e947ca34ca69e234f8de240a321aac0b27f4419 (patch)
tree1de534c6e6f664750337328af035ce39db1076a2
parent9c257c424a5ed4d531fec8e813f44a4ede0baaee (diff)
downloadmullvadvpn-5e947ca34ca69e234f8de240a321aac0b27f4419.tar.xz
mullvadvpn-5e947ca34ca69e234f8de240a321aac0b27f4419.zip
Refactor tun module
-rw-r--r--talpid-core/src/split_tunnel/macos/mod.rs4
-rw-r--r--talpid-core/src/split_tunnel/macos/tun.rs94
-rw-r--r--talpid-routing/src/unix/macos/interface.rs3
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))