From bd111a277bd5f8c29f70530d9aff3ee2c65e5783 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Mon, 29 Jul 2024 19:43:41 +0800 Subject: [PATCH 1/2] refactor: split platform. --- crates/wasi/src/nat/linux.rs | 184 ++++++++++++++++++++++++++++++++++ crates/wasi/src/nat/macos.rs | 46 +++++++++ crates/wasi/src/nat/macros.rs | 19 ++++ crates/wasi/src/nat/mod.rs | 118 +++------------------- 4 files changed, 265 insertions(+), 102 deletions(-) create mode 100644 crates/wasi/src/nat/linux.rs create mode 100644 crates/wasi/src/nat/macos.rs create mode 100644 crates/wasi/src/nat/macros.rs diff --git a/crates/wasi/src/nat/linux.rs b/crates/wasi/src/nat/linux.rs new file mode 100644 index 0000000..b2fd37e --- /dev/null +++ b/crates/wasi/src/nat/linux.rs @@ -0,0 +1,184 @@ +#![allow(non_snake_case)] +#![allow(non_camel_case_types)] +use std::{ffi::c_char, mem::MaybeUninit}; +use std::{ + fs, io::Write, process::{Command, Stdio} +}; +use std::mem; + +use super::NatError; + + +const ETHTOOL_FWVERS_LEN: usize = 32; +const ETHTOOL_BUSINFO_LEN: usize = 32; +const ETHTOOL_EROMVERS_LEN: usize = 32; +const SIOCETHTOOL: usize = 0x8946; +const ETHTOOL_GDRVINFO: usize = 0x00000003; +const ETH_GSTRING_LEN: usize = 32; +const ETHTOOL_GSTRINGS: usize = 32; +const ETH_SS_STATS: usize = 32; + +#[repr(C)] +#[derive(Copy, Clone)] +struct ethtool_gstrings { + cmd: u32, + string_set: u32, + len: u32, +} + +#[repr(C)] +#[derive(Copy, Clone)] +struct ethtool_drvinfo { + cmd: u32, + driver: [c_char; 32], + version: [c_char; 32], + fw_version: [c_char; ETHTOOL_FWVERS_LEN], + bus_info: [c_char; ETHTOOL_BUSINFO_LEN], + erom_version: [c_char; ETHTOOL_EROMVERS_LEN], + reserved2: [c_char; 12], + n_priv_flags: u32, + n_stats: u32, + testinfo_len: u32, + eedump_len: u32, + regdump_len: u32, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct ifreq { + pub ifr_ifrn: ifreq_ifrn, + pub ifr_ifru: ifreq_ifru, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub union ifreq_ifrn { + pub ifrn_name: [i8; 16usize], + align: [u8; 16usize], +} + +type sockaddr = libc::sockaddr; + +#[repr(C)] +#[derive(Copy, Clone)] +pub union ifreq_ifru { + pub ifru_addr: sockaddr, + pub ifru_dstaddr: sockaddr, + pub ifru_broadaddr: sockaddr, + pub ifru_netmask: sockaddr, + pub ifru_hwaddr: sockaddr, + pub ifru_flags: ::std::os::raw::c_short, + pub ifru_uflags: ::std::os::raw::c_ushort, + pub ifru_ivalue: ::std::os::raw::c_int, + pub ifru_mtu: ::std::os::raw::c_int, + pub ifru_bool: ::std::os::raw::c_uchar, + pub ifru_map: ifmap, + pub ifru_slave: [::std::os::raw::c_char; 16usize], + pub ifru_newname: [::std::os::raw::c_char; 16usize], + pub ifru_data: *mut ::std::os::raw::c_char, + align: [u64; 3usize], +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ifmap { + pub mem_start: ::std::os::raw::c_ulong, + pub mem_end: ::std::os::raw::c_ulong, + pub base_addr: ::std::os::raw::c_ushort, + pub irq: ::std::os::raw::c_uchar, + pub dma: ::std::os::raw::c_uchar, + pub port: ::std::os::raw::c_uchar, +} + +pub(crate)fn forward(enable: bool) -> Result<(), NatError> { + let mut ip_fwf = io_wrap!(fs::OpenOptions::new() + .write(true) + .open("/proc/sys/net/ipv4/ip_forward")); + let enable = if enable { b"1" } else { b"0" }; + io_wrap!(ip_fwf.write(enable)); + Ok(()) +} + +fn eth_info(devn: &str) -> Result<(), NatError> { + let sock: i32 = syscall!(libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0)) as _; + let mut ifreq = MaybeUninit::::zeroed(); + let mut drv_info = MaybeUninit::::zeroed(); + unsafe { + drv_info.assume_init_mut().cmd = ETHTOOL_GDRVINFO as _; + let req = ifreq.assume_init_mut(); + req.ifr_ifru.ifru_data = drv_info.as_mut_ptr() as _; + req.ifr_ifrn.ifrn_name.as_mut_ptr().copy_from(devn.as_ptr() as _, devn.len()); + } + syscall!(libc::ioctl(sock, SIOCETHTOOL as _, ifreq.as_mut_ptr())); + let n_stats = unsafe { + drv_info.assume_init_ref().n_stats + }; + if n_stats <= 0 { + return Err(NatError::NoStatError) + } + let size: usize = mem::size_of::() + n_stats as usize* ETH_GSTRING_LEN; + let mut gstrings_data = vec![0u8; size] ; + + unsafe { + let gstr = &mut *(gstrings_data.as_mut_ptr() as *mut ethtool_gstrings); + gstr.cmd = ETHTOOL_GSTRINGS as _; + gstr.string_set = ETH_SS_STATS as _; + gstr.len = n_stats as _; + let ifreq = ifreq.assume_init_mut(); + ifreq.ifr_ifru.ifru_data = gstrings_data.as_mut_ptr() as _; + } + syscall!(libc::ioctl(sock, SIOCETHTOOL as _, ifreq.as_mut_ptr())); + let data = gstrings_data.as_ptr(); + Ok(()) +} + +pub(crate)fn iptable() -> Result<(), NatError> { + let active_if = find_active_eth()?; + let mut command = Command::new("iptables"); + command.args(&["-t", "nat", "-A", "POSTROUTING", "-j", "MASQUERADE", "-o", &active_if]); + command.stdout(Stdio::piped()); + command.stderr(Stdio::piped()); + let child = io_wrap!(command.spawn()); + let output = io_wrap!(child.wait_with_output()); + if output.status.success() { + Ok(()) + } else { + Err(NatError::CommandError) + } +} + +fn find_active_eth() -> Result { + let net_path = std::path::Path::new("/sys/class/net"); + let read_dir = io_wrap!(fs::read_dir(&net_path)); + for entry in read_dir { + let entry = io_wrap!(entry); + let path = entry.path(); + if path.is_symlink() { + let path = io_wrap!(fs::read_link(&path)); + let path_str = path.to_str(); + if let Some(s) = path_str { + if s.contains("virtual") { + continue; + } + let split = s.split("/"); + if let Some(s) = split.last() { + return Ok(s.into()); + } + } + } + } + Err(NatError::NoInterfaceFound) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_eth_info() { + if let Ok(s) = find_active_eth() { + let rs = eth_info(&s); + assert!(rs.is_ok()); + } + } +} \ No newline at end of file diff --git a/crates/wasi/src/nat/macos.rs b/crates/wasi/src/nat/macos.rs new file mode 100644 index 0000000..b4b8206 --- /dev/null +++ b/crates/wasi/src/nat/macos.rs @@ -0,0 +1,46 @@ +use std::{ + fs::OpenOptions, io::Write, process::{Command, Stdio} +}; + +fn sysctl(enable: bool) -> Result<(), NatError> { + let mut command = Command::new("sysctl"); + let enable = if enable { 1 } else { 0 }; + command.args(&["-w", &format!("net.inet.ip.forwarding={enable}")]); + command.stdout(Stdio::piped()); + command.stderr(Stdio::piped()); + let mut child = io_wrap!(command.spawn()); + let exit_code = io_wrap!(child.wait()); + if exit_code.success() { + Ok(()) + } else { + Err(NatError::CommandError) + } +} + +fn pfctl() -> Result<(), NatError> { + let mut command = Command::new("pfctl"); + let child = io_wrap!(command.args(&["-f", "/etc/pf.anchors/bls-vm-nat", "-e" ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()); + let output = io_wrap!(child.wait_with_output()); + if output.status.success() { + return Ok(()); + } else { + let out_string = String::from_utf8(output.stderr).map_err(|_| NatError::Utf8CodeError)?; + if let Some(_) = out_string.find("pf already enabled") { + return Ok(()); + } + } + Err(NatError::CommandError) +} + +/// write the archors file +fn write_anchors(name: &str) { + let mut pfctl = OpenOptions::new() + .write(true) + .create(true) + .open("/etc/pf.anchors/bls-vm-nat")?; + let cmd = format!("nat on en0 from {name}:network to any -> (en0)\n"); + pfctl.write_all(cmd.as_bytes())?; +} diff --git a/crates/wasi/src/nat/macros.rs b/crates/wasi/src/nat/macros.rs new file mode 100644 index 0000000..dca3473 --- /dev/null +++ b/crates/wasi/src/nat/macros.rs @@ -0,0 +1,19 @@ + +macro_rules! io_wrap { + ($io_op:expr) => { + $io_op.map_err(|e| NatError::IoError(e))? + }; +} + +#[cfg(any(target_os="linux", target_os="macos"))] +macro_rules! syscall { + ($ep: expr) => { + unsafe { + let n = $ep as i64; + if n < 0 { + return Err(NatError::IoError(std::io::Error::last_os_error())); + } + n + } + }; +} \ No newline at end of file diff --git a/crates/wasi/src/nat/mod.rs b/crates/wasi/src/nat/mod.rs index f3262fe..2b95551 100644 --- a/crates/wasi/src/nat/mod.rs +++ b/crates/wasi/src/nat/mod.rs @@ -1,18 +1,24 @@ -use std::{ - fs::{self, OpenOptions}, io::{self, Write}, process::{Command, Stdio} -}; +#[macro_use] +mod macros; + use thiserror::Error; +#[cfg(target_os="linux")] +mod linux; +#[cfg(target_os="macos")] +mod macos; #[derive(Error, Debug)] pub enum NatError { #[error("execute command error.")] CommandError, #[error("io error. detail: {0}.")] - IoError(io::Error), + IoError(std::io::Error), #[error("utf8 code error.")] Utf8CodeError, - #[error("no interface found")] + #[error("no interface found.")] NoInterfaceFound, + #[error("no stat info.")] + NoStatError, } pub(crate)struct Nat { @@ -34,113 +40,21 @@ impl Nat { } } -macro_rules! io_wrap { - ($io_op:expr) => { - $io_op.map_err(|e| NatError::IoError(e))? - }; -} - #[cfg(target_os="linux")] impl Nat { - fn forward(enable: bool) -> Result<(), NatError> { - let mut ip_fwf = io_wrap!(fs::OpenOptions::new() - .write(true) - .open("/proc/sys/net/ipv4/ip_forward")); - let enable = if enable { b"1" } else { b"0" }; - io_wrap!(ip_fwf.write(enable)); - Ok(()) - } - - fn find_active_if() -> Result { - let net_path = std::path::Path::new("/sys/class/net"); - let read_dir = io_wrap!(fs::read_dir(&net_path)); - for entry in read_dir { - let entry = io_wrap!(entry); - let path = entry.path(); - if path.is_symlink() { - let path = io_wrap!(fs::read_link(&path)); - let path_str = path.to_str(); - if let Some(s) = path_str { - if s.contains("virtual") { - continue; - } - let split = s.split("/"); - if let Some(s) = split.last() { - return Ok(s.into()); - } - } - } - } - Err(NatError::NoInterfaceFound) - } - - fn iptable() -> Result<(), NatError> { - let active_if = Self::find_active_if()?; - let mut command = Command::new("iptables"); - command.args(&["-t", "nat", "-A", "POSTROUTING", "-j", "MASQUERADE", "-o", &active_if]); - command.stdout(Stdio::piped()); - command.stderr(Stdio::piped()); - let child = io_wrap!(command.spawn()); - let output = io_wrap!(child.wait_with_output()); - if output.status.success() { - Ok(()) - } else { - Err(NatError::CommandError) - } - } - pub fn enable(&self) -> Result<(), NatError> { - Self::forward(true)?; - Self::iptable()?; + linux::forward(true)?; + linux::iptable()?; Ok(()) } } #[cfg(target_os="macos")] impl Nat { - fn sysctl(enable: bool) -> Result<(), NatError> { - let mut command = Command::new("sysctl"); - let enable = if enable { 1 } else { 0 }; - command.args(&["-w", &format!("net.inet.ip.forwarding={enable}")]); - command.stdout(Stdio::piped()); - command.stderr(Stdio::piped()); - let mut child = io_wrap!(command.spawn()); - let exit_code = io_wrap!(child.wait()); - if exit_code.success() { - Ok(()) - } else { - Err(NatError::CommandError) - } - } - - fn pfctl() -> Result<(), NatError> { - let mut command = Command::new("pfctl"); - let child = io_wrap!(command.args(&["-f", "/etc/pf.anchors/bls-vm-nat", "-e" ]) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()); - let output = io_wrap!(child.wait_with_output()); - if output.status.success() { - return Ok(()); - } else { - let out_string = String::from_utf8(output.stderr).map_err(|_| NatError::Utf8CodeError)?; - if let Some(_) = out_string.find("pf already enabled") { - return Ok(()); - } - } - Err(NatError::CommandError) - } - - pub fn enable(&self) -> anyhow::Result<()> { - let mut pfctl = OpenOptions::new() - .write(true) - .create(true) - .open("/etc/pf.anchors/bls-vm-nat")?; - let cmd = format!("nat on en0 from {}:network to any -> (en0)\n", &self.tap_name); - pfctl.write_all(cmd.as_bytes())?; - Self::sysctl(true)?; - Self::pfctl()?; + macos::write_anchors(&self.tap_name)?; + macos::sysctl(true)?; + macos::pfctl()?; Ok(()) } } \ No newline at end of file From 7badb168165c1d1b3be3350e600db5481c39fba4 Mon Sep 17 00:00:00 2001 From: Joinhack Date: Mon, 29 Jul 2024 22:30:02 +0800 Subject: [PATCH 2/2] refactor: split the macos and linux. --- crates/wasi/src/nat/linux.rs | 105 ++++++++++++++++++++++++++++------- 1 file changed, 86 insertions(+), 19 deletions(-) diff --git a/crates/wasi/src/nat/linux.rs b/crates/wasi/src/nat/linux.rs index b2fd37e..ec9cc46 100644 --- a/crates/wasi/src/nat/linux.rs +++ b/crates/wasi/src/nat/linux.rs @@ -8,15 +8,15 @@ use std::mem; use super::NatError; - const ETHTOOL_FWVERS_LEN: usize = 32; const ETHTOOL_BUSINFO_LEN: usize = 32; const ETHTOOL_EROMVERS_LEN: usize = 32; const SIOCETHTOOL: usize = 0x8946; const ETHTOOL_GDRVINFO: usize = 0x00000003; const ETH_GSTRING_LEN: usize = 32; -const ETHTOOL_GSTRINGS: usize = 32; -const ETH_SS_STATS: usize = 32; +const ETHTOOL_GSTRINGS: usize = 0x0000001b; +const ETH_SS_STATS: usize = 1; +const ETHTOOL_GSTATS: usize = 0x0000001d; #[repr(C)] #[derive(Copy, Clone)] @@ -26,6 +26,13 @@ struct ethtool_gstrings { len: u32, } +#[repr(C)] +#[derive(Copy, Clone)] +struct ethtool_stats { + cmd: u32, + n_stats: u32, +} + #[repr(C)] #[derive(Copy, Clone)] struct ethtool_drvinfo { @@ -99,7 +106,64 @@ pub(crate)fn forward(enable: bool) -> Result<(), NatError> { Ok(()) } -fn eth_info(devn: &str) -> Result<(), NatError> { +/// get the interface's string sets of the ethtool +fn eth_stringset( + sock: i32, + ifreq: &mut MaybeUninit, + n_stats: u32 +) -> Result, NatError> { + let size: usize = mem::size_of::() + n_stats as usize* ETH_GSTRING_LEN; + let mut gstrings_data = vec![0u8; size] ; + + unsafe { + let gstr = &mut *(gstrings_data.as_mut_ptr() as *mut ethtool_gstrings); + gstr.cmd = ETHTOOL_GSTRINGS as _; + gstr.string_set = ETH_SS_STATS as _; + gstr.len = n_stats as _; + let ifreq = ifreq.assume_init_mut(); + ifreq.ifr_ifru.ifru_data = gstrings_data.as_mut_ptr() as _; + } + syscall!(libc::ioctl(sock, SIOCETHTOOL as _, ifreq.as_mut_ptr())); + let data = gstrings_data.as_ptr(); + let mut ret = Vec::new(); + for i in 0..n_stats { + let name = unsafe { + let ptr_from = data.add(mem::size_of::() + ETH_GSTRING_LEN*i as usize); + let name = std::slice::from_raw_parts(ptr_from, ETH_GSTRING_LEN); + if let Some(n) = name.iter().position(|i| *i == 0) { + std::str::from_utf8_unchecked(&name[0..n]) + } else { + //keep the same index with stats, always size equals n_stats + "" + } + }; + ret.push(name.into()); + } + Ok(ret) +} + +/// ethtool get stats of interface +fn eth_stats(sock: i32, ifreq: &mut MaybeUninit, n_stats: u32) -> Result, NatError> { + let msize: usize = mem::size_of::() + (n_stats as usize) * mem::size_of::(); + let mut stats_data = vec![0u8; msize] ; + unsafe { + let e_stats = &mut *(stats_data.as_mut_ptr() as *mut ethtool_stats); + e_stats.cmd = ETHTOOL_GSTATS as _; + e_stats.n_stats = n_stats; + let ifreq = ifreq.assume_init_mut(); + ifreq.ifr_ifru.ifru_data = stats_data.as_mut_ptr() as _; + } + syscall!(libc::ioctl(sock, SIOCETHTOOL as _, ifreq.as_mut_ptr())); + let data = stats_data.as_ptr(); + let ret = unsafe { + let d_ptr = data.add(mem::size_of::()); + std::slice::from_raw_parts(d_ptr as *const u64, n_stats as _) + }; + let ret = ret.to_vec(); + Ok(ret) +} + +fn eth_info(devn: &str) -> Result, NatError> { let sock: i32 = syscall!(libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0)) as _; let mut ifreq = MaybeUninit::::zeroed(); let mut drv_info = MaybeUninit::::zeroed(); @@ -116,20 +180,10 @@ fn eth_info(devn: &str) -> Result<(), NatError> { if n_stats <= 0 { return Err(NatError::NoStatError) } - let size: usize = mem::size_of::() + n_stats as usize* ETH_GSTRING_LEN; - let mut gstrings_data = vec![0u8; size] ; - - unsafe { - let gstr = &mut *(gstrings_data.as_mut_ptr() as *mut ethtool_gstrings); - gstr.cmd = ETHTOOL_GSTRINGS as _; - gstr.string_set = ETH_SS_STATS as _; - gstr.len = n_stats as _; - let ifreq = ifreq.assume_init_mut(); - ifreq.ifr_ifru.ifru_data = gstrings_data.as_mut_ptr() as _; - } - syscall!(libc::ioctl(sock, SIOCETHTOOL as _, ifreq.as_mut_ptr())); - let data = gstrings_data.as_ptr(); - Ok(()) + let sets: Vec = eth_stringset(sock, &mut ifreq, n_stats)?; + let stats: Vec = eth_stats(sock, &mut ifreq, n_stats)?; + let stats = sets.into_iter().zip(stats.into_iter()).collect::>(); + Ok(stats) } pub(crate)fn iptable() -> Result<(), NatError> { @@ -162,7 +216,20 @@ fn find_active_eth() -> Result { } let split = s.split("/"); if let Some(s) = split.last() { - return Ok(s.into()); + let stats = eth_info(s)?; + // find the active interface which have packages transport. + let rx = stats.iter().position(|(n, stat)| { + if n == "rx_packets" || *stat > 0 { + true + } else if n == "tx_packets" || *stat > 0 { + true + } else { + false + } + }); + if let Some(_) = rx { + return Ok(s.into()); + } } } }