From e22e4587a47865f21127a91513427711a3d7e90f Mon Sep 17 00:00:00 2001 From: Guillermo Roche Date: Sat, 9 Nov 2024 14:52:11 +0100 Subject: [PATCH] try with fork instead of clone and fix wireguard inicialization --- src/main.rs | 52 +++------- src/manage_interfaces/mod.rs | 20 +++- src/namespace/create_ns.rs | 191 ++++++++++++++++++++++++++++------- 3 files changed, 184 insertions(+), 79 deletions(-) diff --git a/src/main.rs b/src/main.rs index 8970cb2..38b96e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,12 +2,11 @@ mod namespace; mod manage_interfaces; mod wireguard_manager; mod config; -use std::io::Result; + use rtnetlink::NetworkNamespace; use futures::executor::block_on; -use base64::prelude::*; -fn main(){ +fn main() { env_logger::Builder::from_default_env() .format_timestamp_secs() .filter(None, log::LevelFilter::Debug) @@ -27,18 +26,23 @@ fn main(){ } }, _ => {}, - } + }; } -pub fn create_namespace(vpn: config::ConsumableVPNConfig) { +fn create_namespace(vpn: config::ConsumableVPNConfig) { let ns_name = vpn.namespace_name.clone(); block_on(NetworkNamespace::add(ns_name.clone())).unwrap(); - namespace::bind_interface::run_in_namespace(|| { + namespace::create_ns::run_in_namespace(|| { manage_interfaces::set_interface_lo_up().unwrap(); }, &ns_name).unwrap(); - namespace::bind_interface::run_in_namespace(|| { - manage_interfaces::create_wireguard_interface(vpn.interface_name.clone(), + + manage_interfaces::create_and_move_wireguard_interface( + vpn.interface_name.clone(), + ns_name.clone() + ).unwrap(); + namespace::create_ns::run_in_namespace(|| { + manage_interfaces::configure_wireguard_interface(vpn.interface_name.clone(), vpn.ip.clone(), vpn.endpoint.ip.clone(), vpn.prefix as u8, @@ -48,35 +52,3 @@ pub fn create_namespace(vpn: config::ConsumableVPNConfig) { &ns_name).unwrap(); } -/*fn main2() { - env_logger::Builder::from_default_env() - .format_timestamp_secs() - .filter(None, log::LevelFilter::Debug) - .init(); - - let priv_key_dirty = BASE64_STANDARD.decode(b"k1").unwrap(); - let pub_key_dirty = BASE64_STANDARD.decode(b"k2").unwrap(); - let mut priv_key: [u8; 32] = Default::default(); - let mut pub_key: [u8; 32] = Default::default(); - priv_key.copy_from_slice(&priv_key_dirty[0..32]); - pub_key.copy_from_slice(&pub_key_dirty[0..32]); - - //namespace::create_ns::create_ns(); - let ns_name = "test-newns".to_string(); - block_on(NetworkNamespace::add(ns_name.clone())).unwrap(); - namespace::bind_interface::run_in_namespace(|| { - manage_interfaces::set_interface_lo_up().unwrap(); - }, - &ns_name).unwrap(); - namespace::bind_interface::run_in_namespace(|| { - manage_interfaces::create_wireguard_interface(String::from("wgzurich"), - String::from("ip1"), - String::from("ip2"), - 24, - pub_key, - priv_key).unwrap(); - }, - &ns_name).unwrap(); - //println!("{}",wireguard_manager::add_properties::set_params(pub_key, priv_key)) -} -*/ diff --git a/src/manage_interfaces/mod.rs b/src/manage_interfaces/mod.rs index e3f41ae..4ec681b 100644 --- a/src/manage_interfaces/mod.rs +++ b/src/manage_interfaces/mod.rs @@ -1,10 +1,25 @@ use rtnetlink::{new_connection, Error, Handle}; use netlink_packet_route::link::LinkMessage; use std::net::IpAddr; +use std::os::fd::AsRawFd; use futures::TryStreamExt; mod netlink; -pub fn create_wireguard_interface( +pub fn create_and_move_wireguard_interface( + interface_name: String, + namespace_name: String, +) -> Result<(), Error> { + tokio::runtime::Runtime::new().unwrap().handle().block_on( async { + let (connection, handle, _) = new_connection().unwrap(); + tokio::spawn(connection); + netlink::create_wireguard_interface(handle.clone(), interface_name.clone()).await?; + let link = netlink::get_link_interface(handle.clone(), interface_name.clone()).await?; + handle.link().set(link.header.index).setns_by_fd(crate::namespace::create_ns::get_ns_fd(&namespace_name).unwrap().as_raw_fd()).execute().await?; + Ok(()) + }) +} + +pub fn configure_wireguard_interface( interface_name: String, interface_ip: String, peer_ip: String, @@ -14,7 +29,7 @@ pub fn create_wireguard_interface( tokio::runtime::Runtime::new().unwrap().handle().block_on( async { let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); - netlink::create_wireguard_interface(handle.clone(), interface_name.clone()).await?; + //netlink::create_wireguard_interface(handle.clone(), interface_name.clone()).await?; let link = netlink::get_link_interface(handle.clone(), interface_name.clone()).await?; netlink::assign_ip(handle.clone(), link.clone(), interface_ip, prefix).await?; crate::wireguard_manager::add_properties::set_params(wg_pub_key, wg_priv_key, peer_ip, interface_name.clone()); @@ -93,3 +108,4 @@ pub fn get_inferfaces() -> Result<(), Error> { }); Ok(()) } + diff --git a/src/namespace/create_ns.rs b/src/namespace/create_ns.rs index 4e9331c..af8022c 100644 --- a/src/namespace/create_ns.rs +++ b/src/namespace/create_ns.rs @@ -1,43 +1,160 @@ -use std::fs::create_dir; -use std::io::{Result, ErrorKind}; -use std::fs::File; -use crate::namespace::consts::{ - NET_NS_DIR, - PROC_NS_DIR -}; -use nix::mount::{ - mount, - MsFlags -}; +use nix::sched::{CloneFlags, unshare, setns}; +use crate::namespace::consts::NET_NS_DIR; + +use nix::fcntl::{open, OFlag}; +use nix::mount::{mount, MsFlags}; +use nix::unistd::{fork, ForkResult, Pid}; +use nix::sys::wait::{waitpid, WaitStatus}; +use nix::sys::stat::Mode; +use nix::sys::statvfs::{statvfs, FsFlags}; +use std::os::unix::io::RawFd; + +use std::path::{Path, PathBuf}; +use std::process::exit; +use std::fs::{File, OpenOptions}; +use std::os::fd::FromRawFd; + +pub fn run_in_namespace(f: F,ns_name: &String) -> Result<(), ()> where F:FnMut() + Copy { + prep_for_fork()?; + // Configure networking in the child namespace: + // Fork a process that is set to the newly created namespace + // Here set the veth ip addr, routing tables etc. + // Unfortunately the NetworkNamespace interface of rtnetlink does + // not offer these functionalities + match unsafe { fork() } { + Ok(ForkResult::Parent { child, .. }) => { + // Parent process + log::debug!("Net configuration PID: {}", child.as_raw()); + run_parent(child) + } + Ok(ForkResult::Child) => { + // Child process + // Move the child to the target namespace + run_child(f, ns_name) + } + Err(e) => { + log::error!("Can not fork() for ns creation: {}", e); + return Err(()); + } + } + +} + +fn run_parent(child: Pid) -> Result<(), ()> { + log::trace!("[Parent] Child PID: {}", child); + match waitpid(child, None) { + Ok(wait_status) => match wait_status { + WaitStatus::Exited(_, res) => { + log::trace!("Child exited with: {}", res); + if res == 0 { + return Ok(()); + } else { + log::error!("Child exited with status {}", res); + return Err(()); + } + } + WaitStatus::Signaled(_, signal, coredump) => { + log::error!("Child process killed by signal"); + return Err(()); + } + _ => { + log::error!("Unknown child process status: {:?}", wait_status); + return Err(()); + } + } + Err(e) => { + log::error!("wait error : {}", e); + return Err(()); + } + } + +} + +fn run_child(mut f: F, ns_name: &String) -> Result<(), ()> where F:FnMut() { + let res = split_namespace(ns_name); + + match res { + Err(_) => { + log::error!("Child process crashed"); + std::process::abort() + } + Ok(()) => { + log::debug!("Child exited normally"); + f(); + exit(0) + } + } +} + +pub fn get_ns_fd(ns_name: &String) -> Result { + // Open NS path + let ns_path = format!("{}/{}", NET_NS_DIR, ns_name); + let mut open_flags = OFlag::empty(); + open_flags.insert(OFlag::O_RDONLY); + open_flags.insert(OFlag::O_CLOEXEC); + + Ok(match open(Path::new(&ns_path), open_flags, Mode::empty()) { + Ok(raw_fd) => unsafe { + File::from_raw_fd(raw_fd) + } + Err(e) => { + log::error!("Can not open network namespace: {}", e); + return Err(()); + } + }) +} + +fn split_namespace(ns_name: &String) -> Result<(), ()> { + let fd = get_ns_fd(ns_name)?; + // Switch to network namespace with CLONE_NEWNET + if let Err(e) = setns(fd, CloneFlags::CLONE_NEWNET) { + log::error!("Can not set namespace to target {}: {}", ns_name, e); + return Err(()); + } + // unshare with CLONE_NEWNS + if let Err(e) = unshare(CloneFlags::CLONE_NEWNS) { + log::error!("Can not unshare: {}", e); + return Err(()); + } + // mount blind the fs + // let's avoid that any mount propagates to the parent process + // mount_directory(None, &PathBuf::from("/"), vec![MsFlags::MS_REC, MsFlags::MS_PRIVATE])?; + let mut mount_flags = MsFlags::empty(); + mount_flags.insert(MsFlags::MS_REC); + mount_flags.insert(MsFlags::MS_PRIVATE); + if let Err(_e) = mount::(None, &PathBuf::from("/"), None, mount_flags, None) { + log::error!("Can not remount root directory"); + () + } + + // Now unmount /sys + let sys_path = PathBuf::from("/sys"); + mount_flags = MsFlags::empty(); + // Needed to respect the trait for NixPath + let ns_name_path = PathBuf::from(ns_name); + + // TODO do not exit for EINVAL error + // unmount_path(&sys_path)?; + // consider the case that a sysfs is not present + let stat_sys = statvfs(&sys_path) + .map_err(|e| { + log::error!("Can not stat sys: {}", e); + }).unwrap(); + if stat_sys.flags().contains(FsFlags::ST_RDONLY) { + mount_flags.insert(MsFlags::MS_RDONLY); + } + + // and remount a version of /sys that describes the network namespace + if let Err(e) = mount::(Some(&ns_name_path), &sys_path, Some("sysfs"), mount_flags, None) { + log::error!("Can not remount /sys to namespace: {}", e); + () + } -pub fn create_ns() -> Result<()> { - create_ns_dir()?; - let nsdir = format!("{}/{}",NET_NS_DIR,"fake-net"); - File::create(nsdir.clone())?; - bind_ns_file(nsdir)?; Ok(()) } -fn create_ns_dir() -> Result<()> { - match create_dir(NET_NS_DIR) { - Err(e) if e.kind() != ErrorKind::AlreadyExists => { - Err(e) - }, - _ => { - Ok(()) - }, - } -} -fn bind_ns_file(ns_file: String) -> Result<()> { - match create_mount(ns_file) { - Ok(_mount) => { - Ok(()) - }, - Err(e) => Err(e), - } -} - -fn create_mount(ns_file: String) -> Result<()> { - Ok(mount::(Some(PROC_NS_DIR), ns_file.as_str(), None, MsFlags::MS_BIND, None)?) +// Cargo cult from the definition in rtnetlink +fn prep_for_fork() -> Result<(), ()> { + Ok(()) }