diff --git a/crates/nibrunnerd/src/adapters/net/tap.rs b/crates/nibrunnerd/src/adapters/net/tap.rs index 74d64a79..99bc511e 100644 --- a/crates/nibrunnerd/src/adapters/net/tap.rs +++ b/crates/nibrunnerd/src/adapters/net/tap.rs @@ -36,6 +36,14 @@ pub trait HostNetwork: Send + Sync { async fn refresh_neighbour(&self, neighbour: &Neighbour) -> Result<(), NetworkError>; + async fn set_tap_owner(&self, tap_name: &str, _uid: u32) -> Result<(), NetworkError> { + Err(NetworkError { + what: "tap ownership", + device: tap_name.into(), + reason: "this network adapter cannot assign an owner".into(), + }) + } + // A tap outlives the process that made it, which is what lets a daemon restart leave every // tenant serving. The same persistence means nothing reclaims one, so an app that leaves has // to say so. @@ -140,6 +148,43 @@ mod linux { Ok(()) } + #[allow( + unsafe_code, + reason = "assigning a persistent TAP to its jailed VMM requires TUNSETOWNER" + )] + fn set_tap_owner(tap_name: &str, uid: u32) -> Result<(), NetworkError> { + use std::os::fd::AsRawFd; + const TUNSETIFF: libc::Ioctl = 0x400454ca; + const TUNSETOWNER: libc::Ioctl = 0x400454cc; + const IFF_TAP: libc::c_short = 0x0002; + const IFF_NO_PI: libc::c_short = 0x1000; + #[repr(C)] + struct InterfaceRequest { + name: [libc::c_char; libc::IFNAMSIZ], + flags: libc::c_short, + padding: [u8; 22], + } + let device = std::fs::OpenOptions::new() + .read(true) + .write(true) + .open("/dev/net/tun") + .map_err(|error| failed("tap ownership", tap_name, error))?; + let mut request = InterfaceRequest { + name: [0; libc::IFNAMSIZ], + flags: IFF_TAP | IFF_NO_PI, + padding: [0; 22], + }; + for (index, byte) in tap_name.as_bytes().iter().take(libc::IFNAMSIZ - 1).enumerate() { + request.name[index] = *byte as libc::c_char; + } + if unsafe { libc::ioctl(device.as_raw_fd(), TUNSETIFF, &mut request) } < 0 + || unsafe { libc::ioctl(device.as_raw_fd(), TUNSETOWNER, uid) } < 0 + { + return Err(failed("tap ownership", tap_name, std::io::Error::last_os_error())); + } + Ok(()) + } + fn allow_route_localnet(tap_name: &str) -> Result<(), NetworkError> { let path = format!("/proc/sys/net/ipv4/conf/{tap_name}/route_localnet"); std::fs::write(&path, b"1").map_err(|error| failed("route_localnet", tap_name, error)) @@ -171,6 +216,10 @@ mod linux { allow_route_localnet(&tap.tap_name) } + async fn set_tap_owner(&self, tap_name: &str, uid: u32) -> Result<(), NetworkError> { + set_tap_owner(tap_name, uid) + } + async fn refresh_neighbour(&self, neighbour: &Neighbour) -> Result<(), NetworkError> { let index = self .index_of(&neighbour.tap_name) diff --git a/crates/nibrunnerd/src/test_support/mocks.rs b/crates/nibrunnerd/src/test_support/mocks.rs index 5d06f70d..bd80f80b 100644 --- a/crates/nibrunnerd/src/test_support/mocks.rs +++ b/crates/nibrunnerd/src/test_support/mocks.rs @@ -505,12 +505,17 @@ pub fn exports_answering( #[derive(Clone, Default)] pub struct NetworkSpy { + owners: Arc>>, taps: Arc>>, neighbours: Arc>>, removed: Arc>>, } impl NetworkSpy { + pub fn owners(&self) -> Vec<(String, u32)> { + held(&self.owners) + } + pub fn taps(&self) -> Vec { held(&self.taps) } @@ -533,6 +538,11 @@ pub fn network() -> (Arc, NetworkSpy) { push(&taps, tap.clone()); Ok(()) }); + let owners = spy.owners.clone(); + network.expect_set_tap_owner().returning(move |name, uid| { + push(&owners, (name.to_string(), uid)); + Ok(()) + }); let neighbours = spy.neighbours.clone(); network .expect_refresh_neighbour() diff --git a/crates/nibrunnerd/tests/integration.rs b/crates/nibrunnerd/tests/integration.rs index 38daab99..138aafa7 100644 --- a/crates/nibrunnerd/tests/integration.rs +++ b/crates/nibrunnerd/tests/integration.rs @@ -270,6 +270,9 @@ async fn a_tap_is_created_addressed_and_given_the_guest_it_will_hold() { .await .expect("a second pass changes nothing"); assert!(network.tap_names().await.contains(&slot.tap_name)); + network.set_tap_owner(&slot.tap_name, 100_000).await.unwrap(); + let owner = std::fs::read_to_string(format!("/sys/class/net/{}/owner", slot.tap_name)).unwrap(); + assert_eq!(owner.trim(), "100000"); network .refresh_neighbour(&Neighbour {