Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## Unreleased: mitmproxy_rs next

- tun mode: allow using a pre-configured persistent tun interface, to avoid requiring CAP_NET_ADMIN.

## 29 April 2025: mitmproxy_rs 0.12.3

Expand Down
39 changes: 32 additions & 7 deletions src/packet_sources/tun.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::packet_sources::{PacketSourceConf, PacketSourceTask};
use crate::shutdown;
use anyhow::{Context, Result};
use std::cmp::max;
use std::fs;
use std::{fs, io::ErrorKind};
use tokio::sync::mpsc::Sender;
use tokio::sync::mpsc::{Permit, Receiver, UnboundedReceiver};
use tun::AbstractDevice;
Expand Down Expand Up @@ -54,14 +54,40 @@ pub fn create_tun_device(tun_name: Option<String>) -> Result<(tun::AsyncDevice,
// config.netmask("0.0.0.0");
// config.destination("169.254.0.1");
config.up();
if let Some(tun_name) = tun_name {
config.tun_name(&tun_name);
if let Some(tun_name) = &tun_name {
config.tun_name(tun_name);
}

let device = tun::create_as_async(&config).context("Failed to create TUN device")?;
let tun_name = device.tun_name().context("Failed to get TUN name")?;
match tun::create_as_async(&config) {
Ok(device) => {
let tun_name = device.tun_name().context("Failed to get TUN name")?;
configure_device(&tun_name);
Ok((device, tun_name))
}
Err(tun::Error::Io(e)) if e.kind() == ErrorKind::PermissionDenied => {
// If we are instructed to create a tun device with a specific name, it is possible that the
// user wants us to reuse an existing persistent tun interface. A persistent tun interface
// is usually pre-configured, so that mitmproxy does not need to perform configuration, and
// therefore does not need CAP_NET_ADMIN or sudo.
//
// The default `config` will set MTU and address etc which *do* require CAP_NET_ADMIN, which
// will result in PermissionDenied in the non-privileged context. To deal with the case of
// pre-configured persistent interface, we retry `create_as_async` without the MTU/address
// settings.
if let Some(tun_name) = tun_name {
tun::create_as_async(tun::Configuration::default().tun_name(&tun_name))
.map(|d| (d, tun_name))
} else {
Err(tun::Error::Io(e))
}
}
Err(e) => Err(e),
}
.context("Failed to create TUN device")
}

if let Err(e) = disable_rp_filter(&tun_name) {
fn configure_device(tun_name: &str) {
if let Err(e) = disable_rp_filter(tun_name) {
log::error!("failed to set rp_filter: {e}");
}
if let Err(e) = fs::write(
Expand All @@ -78,7 +104,6 @@ pub fn create_tun_device(tun_name: Option<String>) -> Result<(tun::AsyncDevice,
) {
log::error!("Failed to enable accept_local: {e}");
}
Ok((device, tun_name))
}

pub struct TunTask {
Expand Down