diff --git a/Cargo.lock b/Cargo.lock index 62aaf8e..fd28509 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -144,11 +144,14 @@ version = "0.1.0" dependencies = [ "aya", "aya-obj", + "caps", "config", "core_affinity", + "enum_dispatch", "env_logger", "fork", "futures", + "io-uring", "itertools", "libc", "log", @@ -194,6 +197,16 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +[[package]] +name = "caps" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190baaad529bcfbde9e1a19022c42781bdb6ff9de25721abdb8fd98c0807730b" +dependencies = [ + "libc", + "thiserror 1.0.58", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -357,6 +370,18 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "env_filter" version = "0.1.3" @@ -566,6 +591,17 @@ dependencies = [ "hashbrown 0.15.2", ] +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "libc", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" diff --git a/Cargo.toml b/Cargo.toml index 74f0365..c6b81a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "berserker" version = "0.1.0" -edition = "2021" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -23,3 +23,6 @@ libc = "0.2.169" smoltcp = "0.12.0" aya = "0.13.1" aya-obj = "0.2.1" +caps = "0.5.5" +io-uring = "0.7.10" +enum_dispatch = "0.3.13" diff --git a/src/lib.rs b/src/lib.rs index 36cc7b7..596f66d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,13 @@ use core_affinity::CoreId; -use serde::Deserialize; -use std::{fmt::Display, net::Ipv4Addr}; +use serde::{Deserialize, Deserializer}; +use std::{collections::HashMap, fmt::Display, net::Ipv4Addr}; use syscalls::Sysno; pub mod worker; /// Main workload configuration, contains general bits for all types of /// workloads plus workload specific data. -#[derive(Debug, Copy, Clone, Deserialize)] +#[derive(Debug, Clone, Deserialize)] pub struct WorkloadConfig { /// An amount of time for workload payload to run before restarting. pub restart_interval: u64, @@ -55,9 +55,33 @@ fn default_syscalls_syscall_nr() -> u32 { Sysno::getpid as u32 } +fn default_iouring_iouring_nr() -> u8 { + io_uring::opcode::OpenAt::CODE +} + +fn deserialize_args<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let s = String::deserialize(deserializer)?; + let mut map = HashMap::new(); + for arg in s.split(',').filter(|x| !x.is_empty()) { + let parts = arg.split_once('='); + let Some((key, value)) = parts else { + return Err(serde::de::Error::custom( + "invalid syscall arguments format", + )); + }; + map.insert(key.to_string(), value.to_string()); + } + Ok(map) +} + /// Workload specific configuration, contains one enum value for each /// workload type. -#[derive(Debug, Copy, Clone, Deserialize)] +#[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "lowercase", tag = "type")] pub enum Workload { /// How to listen on ports. @@ -92,6 +116,33 @@ pub enum Workload { /// Which syscall to trigger #[serde(default = "default_syscalls_syscall_nr")] syscall_nr: u32, + + /// Arguments for syscall in format "arg1=value1,arg2=value2" + #[serde( + deserialize_with = "deserialize_args", + default = "HashMap::new" + )] + syscall_args: HashMap, + }, + + /// How to invoke syscalls + IOUring { + /// How often to invoke a io_uring events. + #[serde(default = "default_syscalls_arrival_rate")] + arrival_rate: f64, + + /// Number of io uring event to trigger + /// List of io_uring events can be found at https://github.com/tokio-rs/io-uring/blob/master/src/sys/sys_x86_64.rs + /// or at https://github.com/torvalds/linux/blob/b320789d6883cc00ac78ce83bccbfe7ed58afcf0/include/uapi/linux/io_uring.h + #[serde(default = "default_iouring_iouring_nr")] + iouring_nr: u8, + + /// Arguments for io_uring in format "arg1=value1,arg2=value2" + #[serde( + deserialize_with = "deserialize_args", + default = "HashMap::new" + )] + iouring_args: HashMap, }, /// How to open network connections @@ -186,11 +237,17 @@ pub enum Distribution { #[derive(Debug)] pub enum WorkerError { Internal, + InternalWithMessage(String), } impl Display for WorkerError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "worker error found") + match self { + WorkerError::Internal => write!(f, "worker error found"), + WorkerError::InternalWithMessage(msg) => { + write!(f, "worker error: {}", msg) + } + } } } @@ -356,12 +413,7 @@ mod tests { .. } = config; assert_eq!(restart_interval, 10); - if let Workload::Syscalls { - arrival_rate, - tight_loop, - syscall_nr, - } = workload - { + if let Workload::Syscalls { arrival_rate, .. } = workload { assert_eq!(arrival_rate, 10.0); } else { panic!("wrong workload type found"); diff --git a/src/main.rs b/src/main.rs index 7973ce1..02d5864 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,15 +19,15 @@ extern crate core_affinity; use config::Config; use core_affinity::CoreId; -use fork::{fork, Fork}; +use fork::{Fork, fork}; use itertools::iproduct; -use nix::sys::signal::{kill, Signal}; +use nix::sys::signal::{Signal, kill}; use nix::sys::wait::waitpid; use nix::unistd::Pid; use std::time::SystemTime; use std::{env, thread, time}; -use berserker::{worker::new_worker, WorkloadConfig}; +use berserker::{WorkloadConfig, worker::new_worker}; fn main() { let args: Vec = env::args().collect(); @@ -70,8 +70,13 @@ fn main() { let handles: Vec<_> = iproduct!(core_ids.into_iter(), 0..config.workers) .map(|(cpu, process)| { - let worker = - new_worker(config, cpu, process, &mut lower, &mut upper); + let worker = new_worker( + config.clone(), + cpu, + process, + &mut lower, + &mut upper, + ); match fork() { Ok(Fork::Parent(child)) => { @@ -102,17 +107,20 @@ fn main() { thread::scope(|s| { if config.duration != 0 { // Spin a watcher thread - s.spawn(move || loop { - thread::sleep(time::Duration::from_secs(1)); - let elapsed = duration_timer.elapsed().unwrap().as_secs(); - - if elapsed > config.duration { - for handle in processes.iter().flatten() { - info!("Terminating: {}", *handle); - let _ = kill(Pid::from_raw(*handle), Signal::SIGTERM); + s.spawn(move || { + loop { + thread::sleep(time::Duration::from_secs(1)); + let elapsed = duration_timer.elapsed().unwrap().as_secs(); + + if elapsed > config.duration { + for handle in processes.iter().flatten() { + info!("Terminating: {}", *handle); + let _ = + kill(Pid::from_raw(*handle), Signal::SIGTERM); + } + + break; } - - break; } }); } diff --git a/src/worker/bpf.rs b/src/worker/bpf.rs index 14f840d..87dd594 100644 --- a/src/worker/bpf.rs +++ b/src/worker/bpf.rs @@ -1,6 +1,6 @@ use std::{ cmp, - ffi::{c_char, CString}, + ffi::{CString, c_char}, fmt::Display, mem, slice, thread, }; @@ -17,7 +17,7 @@ use aya_obj::generated::{ use crate::{BaseConfig, Worker, WorkerError, Workload, WorkloadConfig}; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub struct BpfWorker { config: BaseConfig, workload: WorkloadConfig, diff --git a/src/worker/io_uring/mod.rs b/src/worker/io_uring/mod.rs new file mode 100644 index 0000000..6fe3fd2 --- /dev/null +++ b/src/worker/io_uring/mod.rs @@ -0,0 +1,169 @@ +mod openat; +mod openat2; +mod statx; +mod unlinkat; + +use std::{collections::HashMap, fmt::Display, str::FromStr, time::Instant}; + +use core_affinity::CoreId; +use enum_dispatch::enum_dispatch; +use log::{debug, info, trace}; +use rand::{Rng, thread_rng}; +use rand_distr::Exp; +use syscalls::Errno; + +use crate::{ + BaseConfig, Worker, WorkerError, Workload, WorkloadConfig, + worker::io_uring::{ + openat::OpenatIOUringCall, openat2::Openat2IOUringCall, + statx::StatxIOUringCall, unlinkat::UnlinkatIOUringCall, + }, +}; + +#[derive(Debug, Clone)] +pub struct IOUringWorker { + config: BaseConfig, + workload: WorkloadConfig, +} + +impl IOUringWorker { + pub fn new(workload: WorkloadConfig, cpu: CoreId, process: usize) -> Self { + IOUringWorker { + config: BaseConfig { cpu, process }, + workload, + } + } +} + +impl Worker for IOUringWorker { + fn run_payload(&self) -> Result<(), WorkerError> { + info!("{self}"); + + let mut counter = 0; + let mut start = Instant::now(); + + let Workload::IOUring { + arrival_rate, + iouring_nr, + iouring_args, + } = &self.workload.workload + else { + unreachable!() + }; + + let mut caller = new_iouring_generator(*iouring_nr, iouring_args)?; + if let Err(e) = caller.init() { + return Err(WorkerError::InternalWithMessage(format!( + "Error initializing iouring: {:?}", + e + ))); + }; + let mut ring = io_uring::IoUring::new(1).unwrap(); + + let exp = Exp::new(*arrival_rate).unwrap(); + let rng = thread_rng(); + let mut rng_iter = rng.sample_iter(exp); + + info!("Running iouring {iouring_nr}"); + + loop { + if start.elapsed().as_secs() > 10 { + info!( + "CPU {}, {}", + self.config.cpu.id, + counter / start.elapsed().as_secs() + ); + start = Instant::now(); + counter = 0; + } + + counter += 1; + // Do the iouring directly, without spawning a thread (it would + // introduce too much overhead for a quick iouring). + match caller.submit(&mut ring) { + Ok(_) => trace!( + "{}-{}: Success", + self.config.cpu.id, self.config.process + ), + Err(e) => debug!( + "{}-{}: Error: {:?}", + self.config.cpu.id, self.config.process, e + ), + } + + // Otherwise calculate waiting time + let interval: f64 = rng_iter.next().unwrap(); + trace!( + "{}-{}: Interval {}, rounded {}", + self.config.cpu.id, + self.config.process, + interval, + (interval * 1000000.0).round() as u64 + ); + std::thread::sleep(std::time::Duration::from_nanos( + (interval * 1000000.0).round() as u64, + )); + trace!("{}-{}: Continue", self.config.cpu.id, self.config.process); + } + } +} + +impl Display for IOUringWorker { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.config) + } +} + +#[allow(clippy::enum_variant_names)] +#[enum_dispatch] +#[derive(Debug)] +enum IOUringCallerEnum { + OpenatIOUringCall, + Openat2IOUringCall, + StatxIOUringCall, + UnlinkatIOUringCall, +} + +#[enum_dispatch(IOUringCallerEnum)] +trait IOUringCaller { + fn init(&mut self) -> std::io::Result { + Ok(0) + } + fn submit(&self, ring: &mut io_uring::IoUring) -> Result; +} + +fn new_iouring_generator( + iouring_nr: u8, + iouring_args: &HashMap, +) -> Result { + use io_uring::opcode::*; + match iouring_nr { + OpenAt::CODE => Ok(IOUringCallerEnum::OpenatIOUringCall( + OpenatIOUringCall::new(iouring_args), + )), + OpenAt2::CODE => Ok(IOUringCallerEnum::Openat2IOUringCall( + Openat2IOUringCall::new(iouring_args), + )), + Statx::CODE => Ok(IOUringCallerEnum::StatxIOUringCall( + StatxIOUringCall::new(iouring_args), + )), + UnlinkAt::CODE => Ok(IOUringCallerEnum::UnlinkatIOUringCall( + UnlinkatIOUringCall::new(iouring_args), + )), + _ => Err(WorkerError::InternalWithMessage( + "Unsupported iouring number".to_string(), + )), + } +} + +fn get_argument( + args: &HashMap, + name: &str, + default: T, +) -> T { + if let Some(arg) = args.get(name) { + arg.parse().unwrap_or(default) + } else { + default + } +} diff --git a/src/worker/io_uring/openat.rs b/src/worker/io_uring/openat.rs new file mode 100644 index 0000000..159c1a0 --- /dev/null +++ b/src/worker/io_uring/openat.rs @@ -0,0 +1,56 @@ +use std::collections::HashMap; +use std::ffi::CString; +use std::fs::File; +use std::os::fd::FromRawFd; + +use io_uring::opcode::OpenAt; +use io_uring::squeue::Entry; +use io_uring::{IoUring, types}; +use syscalls::Errno; + +use crate::worker::io_uring::IOUringCaller; +use crate::worker::io_uring::get_argument; + +#[derive(Debug)] +#[allow(dead_code)] +pub(super) struct OpenatIOUringCall { + openat: Entry, + + pathname: CString, // used a raw pointer from string +} + +impl OpenatIOUringCall { + pub fn new(openat_args: &HashMap) -> Self { + let pathname = get_argument( + openat_args, + "pathname", + CString::new("/tmp").unwrap(), + ); + let flags = get_argument(openat_args, "flags", 0); + let mode = get_argument(openat_args, "mode", 0); + let openat = OpenAt::new(types::Fd(-1), pathname.as_ptr()) + .flags(flags) + .mode(mode) + .build(); + Self { openat, pathname } + } +} + +impl IOUringCaller for OpenatIOUringCall { + fn submit(&self, ring: &mut IoUring) -> Result { + unsafe { + ring.submission() + .push(&self.openat) + .expect("submission queue is full"); + } + ring.submit_and_wait(1).expect("submission failed"); + + let cqe = ring.completion().next().expect("completion queue is empty"); + if cqe.result() > -1 { + // Close file descriptor + unsafe { File::from_raw_fd(cqe.result()) }; + return Ok(cqe.result() as usize); + } + Err(Errno::new(-cqe.result())) + } +} diff --git a/src/worker/io_uring/openat2.rs b/src/worker/io_uring/openat2.rs new file mode 100644 index 0000000..bafc219 --- /dev/null +++ b/src/worker/io_uring/openat2.rs @@ -0,0 +1,68 @@ +use std::collections::HashMap; +use std::ffi::CString; +use std::fs::File; +use std::os::fd::FromRawFd; + +use io_uring::opcode::OpenAt2; +use io_uring::squeue::Entry; +use io_uring::{IoUring, types}; +use syscalls::Errno; + +use crate::worker::io_uring::IOUringCaller; +use crate::worker::io_uring::get_argument; + +#[derive(Debug)] +#[allow(dead_code)] +pub(super) struct Openat2IOUringCall { + openat: Entry, + + pathname: CString, // used a raw pointer from string + openhow: Box, +} + +impl Openat2IOUringCall { + pub fn new(openat2_args: &HashMap) -> Self { + let pathname = get_argument( + openat2_args, + "pathname", + CString::new("/tmp").unwrap(), + ); + let flags = get_argument(openat2_args, "flags", 0); + let mode = get_argument(openat2_args, "mode", 0); + let resolve = get_argument(openat2_args, "resolve", 0); + let openhow = Box::new( + types::OpenHow::new() + .flags(flags) + .mode(mode) + .resolve(resolve), + ); + + let openat = + OpenAt2::new(types::Fd(-1), pathname.as_ptr(), openhow.as_ref()) + .build(); + Self { + openat, + pathname, + openhow, + } + } +} + +impl IOUringCaller for Openat2IOUringCall { + fn submit(&self, ring: &mut IoUring) -> Result { + unsafe { + ring.submission() + .push(&self.openat) + .expect("submission queue is full"); + } + ring.submit_and_wait(1).expect("submission failed"); + + let cqe = ring.completion().next().expect("completion queue is empty"); + if cqe.result() > -1 { + // Close file descriptor + unsafe { File::from_raw_fd(cqe.result()) }; + return Ok(cqe.result() as usize); + } + Err(Errno::new(-cqe.result())) + } +} diff --git a/src/worker/io_uring/statx.rs b/src/worker/io_uring/statx.rs new file mode 100644 index 0000000..150a735 --- /dev/null +++ b/src/worker/io_uring/statx.rs @@ -0,0 +1,66 @@ +use std::collections::HashMap; +use std::ffi::CString; + +use io_uring::opcode::Statx; +use io_uring::squeue::Entry; +use io_uring::{IoUring, types}; +use syscalls::Errno; + +use crate::worker::io_uring::IOUringCaller; +use crate::worker::io_uring::get_argument; + +#[derive(Debug)] +#[allow(dead_code)] +pub(super) struct StatxIOUringCall { + statx: Entry, + + pathname: CString, // used a raw pointer from string + statx_struct: Box, // used as a mutable raw pointer +} + +impl StatxIOUringCall { + pub fn new(openat_args: &HashMap) -> Self { + let pathname = get_argument( + openat_args, + "pathname", + CString::new("/tmp").unwrap(), + ); + let flags = get_argument(openat_args, "flags", 0); + let mask = get_argument(openat_args, "mask", 0); + let mut statx_struct: Box = + Box::new(unsafe { std::mem::zeroed() }); + + let statx = Statx::new( + types::Fd(-1), + pathname.as_ptr(), + statx_struct.as_mut() as *mut libc::statx as *mut _, + ) + .flags(flags) + .mask(mask) + .build(); + Self { + statx, + pathname, + statx_struct, + } + } +} + +impl IOUringCaller for StatxIOUringCall { + fn submit(&self, ring: &mut IoUring) -> Result { + unsafe { + ring.submission() + .push(&self.statx) + .expect("submission queue is full"); + } + ring.submit_and_wait(1).expect("submission failed"); + + let cqe = ring.completion().next().expect("completion queue is empty"); + + if cqe.result() == 0 { + Ok(0) + } else { + Err(Errno::new(-cqe.result())) + } + } +} diff --git a/src/worker/io_uring/unlinkat.rs b/src/worker/io_uring/unlinkat.rs new file mode 100644 index 0000000..0179aa5 --- /dev/null +++ b/src/worker/io_uring/unlinkat.rs @@ -0,0 +1,53 @@ +use std::collections::HashMap; +use std::ffi::CString; + +use io_uring::opcode::UnlinkAt; +use io_uring::squeue::Entry; +use io_uring::{IoUring, types}; +use syscalls::Errno; + +use crate::worker::io_uring::IOUringCaller; +use crate::worker::io_uring::get_argument; + +#[derive(Debug)] +#[allow(dead_code)] +pub(super) struct UnlinkatIOUringCall { + unlinkat: Entry, + + pathname: CString, // used a raw pointer from string +} + +impl UnlinkatIOUringCall { + pub fn new(unlinkat_args: &HashMap) -> Self { + let pathname = get_argument( + unlinkat_args, + "pathname", + CString::new("/not_existing_file").unwrap(), + ); + let flags = get_argument(unlinkat_args, "flags", 0); + + let unlinkat = UnlinkAt::new(types::Fd(-1), pathname.as_ptr()) + .flags(flags) + .build(); + Self { unlinkat, pathname } + } +} + +impl IOUringCaller for UnlinkatIOUringCall { + fn submit(&self, ring: &mut IoUring) -> Result { + unsafe { + ring.submission() + .push(&self.unlinkat) + .expect("submission queue is full"); + } + ring.submit_and_wait(1).expect("submission failed"); + + let cqe = ring.completion().next().expect("completion queue is empty"); + + if cqe.result() == 0 { + Ok(0) + } else { + Err(Errno::new(-cqe.result())) + } + } +} diff --git a/src/worker/mod.rs b/src/worker/mod.rs index 6723af5..7b0c3ef 100644 --- a/src/worker/mod.rs +++ b/src/worker/mod.rs @@ -1,8 +1,11 @@ use core_affinity::CoreId; -use rand::{thread_rng, Rng}; +use rand::{Rng, thread_rng}; use rand_distr::{Uniform, Zipf}; -use crate::{Distribution, Worker, Workload, WorkloadConfig}; +use crate::{ + Distribution, Worker, Workload, WorkloadConfig, + worker::io_uring::IOUringWorker, +}; use self::{ bpf::BpfWorker, endpoints::EndpointWorker, network::NetworkWorker, @@ -11,6 +14,7 @@ use self::{ pub mod bpf; pub mod endpoints; +pub mod io_uring; pub mod network; pub mod processes; pub mod syscalls; @@ -61,5 +65,8 @@ pub fn new_worker( Workload::Bpf { .. } => { Box::new(BpfWorker::new(workload, cpu, process)) } + Workload::IOUring { .. } => { + Box::new(IOUringWorker::new(workload, cpu, process)) + } } } diff --git a/src/worker/network.rs b/src/worker/network.rs index 8781ced..7f2b0af 100644 --- a/src/worker/network.rs +++ b/src/worker/network.rs @@ -1,6 +1,6 @@ use core_affinity::CoreId; use log::{debug, info, trace}; -use rand::{thread_rng, Rng}; +use rand::{Rng, thread_rng}; use rand_distr::Exp; use std::collections::HashMap; use std::os::unix::io::AsRawFd; @@ -8,7 +8,7 @@ use std::str; use std::time::{SystemTime, UNIX_EPOCH}; use std::{ fmt::Display, - io::{prelude::*, BufReader}, + io::{BufReader, prelude::*}, net::TcpListener, thread, }; @@ -17,10 +17,10 @@ use crate::{BaseConfig, Worker, WorkerError, Workload, WorkloadConfig}; use smoltcp::iface::{Config, Interface, SocketSet}; use smoltcp::phy::{ - wait as phy_wait, Device, FaultInjector, Medium, Tracer, TunTapInterface, + Device, FaultInjector, Medium, Tracer, TunTapInterface, wait as phy_wait, }; -use smoltcp::socket::tcp; use smoltcp::socket::AnySocket; +use smoltcp::socket::tcp; use smoltcp::time::Instant; use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address}; @@ -61,31 +61,33 @@ impl NetworkWorker { // through streams and replies. This way the connections will have // high latency, but for the purpose of networking workload it // doesn't matter. - thread::spawn(move || loop { - let mut buf_reader = BufReader::new(&stream); - let mut buffer = String::new(); - - match buf_reader.read_line(&mut buffer) { - Ok(0) => { - // EOF, exit - break; - } - Ok(_n) => { - trace!("Received {:?}", buffer); - - let response = "hello\n"; - match stream.write_all(response.as_bytes()) { - Ok(_) => { - // Response is sent, handle the next one - } - Err(e) => { - trace!("ERROR: sending response, {}", e); - break; + thread::spawn(move || { + loop { + let mut buf_reader = BufReader::new(&stream); + let mut buffer = String::new(); + + match buf_reader.read_line(&mut buffer) { + Ok(0) => { + // EOF, exit + break; + } + Ok(_n) => { + trace!("Received {:?}", buffer); + + let response = "hello\n"; + match stream.write_all(response.as_bytes()) { + Ok(_) => { + // Response is sent, handle the next one + } + Err(e) => { + trace!("ERROR: sending response, {}", e); + break; + } } } - } - Err(e) => { - trace!("ERROR: reading a line, {}", e) + Err(e) => { + trace!("ERROR: reading a line, {}", e) + } } } }); diff --git a/src/worker/processes.rs b/src/worker/processes.rs index 3028e87..70e8cde 100644 --- a/src/worker/processes.rs +++ b/src/worker/processes.rs @@ -1,15 +1,15 @@ use std::{fmt::Display, process::Command, thread, time}; use core_affinity::CoreId; -use fork::{fork, Fork}; +use fork::{Fork, fork}; use log::{info, warn}; use nix::{sys::wait::waitpid, unistd::Pid}; -use rand::{distributions::Alphanumeric, thread_rng, Rng}; +use rand::{Rng, distributions::Alphanumeric, thread_rng}; use rand_distr::Exp; use crate::{BaseConfig, Worker, WorkerError, Workload, WorkloadConfig}; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub struct ProcessesWorker { config: BaseConfig, workload: WorkloadConfig, @@ -77,31 +77,37 @@ impl Worker for ProcessesWorker { unreachable!() }; - loop { - let lifetime: f64 = - thread_rng().sample(Exp::new(departure_rate).unwrap()); + thread::scope(|s| { + loop { + let lifetime: f64 = + thread_rng().sample(Exp::new(departure_rate).unwrap()); - let worker = *self; - thread::spawn(move || { - worker.spawn_process((lifetime * 1000.0).round() as u64) - }); + let worker = self; - let interval: f64 = - thread_rng().sample(Exp::new(arrival_rate).unwrap()); - info!( - "{}-{}: Interval {}, rounded {}, lifetime {}, rounded {}", - self.config.cpu.id, - self.config.process, - interval, - (interval * 1000.0).round() as u64, - lifetime, - (lifetime * 1000.0).round() as u64 - ); - thread::sleep(time::Duration::from_millis( - (interval * 1000.0).round() as u64, - )); - info!("{}-{}: Continue", self.config.cpu.id, self.config.process); - } + s.spawn(move || { + worker.spawn_process((lifetime * 1000.0).round() as u64) + }); + + let interval: f64 = + thread_rng().sample(Exp::new(arrival_rate).unwrap()); + info!( + "{}-{}: Interval {}, rounded {}, lifetime {}, rounded {}", + self.config.cpu.id, + self.config.process, + interval, + (interval * 1000.0).round() as u64, + lifetime, + (lifetime * 1000.0).round() as u64 + ); + thread::sleep(time::Duration::from_millis( + (interval * 1000.0).round() as u64, + )); + info!( + "{}-{}: Continue", + self.config.cpu.id, self.config.process + ); + } + }) } } diff --git a/src/worker/syscalls.rs b/src/worker/syscalls.rs deleted file mode 100644 index 022d7a7..0000000 --- a/src/worker/syscalls.rs +++ /dev/null @@ -1,113 +0,0 @@ -use std::time::Instant; -use std::{fmt::Display, thread, time}; - -use core_affinity::CoreId; -use log::{info, trace}; -use rand::{thread_rng, Rng}; -use rand_distr::Exp; -use syscalls::{syscall, Sysno}; - -use crate::{BaseConfig, Worker, WorkerError, Workload, WorkloadConfig}; - -#[derive(Debug, Copy, Clone)] -pub struct SyscallsWorker { - config: BaseConfig, - workload: WorkloadConfig, -} - -impl SyscallsWorker { - pub fn new(workload: WorkloadConfig, cpu: CoreId, process: usize) -> Self { - SyscallsWorker { - config: BaseConfig { cpu, process }, - workload, - } - } - - fn do_syscall(&self, syscall: Sysno) { - #[cfg(debug_assertions)] - { - match unsafe { syscall!(syscall) } { - Ok(_) => (), - Err(err) => { - info!("Syscall failed: {}", err); - } - } - } - #[cfg(not(debug_assertions))] - { - unsafe { - // Some syscalls are expected to fail, ignore the result - let _ = syscall!(syscall); - } - } - } -} - -impl Worker for SyscallsWorker { - fn run_payload(&self) -> Result<(), WorkerError> { - info!("{self}"); - - let mut counter = 0; - let mut start = Instant::now(); - - let Workload::Syscalls { - arrival_rate, - tight_loop, - syscall_nr, - } = self.workload.workload - else { - unreachable!() - }; - - let exp = Exp::new(arrival_rate).unwrap(); - let rng = thread_rng(); - let mut rng_iter = rng.sample_iter(exp); - - let syscall = Sysno::from(syscall_nr); - info!("Running syscall {syscall}"); - - loop { - let worker = *self; - - if start.elapsed().as_secs() > 10 { - info!( - "CPU {}, {}", - self.config.cpu.id, - counter / start.elapsed().as_secs() - ); - start = Instant::now(); - counter = 0; - } - - counter += 1; - // Do the syscall directly, without spawning a thread (it would - // introduce too much overhead for a quick syscall). - worker.do_syscall(syscall); - - // If running in a tight loop, go to the next iteration - if tight_loop { - continue; - } - - // Otherwise calculate waiting time - let interval: f64 = rng_iter.next().unwrap(); - trace!( - "{}-{}: Interval {}, rounded {}", - self.config.cpu.id, - self.config.process, - interval, - (interval * 1000000.0).round() as u64 - ); - thread::sleep(time::Duration::from_nanos( - (interval * 1000000.0).round() as u64, - )); - trace!("{}-{}: Continue", self.config.cpu.id, self.config.process); - } - } -} - -impl Display for SyscallsWorker { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.config) - } -} diff --git a/src/worker/syscalls/accept.rs b/src/worker/syscalls/accept.rs new file mode 100644 index 0000000..5f54bd8 --- /dev/null +++ b/src/worker/syscalls/accept.rs @@ -0,0 +1,39 @@ +use std::collections::HashMap; +use syscalls::{Errno, Sysno, syscall}; + +use super::SysCaller; +use crate::worker::syscalls::listen::ListenCall; + +#[derive(Debug)] +pub struct AcceptCall { + pub accept_nr: Sysno, + pub listen_call: ListenCall, + pub sockfd: usize, +} + +impl AcceptCall { + pub fn new( + accept_args: &HashMap, + accept_nr: Sysno, + ) -> Self { + let listen_call = ListenCall::new(accept_args); + let sockfd = 0; + + Self { + accept_nr, + listen_call, + sockfd, + } + } +} + +impl SysCaller for AcceptCall { + fn init(&mut self) -> Result { + self.sockfd = self.listen_call.init()?; + self.listen_call.call()?; + Ok(self.sockfd) + } + fn call(&self) -> Result { + unsafe { syscall!(self.accept_nr, self.sockfd, 0, 0, 0) } + } +} diff --git a/src/worker/syscalls/capset.rs b/src/worker/syscalls/capset.rs new file mode 100644 index 0000000..a482169 --- /dev/null +++ b/src/worker/syscalls/capset.rs @@ -0,0 +1,27 @@ +use super::SysCaller; +use std::collections::HashMap; +use syscalls::Errno; + +#[derive(Debug)] +pub struct CapsetCall {} + +impl CapsetCall { + pub fn new(_capset_args: &HashMap) -> Self { + Self {} + } +} + +impl SysCaller for CapsetCall { + fn call(&self) -> Result { + match caps::set( + None, + caps::CapSet::Effective, + &[caps::Capability::CAP_SYS_ADMIN].into(), + ) { + Ok(_) => Ok(0), + Err(_) => Err(Errno::new( + std::io::Error::last_os_error().raw_os_error().unwrap(), + )), + } + } +} diff --git a/src/worker/syscalls/chmod.rs b/src/worker/syscalls/chmod.rs new file mode 100644 index 0000000..577debb --- /dev/null +++ b/src/worker/syscalls/chmod.rs @@ -0,0 +1,34 @@ +use libc::{S_IRWXG, S_IRWXO, S_IRWXU, S_ISVTX}; +use std::collections::HashMap; +use std::ffi::CString; + +use super::{SysCaller, get_argument}; +use syscalls::{self, Sysno}; + +#[derive(Debug)] +pub struct ChmodCall { + pub pathname: CString, + pub mode: usize, +} + +impl ChmodCall { + pub fn new(chmod_args: &HashMap) -> Self { + let pathname = + get_argument(chmod_args, "pathname", CString::new("/tmp").unwrap()); + let mode = get_argument( + chmod_args, + "mode", + (S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO) as usize, + ); + + Self { pathname, mode } + } +} + +impl SysCaller for ChmodCall { + fn call(&self) -> Result { + unsafe { + syscalls::syscall!(Sysno::chmod, self.pathname.as_ptr(), self.mode) + } + } +} diff --git a/src/worker/syscalls/chown.rs b/src/worker/syscalls/chown.rs new file mode 100644 index 0000000..1e4473b --- /dev/null +++ b/src/worker/syscalls/chown.rs @@ -0,0 +1,40 @@ +use std::collections::HashMap; +use std::ffi::CString; + +use super::{SysCaller, get_argument}; +use syscalls::{self, Sysno, syscall}; + +#[derive(Debug)] +pub struct ChownCall { + pub pathname: CString, + pub owner: usize, + pub group: usize, +} + +impl ChownCall { + pub fn new(chown_args: &HashMap) -> Self { + let pathname = + get_argument(chown_args, "pathname", CString::new("/tmp").unwrap()); + let owner = get_argument(chown_args, "owner", 0); + let group = get_argument(chown_args, "group", 0); + + Self { + pathname, + owner, + group, + } + } +} + +impl SysCaller for ChownCall { + fn call(&self) -> Result { + unsafe { + syscall!( + Sysno::chown, + self.pathname.as_ptr(), + self.owner, + self.group + ) + } + } +} diff --git a/src/worker/syscalls/connect.rs b/src/worker/syscalls/connect.rs new file mode 100644 index 0000000..60fa3c2 --- /dev/null +++ b/src/worker/syscalls/connect.rs @@ -0,0 +1,71 @@ +use std::collections::HashMap; +use std::mem; +use std::{fs::File, os::fd::FromRawFd}; + +use libc::{AF_INET, htons}; +use syscalls::{Errno, Sysno, syscall}; + +use super::SysCaller; +use crate::worker::syscalls::socket::SocketCall; + +#[derive(Debug)] +pub struct ConnectCall { + pub socket_call: SocketCall, + pub sockfd: usize, + pub serv_addr: libc::sockaddr_in, + pub addrlen: usize, +} + +impl ConnectCall { + pub fn new(connect_args: &HashMap) -> Self { + let socket_call = SocketCall::new(connect_args); + let sockfd = 0; + let serv_addr = libc::sockaddr_in { + sin_family: AF_INET as u16, + sin_port: htons(63333), + sin_addr: libc::in_addr { + s_addr: u32::from_le_bytes([127, 0, 0, 1]), + }, + sin_zero: Default::default(), + }; + let addrlen = mem::size_of::(); + + Self { + socket_call, + sockfd, + serv_addr, + addrlen, + } + } +} + +impl Drop for ConnectCall { + fn drop(&mut self) { + unsafe { File::from_raw_fd(self.sockfd as i32) }; + } +} + +impl SysCaller for ConnectCall { + fn init(&mut self) -> Result { + self.sockfd = unsafe { + syscall!( + Sysno::socket, + self.socket_call.domain, + self.socket_call.stype | libc::SOCK_NONBLOCK as usize, + self.socket_call.protocol + )? + }; + Ok(self.sockfd) + } + + fn call(&self) -> Result { + unsafe { + syscall!( + Sysno::connect, + self.sockfd, + &self.serv_addr as *const libc::sockaddr_in as usize, + self.addrlen + ) + } + } +} diff --git a/src/worker/syscalls/dummy.rs b/src/worker/syscalls/dummy.rs new file mode 100644 index 0000000..52f377a --- /dev/null +++ b/src/worker/syscalls/dummy.rs @@ -0,0 +1,21 @@ +use std::collections::HashMap; +use syscalls::{Errno, Sysno, syscall}; + +use super::SysCaller; + +#[derive(Debug)] +pub struct DummyCall { + pub syscall: Sysno, +} + +impl DummyCall { + pub fn new(_dummy_args: &HashMap, syscall: Sysno) -> Self { + Self { syscall } + } +} + +impl SysCaller for DummyCall { + fn call(&self) -> Result { + unsafe { syscall!(self.syscall) } + } +} diff --git a/src/worker/syscalls/ioctl.rs b/src/worker/syscalls/ioctl.rs new file mode 100644 index 0000000..5da5568 --- /dev/null +++ b/src/worker/syscalls/ioctl.rs @@ -0,0 +1,43 @@ +use super::SysCaller; +use std::collections::HashMap; +use std::fs::File; +use std::os::fd::IntoRawFd; +use std::os::unix::prelude::FromRawFd; +use syscalls::syscall; +use syscalls::{Errno, Sysno}; + +#[derive(Debug)] +pub struct IoctlCall { + pub fd: usize, + pub op: usize, + pub argp: usize, +} + +impl IoctlCall { + pub fn new(_: &HashMap) -> Self { + let fd = 0; // Will be initialized in init() + let op = 0; // Default value, can be overridden if needed + let argp = 0; // Default value, can be overridden if needed + + Self { fd, op, argp } + } +} + +impl Drop for IoctlCall { + fn drop(&mut self) { + unsafe { File::from_raw_fd(self.fd as i32) }; + } +} + +impl SysCaller for IoctlCall { + fn init(&mut self) -> Result { + self.fd = match File::open("/dev/null") { + Ok(f) => f.into_raw_fd() as usize, + Err(e) => return Err(Errno::new(e.raw_os_error().unwrap())), + }; + Ok(self.fd) + } + fn call(&self) -> Result { + unsafe { syscall!(Sysno::ioctl, self.fd, self.op, self.argp) } + } +} diff --git a/src/worker/syscalls/listen.rs b/src/worker/syscalls/listen.rs new file mode 100644 index 0000000..ea8c0b1 --- /dev/null +++ b/src/worker/syscalls/listen.rs @@ -0,0 +1,49 @@ +use std::collections::HashMap; +use std::{fs::File, os::fd::FromRawFd}; + +use syscalls::{Errno, Sysno, syscall}; + +use super::SysCaller; +use crate::worker::syscalls::socket::SocketCall; + +#[derive(Debug)] +pub struct ListenCall { + pub socket_call: SocketCall, + pub sockfd: usize, +} + +impl ListenCall { + pub fn new(listen_args: &HashMap) -> Self { + let socket_call = SocketCall::new(listen_args); + let sockfd = 0; + + Self { + socket_call, + sockfd, + } + } +} + +impl Drop for ListenCall { + fn drop(&mut self) { + unsafe { File::from_raw_fd(self.sockfd as i32) }; + } +} + +impl SysCaller for ListenCall { + fn init(&mut self) -> Result { + // Create socket directly instead of calling socket_call.call() + self.sockfd = unsafe { + syscall!( + Sysno::socket, + self.socket_call.domain, + self.socket_call.stype | libc::SOCK_NONBLOCK as usize, + self.socket_call.protocol + )? + }; + Ok(self.sockfd) + } + fn call(&self) -> Result { + unsafe { syscall!(Sysno::listen, self.sockfd, 10) } + } +} diff --git a/src/worker/syscalls/mmap.rs b/src/worker/syscalls/mmap.rs new file mode 100644 index 0000000..ba6fc2d --- /dev/null +++ b/src/worker/syscalls/mmap.rs @@ -0,0 +1,75 @@ +use libc::{MAP_ANONYMOUS, MAP_PRIVATE, PROT_EXEC, PROT_READ, PROT_WRITE}; +use std::collections::HashMap; +use std::{fs::File, os::fd::FromRawFd}; + +use super::{SysCaller, get_argument}; +use syscalls::{Errno, Sysno, syscall}; + +#[derive(Debug)] +pub struct MmapCall { + pub address: usize, + pub length: usize, + pub prot: usize, + pub flags: usize, + pub fd: usize, + pub offset: usize, +} + +impl MmapCall { + pub fn new(mmap_args: &HashMap) -> Self { + let address = 0; + let length = get_argument(mmap_args, "length", 8); + let prot = get_argument( + mmap_args, + "prot", + (PROT_READ | PROT_WRITE | PROT_EXEC) as usize, + ); + let flags = get_argument( + mmap_args, + "flags", + (MAP_PRIVATE | MAP_ANONYMOUS) as usize, + ); + let fd = get_argument(mmap_args, "fd", usize::MAX); // -1 + let offset = get_argument(mmap_args, "offset", 0); + + Self { + address, + length, + prot, + flags, + fd, + offset, + } + } +} + +impl Drop for MmapCall { + fn drop(&mut self) { + unsafe { + File::from_raw_fd(self.fd as i32); + } + } +} + +impl SysCaller for MmapCall { + fn call(&self) -> Result { + let res = unsafe { + syscall!( + Sysno::mmap, + self.address, + self.length, + self.prot, + self.flags, + self.fd, + self.offset + ) + }; + + if let Ok(addr) = res { + // Unmap memory + unsafe { syscall!(Sysno::munmap, addr, self.length)? }; + } + + res + } +} diff --git a/src/worker/syscalls/mod.rs b/src/worker/syscalls/mod.rs new file mode 100644 index 0000000..8d398bf --- /dev/null +++ b/src/worker/syscalls/mod.rs @@ -0,0 +1,231 @@ +mod accept; +mod capset; +mod chmod; +mod chown; +mod connect; +mod dummy; +mod ioctl; +mod listen; +mod mmap; +mod mount; +mod open; +mod openat; +mod prctl; +mod setresuid; +mod setreuid; +mod setuid; +mod socket; +mod unlink; +mod unshare; + +use std::collections::HashMap; +use std::str::FromStr; +use std::time::Instant; +use std::{fmt::Display, thread, time}; + +use core_affinity::CoreId; +use enum_dispatch::enum_dispatch; +use log::{debug, error, info, trace}; +use rand::{Rng, thread_rng}; +use rand_distr::Exp; +use syscalls::{Errno, Sysno}; + +use crate::worker::syscalls::accept::AcceptCall; +use crate::worker::syscalls::capset::CapsetCall; +use crate::worker::syscalls::chmod::ChmodCall; +use crate::worker::syscalls::chown::ChownCall; +use crate::worker::syscalls::connect::ConnectCall; +use crate::worker::syscalls::dummy::DummyCall; +use crate::worker::syscalls::ioctl::IoctlCall; +use crate::worker::syscalls::listen::ListenCall; +use crate::worker::syscalls::mmap::MmapCall; +use crate::worker::syscalls::mount::MountCall; +use crate::worker::syscalls::open::OpenCall; +use crate::worker::syscalls::openat::OpenatCall; +use crate::worker::syscalls::prctl::PrctlCall; +use crate::worker::syscalls::setresuid::SetresuidCall; +use crate::worker::syscalls::setreuid::SetreuidCall; +use crate::worker::syscalls::setuid::SetuidCall; +use crate::worker::syscalls::socket::SocketCall; +use crate::worker::syscalls::unlink::UnlinkCall; +use crate::worker::syscalls::unshare::UnshareCall; +use crate::{BaseConfig, Worker, WorkerError, Workload, WorkloadConfig}; + +#[derive(Debug, Clone)] +pub struct SyscallsWorker { + config: BaseConfig, + workload: WorkloadConfig, +} + +impl SyscallsWorker { + pub fn new(workload: WorkloadConfig, cpu: CoreId, process: usize) -> Self { + SyscallsWorker { + config: BaseConfig { cpu, process }, + workload, + } + } +} + +impl Worker for SyscallsWorker { + fn run_payload(&self) -> Result<(), WorkerError> { + info!("{self}"); + + let mut counter = 0; + let mut start = Instant::now(); + + let Workload::Syscalls { + arrival_rate, + tight_loop, + syscall_nr, + syscall_args, + } = &self.workload.workload + else { + unreachable!() + }; + + let mut caller = + SysCallerEnum::new(Sysno::from(*syscall_nr), syscall_args); + if let Err(e) = caller.init() { + error!("Error initializing syscall: {:?}", e); + return Err(WorkerError::Internal); + }; + + let exp = Exp::new(*arrival_rate).unwrap(); + let rng = thread_rng(); + let mut rng_iter = rng.sample_iter(exp); + + let syscall = Sysno::from(*syscall_nr); + info!("Running syscall {syscall}"); + + loop { + if start.elapsed().as_secs() > 10 { + info!( + "CPU {}, {}", + self.config.cpu.id, + counter / start.elapsed().as_secs() + ); + start = Instant::now(); + counter = 0; + } + + counter += 1; + // Do the syscall directly, without spawning a thread (it would + // introduce too much overhead for a quick syscall). + match caller.call() { + Ok(_) => trace!( + "{}-{}: Success", + self.config.cpu.id, self.config.process + ), + Err(e) => debug!( + "{}-{}: Error: {:?}", + self.config.cpu.id, self.config.process, e + ), + } + // If running in a tight loop, go to the next iteration + if *tight_loop { + continue; + } + + // Otherwise calculate waiting time + let interval: f64 = rng_iter.next().unwrap(); + trace!( + "{}-{}: Interval {}, rounded {}", + self.config.cpu.id, + self.config.process, + interval, + (interval * 1000000.0).round() as u64 + ); + thread::sleep(time::Duration::from_nanos( + (interval * 1000000.0).round() as u64, + )); + trace!("{}-{}: Continue", self.config.cpu.id, self.config.process); + } + } +} + +impl Display for SyscallsWorker { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.config) + } +} + +#[allow(clippy::enum_variant_names)] +#[enum_dispatch] +#[derive(Debug)] +enum SysCallerEnum { + DummyCall, + OpenCall, + OpenatCall, + SocketCall, + ConnectCall, + ListenCall, + AcceptCall, + SetuidCall, + SetreuidCall, + SetresuidCall, + MmapCall, + MountCall, + UnlinkCall, + UnshareCall, + ChownCall, + ChmodCall, + PrctlCall, + IoctlCall, + CapsetCall, +} + +#[enum_dispatch(SysCallerEnum)] +trait SysCaller { + fn init(&mut self) -> Result { + Ok(0) + } + fn call(&self) -> Result; +} + +impl SysCallerEnum { + fn new(syscall: Sysno, syscall_args: &HashMap) -> Self { + match syscall { + Sysno::open => Self::OpenCall(OpenCall::new(syscall_args)), + Sysno::openat => Self::OpenatCall(OpenatCall::new(syscall_args)), + Sysno::socket => Self::SocketCall(SocketCall::new(syscall_args)), + Sysno::connect => Self::ConnectCall(ConnectCall::new(syscall_args)), + Sysno::listen => Self::ListenCall(ListenCall::new(syscall_args)), + Sysno::accept => { + Self::AcceptCall(AcceptCall::new(syscall_args, Sysno::accept)) + } + Sysno::accept4 => { + // For accept4, we need to base it on accept + Self::AcceptCall(AcceptCall::new(syscall_args, Sysno::accept4)) + } + Sysno::setuid => Self::SetuidCall(SetuidCall::new(syscall_args)), + Sysno::setreuid => { + Self::SetreuidCall(SetreuidCall::new(syscall_args)) + } + Sysno::setresuid => { + Self::SetresuidCall(SetresuidCall::new(syscall_args)) + } + Sysno::mmap => Self::MmapCall(MmapCall::new(syscall_args)), + Sysno::mount => Self::MountCall(MountCall::new(syscall_args)), + Sysno::unlink => Self::UnlinkCall(UnlinkCall::new(syscall_args)), + Sysno::unshare => Self::UnshareCall(UnshareCall::new(syscall_args)), + Sysno::chown => Self::ChownCall(ChownCall::new(syscall_args)), + Sysno::chmod => Self::ChmodCall(ChmodCall::new(syscall_args)), + Sysno::prctl => Self::PrctlCall(PrctlCall::new(syscall_args)), + Sysno::ioctl => Self::IoctlCall(IoctlCall::new(syscall_args)), + Sysno::capset => Self::CapsetCall(CapsetCall::new(syscall_args)), + _ => Self::DummyCall(DummyCall::new(syscall_args, syscall)), + } + } +} + +fn get_argument( + args: &HashMap, + name: &str, + default: T, +) -> T { + if let Some(arg) = args.get(name) { + arg.parse().unwrap_or(default) + } else { + default + } +} diff --git a/src/worker/syscalls/mount.rs b/src/worker/syscalls/mount.rs new file mode 100644 index 0000000..9319ab9 --- /dev/null +++ b/src/worker/syscalls/mount.rs @@ -0,0 +1,55 @@ +use libc::MS_PRIVATE; +use std::collections::HashMap; +use std::ffi::CString; + +use super::{SysCaller, get_argument}; +use syscalls::{Errno, Sysno}; + +#[derive(Debug)] +pub struct MountCall { + pub source: CString, + pub target: CString, + pub filesystemtype: CString, + pub mountflags: usize, + pub data: usize, +} + +impl MountCall { + pub fn new(mount_args: &HashMap) -> Self { + let source = + get_argument(mount_args, "source", CString::new("").unwrap()); + let target = + get_argument(mount_args, "target", CString::new("/tmp").unwrap()); + let filesystemtype = get_argument( + mount_args, + "filesystemtype", + CString::new("").unwrap(), + ); + let mountflags = + get_argument(mount_args, "mountflags", MS_PRIVATE as usize); + let data = 0; + + Self { + source, + target, + filesystemtype, + mountflags, + data, + } + } +} + +impl SysCaller for MountCall { + fn call(&self) -> Result { + unsafe { + syscalls::syscall!( + Sysno::mount, + self.source.as_ptr(), + self.target.as_ptr(), + self.filesystemtype.as_ptr(), + self.mountflags, + self.data + ) + } + } +} diff --git a/src/worker/syscalls/open.rs b/src/worker/syscalls/open.rs new file mode 100644 index 0000000..fc9a736 --- /dev/null +++ b/src/worker/syscalls/open.rs @@ -0,0 +1,44 @@ +use std::collections::HashMap; +use std::{ffi::CString, fs::File, os::fd::FromRawFd}; + +use syscalls::{Errno, Sysno, syscall}; + +use super::SysCaller; +use super::get_argument; + +#[derive(Debug)] +pub struct OpenCall { + pub pathname: CString, + pub flags: usize, + pub mode: usize, +} + +impl OpenCall { + pub fn new(open_args: &HashMap) -> Self { + let pathname = + get_argument(open_args, "pathname", CString::new("/tmp").unwrap()); + let flags = get_argument(open_args, "flags", 0); + let mode = get_argument(open_args, "mode", 0); + + Self { + pathname, + flags, + mode, + } + } +} + +impl SysCaller for OpenCall { + fn call(&self) -> Result { + let res = unsafe { + syscall!(Sysno::open, self.pathname.as_ptr(), self.flags, self.mode) + }; + + if let Ok(fd) = res { + // Close file descriptor + unsafe { File::from_raw_fd(fd as i32) }; + } + + res + } +} diff --git a/src/worker/syscalls/openat.rs b/src/worker/syscalls/openat.rs new file mode 100644 index 0000000..f3c6ee3 --- /dev/null +++ b/src/worker/syscalls/openat.rs @@ -0,0 +1,55 @@ +use std::collections::HashMap; +use std::{ffi::CString, fs::File, os::fd::FromRawFd}; + +use super::SysCaller; +use super::get_argument; +use syscalls::{Errno, Sysno, syscall}; + +#[derive(Debug)] +pub struct OpenatCall { + pub dirfd: usize, + pub pathname: CString, + pub flags: usize, + pub mode: usize, +} + +impl OpenatCall { + pub fn new(openat_args: &HashMap) -> Self { + let dirfd = 0; // Default value, can be overridden if needed + let pathname = get_argument( + openat_args, + "pathname", + CString::new("/tmp").unwrap(), + ); + let flags = get_argument(openat_args, "flags", 0); + let mode = get_argument(openat_args, "mode", 0); + + Self { + dirfd, + pathname, + flags, + mode, + } + } +} + +impl SysCaller for OpenatCall { + fn call(&self) -> Result { + let res = unsafe { + syscall!( + Sysno::openat, + self.dirfd, + self.pathname.as_ptr(), + self.flags, + self.mode + ) + }; + + if let Ok(fd) = res { + // Close file descriptor + unsafe { File::from_raw_fd(fd as i32) }; + } + + res + } +} diff --git a/src/worker/syscalls/prctl.rs b/src/worker/syscalls/prctl.rs new file mode 100644 index 0000000..1ab797d --- /dev/null +++ b/src/worker/syscalls/prctl.rs @@ -0,0 +1,46 @@ +use super::{SysCaller, get_argument}; +use libc::PR_GET_KEEPCAPS; +use std::collections::HashMap; +use syscalls::{self, Sysno, syscall}; + +#[derive(Debug)] +pub struct PrctlCall { + pub op: usize, + pub arg2: usize, + pub arg3: usize, + pub arg4: usize, + pub arg5: usize, +} + +impl PrctlCall { + pub fn new(prctl_args: &HashMap) -> Self { + let op = get_argument(prctl_args, "op", PR_GET_KEEPCAPS as usize); + let arg2 = get_argument(prctl_args, "arg2", 0); + let arg3 = get_argument(prctl_args, "arg3", 0); + let arg4 = get_argument(prctl_args, "arg4", 0); + let arg5 = get_argument(prctl_args, "arg5", 0); + + Self { + op, + arg2, + arg3, + arg4, + arg5, + } + } +} + +impl SysCaller for PrctlCall { + fn call(&self) -> Result { + unsafe { + syscall!( + Sysno::prctl, + self.op, + self.arg2, + self.arg3, + self.arg4, + self.arg5 + ) + } + } +} diff --git a/src/worker/syscalls/setresuid.rs b/src/worker/syscalls/setresuid.rs new file mode 100644 index 0000000..f002b7f --- /dev/null +++ b/src/worker/syscalls/setresuid.rs @@ -0,0 +1,26 @@ +use super::{SysCaller, get_argument}; +use std::collections::HashMap; +use syscalls::{Errno, Sysno, syscall}; + +#[derive(Debug)] +pub struct SetresuidCall { + pub ruid: usize, + pub euid: usize, + pub suid: usize, +} + +impl SetresuidCall { + pub fn new(setresuid_args: &HashMap) -> Self { + let ruid = get_argument(setresuid_args, "ruid", 0); + let euid = get_argument(setresuid_args, "euid", 0); + let suid = get_argument(setresuid_args, "suid", 0); + + Self { ruid, euid, suid } + } +} + +impl SysCaller for SetresuidCall { + fn call(&self) -> Result { + unsafe { syscall!(Sysno::setresuid, self.ruid, self.euid, self.suid) } + } +} diff --git a/src/worker/syscalls/setreuid.rs b/src/worker/syscalls/setreuid.rs new file mode 100644 index 0000000..889b5da --- /dev/null +++ b/src/worker/syscalls/setreuid.rs @@ -0,0 +1,24 @@ +use super::{SysCaller, get_argument}; +use std::collections::HashMap; +use syscalls::{Errno, Sysno, syscall}; + +#[derive(Debug)] +pub struct SetreuidCall { + pub ruid: usize, + pub euid: usize, +} + +impl SetreuidCall { + pub fn new(setreuid_args: &HashMap) -> Self { + let ruid = get_argument(setreuid_args, "ruid", 0); + let euid = get_argument(setreuid_args, "euid", 0); + + Self { ruid, euid } + } +} + +impl SysCaller for SetreuidCall { + fn call(&self) -> Result { + unsafe { syscall!(Sysno::setreuid, self.ruid, self.euid) } + } +} diff --git a/src/worker/syscalls/setuid.rs b/src/worker/syscalls/setuid.rs new file mode 100644 index 0000000..1ac2242 --- /dev/null +++ b/src/worker/syscalls/setuid.rs @@ -0,0 +1,22 @@ +use super::{SysCaller, get_argument}; +use std::collections::HashMap; +use syscalls::{Errno, Sysno, syscall}; + +#[derive(Debug)] +pub struct SetuidCall { + pub uid: usize, +} + +impl SetuidCall { + pub fn new(setuid_args: &HashMap) -> Self { + let uid = get_argument(setuid_args, "uid", 0); + + Self { uid } + } +} + +impl SysCaller for SetuidCall { + fn call(&self) -> Result { + unsafe { syscall!(Sysno::setuid, self.uid) } + } +} diff --git a/src/worker/syscalls/socket.rs b/src/worker/syscalls/socket.rs new file mode 100644 index 0000000..707ff43 --- /dev/null +++ b/src/worker/syscalls/socket.rs @@ -0,0 +1,48 @@ +use libc::{AF_INET, SOCK_STREAM}; +use std::collections::HashMap; +use std::{fs::File, os::fd::FromRawFd}; + +use super::{SysCaller, get_argument}; +use log::info; +use syscalls::{Errno, Sysno, syscall}; + +#[derive(Debug)] +pub struct SocketCall { + pub domain: usize, + pub stype: usize, + pub protocol: usize, +} + +impl SocketCall { + pub fn new(socket_args: &HashMap) -> Self { + let domain = get_argument(socket_args, "domain", AF_INET as usize); + let stype = get_argument(socket_args, "type", SOCK_STREAM as usize); + let protocol = get_argument(socket_args, "protocol", 0); + + Self { + domain, + stype, + protocol, + } + } +} + +impl SysCaller for SocketCall { + fn call(&self) -> Result { + let res = unsafe { + syscall!( + Sysno::socket, + self.domain, + self.stype | libc::SOCK_NONBLOCK as usize, + self.protocol + ) + }; + + if let Ok(fd) = res { + // Close file descriptor + unsafe { File::from_raw_fd(fd as i32) }; + } + + res + } +} diff --git a/src/worker/syscalls/unlink.rs b/src/worker/syscalls/unlink.rs new file mode 100644 index 0000000..3c5389f --- /dev/null +++ b/src/worker/syscalls/unlink.rs @@ -0,0 +1,28 @@ +use std::collections::HashMap; +use std::ffi::CString; + +use super::{SysCaller, get_argument}; +use syscalls::{Errno, Sysno, syscall}; + +#[derive(Debug)] +pub struct UnlinkCall { + pub pathname: CString, +} + +impl UnlinkCall { + pub fn new(unlink_args: &HashMap) -> Self { + let pathname = get_argument( + unlink_args, + "pathname", + CString::new("/privileged_dir/file").unwrap(), + ); + + Self { pathname } + } +} + +impl SysCaller for UnlinkCall { + fn call(&self) -> Result { + unsafe { syscall!(Sysno::unlink, self.pathname.as_ptr()) } + } +} diff --git a/src/worker/syscalls/unshare.rs b/src/worker/syscalls/unshare.rs new file mode 100644 index 0000000..c62d630 --- /dev/null +++ b/src/worker/syscalls/unshare.rs @@ -0,0 +1,22 @@ +use super::{SysCaller, get_argument}; +use std::collections::HashMap; +use syscalls::{Errno, Sysno}; + +#[derive(Debug)] +pub struct UnshareCall { + pub flags: usize, +} + +impl UnshareCall { + pub fn new(unshare_args: &HashMap) -> Self { + let flags = get_argument(unshare_args, "flags", 0); + + Self { flags } + } +} + +impl SysCaller for UnshareCall { + fn call(&self) -> Result { + unsafe { syscalls::syscall!(Sysno::unshare, self.flags) } + } +} diff --git a/workloads/io_uring.toml b/workloads/io_uring.toml new file mode 100644 index 0000000..5aa271c --- /dev/null +++ b/workloads/io_uring.toml @@ -0,0 +1,22 @@ +# An amount of time for workload payload to run before restarting. +restart_interval = 10 +# Controls per-core mode to handle number of workers. If per-core mode +# is enabled, `workers` will be treated as a number of workers per CPU +# core. Otherwise it will be treated as a total number of workers. +per_core = false +# How many workers to spin, depending on `per_core` in either per-core +# or total mode. +workers = 1 +# For how long to run the worker. Default value is zero, meaning no limit. +duration = 0 + +[workload] +type = "iouring" +# How often to invoke a io_uring call. Parameter of exponential distribution. +arrival_rate = 0.001 +# io_uring number to invoke. List can be found +# at https://github.com/tokio-rs/io-uring/blob/master/src/sys/sys_x86_64.rs +# or at https://github.com/torvalds/linux/blob/master/include/uapi/linux/io_uring.h +iouring_nr = 36 +# Arguments for io_uring call in format "arg1=value1,arg2=value2". Not all arguments can be parsed. +iouring_args = "" diff --git a/workloads/syscalls.toml b/workloads/syscalls.toml deleted file mode 100644 index 0cbdcfc..0000000 --- a/workloads/syscalls.toml +++ /dev/null @@ -1,5 +0,0 @@ -restart_interval = 10 - -[workload] -type = "syscalls" -arrival_rate = 10.0 diff --git a/workloads/syscalls/syscall.toml b/workloads/syscalls/syscall.toml new file mode 100644 index 0000000..87a45b6 --- /dev/null +++ b/workloads/syscalls/syscall.toml @@ -0,0 +1,22 @@ +# An amount of time for workload payload to run before restarting. +restart_interval = 10 +# Controls per-core mode to handle number of workers. If per-core mode +# is enabled, `workers` will be treated as a number of workers per CPU +# core. Otherwise it will be treated as a total number of workers. +per_core = false +# How many workers to spin, depending on `per_core` in either per-core +# or total mode. +workers = 1 +# For how long to run the worker. Default value is zero, meaning no limit. +duration = 0 + +[workload] +type = "syscalls" +# Run without any delay between syscalls. +tight_loop = false +# How often to invoke a syscall. Parameter of exponential distribution. +arrival_rate = 10.0 +# Syscall number to invoke. +syscall_nr = 162 +# Arguments for syscall in format "arg1=value1,arg2=value2". Not all arguments can be parsed. +syscall_args = "" diff --git a/workloads/syscalls/syscall_accept.toml b/workloads/syscalls/syscall_accept.toml new file mode 100644 index 0000000..7916b41 --- /dev/null +++ b/workloads/syscalls/syscall_accept.toml @@ -0,0 +1,9 @@ +restart_interval = 10 +per_core = false +workers = 1 + +[workload] +type = "syscalls" +arrival_rate = 0.001 +syscall_nr = 43 +syscall_args = "" # will be called with default parameters \ No newline at end of file diff --git a/workloads/syscalls/syscall_open.toml b/workloads/syscalls/syscall_open.toml new file mode 100644 index 0000000..92ab71a --- /dev/null +++ b/workloads/syscalls/syscall_open.toml @@ -0,0 +1,9 @@ +restart_interval = 10 +per_core = false +workers = 1 + +[workload] +type = "syscalls" +arrival_rate = 0.001 +syscall_nr = 2 +syscall_args = "pathname=/tmp,flags=0,mode=0"