1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
//!Hook libc functions with an easy API //! //!## Usage //! //!1- Import the trait [RunHook] //! //!2- Create an [Command](std::process::Command) with [Command::new](std::process::Command::new) and add hooks to it via [add_hook](RunHook::add_hook) and [add_hooks](RunHook::add_hooks) methods //! //!3- Confirm the hooks with [set_hooks](RunHook::set_hooks) method this step is necessary //! //!3.1- Hooks are closures that takes no input and return an option of the libc function as output. //! //! If the closure return `None` that is equivalent to returning `Some(original_function(args))` in //! other words it will run and use the original function output //! //! Inside the closure you have access to the libc function input + some imports from std (see //! src/scaffold.rs) //! //!4- Now you can carry on with the usual [Command](std::process::Command) methods ([output](std::process::Command::output), [spawn](std::process::Command::spawn),[status](std::process::Command::status),..) //! //! //!**Tricks:** //! //! The closure used for hooks have acess to many things: (imported by https://github.com/sigmaSd/Rhook/blob/master/src/scaffold.rs) //! - closure input (which is the libc function input) //! - closure output (which is the libc function output) //! - The original function with the following name `original_$libcfn` this is useful in particular to avoid recursion //! - Some varaibles to make coding easier: `transmute` `ManuallyDrop` `CString` and a static mut `COUNTER` //! //! - You can find the input/output of a function by looking it up here [libc](https://docs.rs/libc) //! //!## Example //! //!Say you want to limit the bandwidth of a program //! //!Usually downloading calls `libc::recv` function //! //!So our goal is to throttle it with a simple sleep //! //!To do that with this crate: (taking speedtest program as an example) //! //!1- Look up its doc's here [recv](https://docs.rs/libc/0.2.93/libc/fn.recv.html) to see what the //!function's input/output is //! //!2- use this crate //!```rust //!use rhook::{RunHook, Hook}; //! //!std::process::Command::new("speedtest").add_hook(Hook::recv(stringify!(||{ //! std::thread::sleep(std::time::Duration::from_millis(10)); //! // since we're not doing any modification to the output you can just return None here //! Some(original_recv(socket, buf, len, flags)) //!}))).set_hooks().unwrap().spawn(); //!``` //! //!Thats it! //!Note that you have acess inside the closure to the original function denoted by the prefix //!`original_` + the function name //! //!Couple of points: //!- If you take ownership of an input value inside of the closure, be sure to use ManuallyDrop so //!you don't free it //! //!Check out the examples for more info #[cfg(not(unix))] compile_error!("This crate is unix only"); pub(crate) mod libcfn; use std::{ cell::RefCell, collections::HashSet, io::{self, Write}, sync::Mutex, }; use once_cell::sync::Lazy; use std::io::Result; use std::process::{Command, Stdio}; // each thread have its own copy of the hooks thread_local! { static HOOKS: RefCell<HashSet<Hook>> = RefCell::new(HashSet::new()); } static RHOOK_DYNLIB_DIR_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(())); mod hook; pub use hook::Hook; /// Specify libc hooks for a Command pub trait RunHook { /// Add a libc hook to the command fn add_hook(&mut self, hook: Hook) -> &mut Self { HOOKS.with(|hooks| hooks.borrow_mut().insert(hook)); self } /// Add a Vec of libc hooks to the command fn add_hooks(&mut self, hooks: Vec<Hook>) -> &mut Self { for hook in hooks { self.add_hook(hook); } self } /// Set the hooks, this is a required method since it does the actual work of creating a /// dynamic library and linking the target program with it fn set_hooks(&mut self) -> Result<&mut Self>; } impl RunHook for Command { fn set_hooks(&mut self) -> Result<&mut Self> { //only one Command should do the next lines at a given time //take lock here let _lock = RHOOK_DYNLIB_DIR_LOCK.lock().expect("should not happen"); prepare()?; let mut drained_hooks = HashSet::new(); HOOKS.with(|hooks| drained_hooks = hooks.borrow_mut().drain().collect()); for hook in drained_hooks { append(hook.function())?; } build_dylib()?; //drop lock here drop(_lock); Ok(self.env("LD_PRELOAD", "/tmp/rhookdyl/target/debug/librhookdyl.so")) } } /// Create the dynamic library and write the scaffold to it fn prepare() -> Result<()> { const CARGO_TOML: &str = r#"[package] name = "rhookdyl" version = "0.1.0" edition = "2018" [lib] crate-type = ["dylib"] [dependencies] libc = "0.2.92""#; // Ignore project already exists error Command::new("cargo") .arg("new") .arg("rhookdyl") .arg("--lib") .current_dir("/tmp") .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn()? .wait()?; std::fs::write("/tmp/rhookdyl/Cargo.toml", CARGO_TOML)?; std::fs::write("/tmp/rhookdyl/src/lib.rs", include_str!("scaffold.rs"))?; Ok(()) } /// Append rust generated code to the initial scaffold fn append(fun: String) -> Result<()> { std::fs::OpenOptions::new() .append(true) .open("/tmp/rhookdyl/src/lib.rs")? .write_all(fun.as_bytes())?; Ok(()) } /// Build the dynamic library fn build_dylib() -> Result<()> { let status = Command::new("cargo") .arg("b") .current_dir("/tmp/rhookdyl") .env("CARGO_TARGET_DIR", "/tmp/rhookdyl/target") .spawn()? .wait()?; if status.success() { Ok(()) } else { Err(io::Error::new( io::ErrorKind::Other, "failed to compile the dynamic library", )) } }