diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 9a61830..d585ae4 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -285,6 +285,18 @@ dependencies = [ "wasi", ] +[[package]] +name = "getset" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "glob" version = "0.3.3" @@ -519,6 +531,28 @@ dependencies = [ "syn", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.103" @@ -916,6 +950,7 @@ dependencies = [ "anyhow", "clap", "daemonize", + "getset", "iptables", "libc", "libnfqws", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 9eff617..edafc77 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -23,3 +23,4 @@ log = "0.4.28" pretty_env_logger = "0.5.0" nix = { version = "0.30.1", features = ["signal"] } iptables = { git = "https://github.com/sqlerrorthing/rust-iptables.git", branch = "feat/add-android" } +getset = "0.1.6" diff --git a/rust/crates/libnfqws/build.rs b/rust/crates/libnfqws/build.rs index e1769b0..6ba00d5 100644 --- a/rust/crates/libnfqws/build.rs +++ b/rust/crates/libnfqws/build.rs @@ -37,13 +37,13 @@ fn main() { println!("cargo:rustc-link-lib=nfnetlink"); println!("cargo:rustc-link-lib=mnl"); - if let Ok(link_libs) = env::var("NETFILTER_LIBS") { - println!("cargo:rustc-link-search=native={link_libs}/lib"); - } + let _ = env::var("NETFILTER_LIBS") + .map(|libs| println!("cargo:rustc-link-search=native={libs}/lib")); println!("cargo:rustc-link-lib=static=nfqws"); println!("cargo:rerun-if-changed={}", NFQ.display()); println!("cargo:rerun-if-changed={}", NFQ_CRYPTO.display()); + println!("cargo:rerun-if-changed=build.rs"); let mut builder = bindgen::Builder::default(); diff --git a/rust/crates/zaprett/Cargo.toml b/rust/crates/zaprett/Cargo.toml index 18ee726..30483b7 100644 --- a/rust/crates/zaprett/Cargo.toml +++ b/rust/crates/zaprett/Cargo.toml @@ -20,3 +20,4 @@ pretty_env_logger = { workspace = true } log = { workspace = true } nix = { workspace = true, features = ["user"] } iptables = { workspace = true } +getset = { workspace = true } \ No newline at end of file diff --git a/rust/crates/zaprett/src/cli.rs b/rust/crates/zaprett/src/cli.rs new file mode 100644 index 0000000..fe4768d --- /dev/null +++ b/rust/crates/zaprett/src/cli.rs @@ -0,0 +1,12 @@ +use clap::Parser; +use getset::Getters; +use serde::{Deserialize, Serialize}; +use crate::commands::Command; + +#[derive(Parser, Getters)] +#[command(version)] +#[getset(get = "pub")] +pub struct CliApp { + #[command(subcommand)] + cmd: Option, +} diff --git a/rust/crates/zaprett/src/commands.rs b/rust/crates/zaprett/src/commands.rs new file mode 100644 index 0000000..289d381 --- /dev/null +++ b/rust/crates/zaprett/src/commands.rs @@ -0,0 +1,66 @@ +use crate::{bin_version, get_autostart, module_version, restart_service, service_status, set_autostart, start_service, stop_service}; +use clap::Subcommand; +use log::error; + +#[derive(Subcommand)] +pub enum Command { + /// Start the service + Start, + + /// Stop the service + Stop, + + /// Restart the service + Restart, + + /// Show the current service status + Status, + + /// Enable or disable automatic restart + SetAutostart { + /// Whether to enable (true) or disable (false) autostart + #[arg(value_parser = clap::value_parser!(bool))] + autostart: bool, + }, + + /// Show whether autostart is enabled + GetAutostart, + + /// Show the module version + ModuleVersion, + + /// Show the nfqws binary version + BinaryVersion, +} + +impl Command { + pub async fn exec(&self) -> anyhow::Result<()> { + match self { + Command::Start => return start_service().await, + Command::Stop => { + let _ = stop_service().await; + }, + Command::Restart => return restart_service().await, + Command::Status => { + println!( + "zaprett is {}", + if service_status().await { + "working" + } else { + "stopped" + } + ); + }, + Command::SetAutostart { autostart } => { + if let Err(err) = set_autostart(autostart).await { + error!("Failed to set auto start: {err}") + } + }, + Command::GetAutostart => get_autostart(), + Command::ModuleVersion => module_version(), + Command::BinaryVersion => bin_version(), + } + + Ok(()) + } +} \ No newline at end of file diff --git a/rust/crates/zaprett/src/config.rs b/rust/crates/zaprett/src/config.rs new file mode 100644 index 0000000..4667c15 --- /dev/null +++ b/rust/crates/zaprett/src/config.rs @@ -0,0 +1,65 @@ +use getset::Getters; +use serde::{Deserialize, Serialize}; +use crate::{merge_files, MODULE_PATH}; + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ListType { + Whitelist, + Blacklist, +} + +#[derive(Serialize, Deserialize, Getters)] +#[getset(get = "pub")] +pub struct Config { + active_lists: Vec, + active_ipsets: Vec, + active_exclude_lists: Vec, + active_exclude_ipsets: Vec, + list_type: ListType, + strategy: String, + app_list: String, + whitelist: Vec, + blacklist: Vec, +} + +impl ListType { + /// # Returns + /// + /// (hostlist arg, ipset arg) + pub async fn merge(&self, config: &Config) -> (String, String) { + let module_path_str = MODULE_PATH.to_str().unwrap(); + + let (host_files, ipset_files, host_suffix, ipset_suffix) = match self { + ListType::Whitelist => ( + &config.active_lists, + &config.active_ipsets, + "hostlist", + "ipset", + ), + ListType::Blacklist => ( + &config.active_exclude_lists, + &config.active_exclude_ipsets, + "hostlist-exclude", + "ipset-exclude", + ), + }; + + let host_path = MODULE_PATH.join(format!("tmp/{host_suffix}")); + let ipset_path = MODULE_PATH.join(format!("tmp/{ipset_suffix}")); + + merge_files(host_files, host_path).await.unwrap(); + merge_files(ipset_files, ipset_path).await.unwrap(); + + let exclude = if matches!(self, ListType::Blacklist) { + "-exclude" + } else { + "" + }; + + ( + format!("--hostlist{exclude}={module_path_str}/tmp/{host_suffix}"), + format!("--ipset{exclude}={module_path_str}/tmp/{ipset_suffix}"), + ) + } +} diff --git a/rust/crates/zaprett/src/lib.rs b/rust/crates/zaprett/src/lib.rs new file mode 100644 index 0000000..8d6dc6b --- /dev/null +++ b/rust/crates/zaprett/src/lib.rs @@ -0,0 +1,268 @@ +pub mod commands; +pub mod cli; +pub mod config; + +use std::error; +use std::ffi::CString; +use std::os::raw::c_char; +use std::path::Path; +use std::sync::LazyLock; +use anyhow::bail; +use daemonize::Daemonize; +use ini::Ini; +use log::{error, info}; +use nix::sys::signal::{kill, Signal}; +use nix::unistd::{Pid, Uid}; +use regex::Regex; +use sysctl::Sysctl; +use tokio::{fs, task}; +use tokio::fs::File; +use tokio::io::{copy, AsyncReadExt, AsyncWriteExt}; +use libnfqws::nfqws_main; +use crate::config::Config; + +pub static MODULE_PATH: LazyLock<&Path> = LazyLock::new(|| Path::new("/data/adb/modules/zaprett")); +pub static ZAPRETT_DIR_PATH: LazyLock<&Path> = + LazyLock::new(|| Path::new("/storage/emulated/0/zaprett")); + +async fn daemonize_nfqws(args: &str) { + info!("Starting nfqws as a daemon"); + let daemonize = Daemonize::new() + .pid_file(MODULE_PATH.join("tmp/pid.lock").as_path()) + .working_directory("/tmp") + .group("daemon") + .privileged_action(|| "Executed before drop privileges"); + + match daemonize.start() { + Ok(_) => { + info!("Success, daemonized"); + run_nfqws(args).await.unwrap() + } + Err(e) => error!("Error while starting nfqws daemon: {e}"), + } +} + +async fn start_service() -> anyhow::Result<()> { + if !Uid::effective().is_root() { + bail!("Running not from root, exiting"); + }; + + info!("Starting zaprett service..."); + + let tmp_dir = MODULE_PATH.join("/tmp"); + if tmp_dir.exists() { + fs::remove_dir_all(&tmp_dir).await?; + fs::create_dir_all(&tmp_dir).await?; + } + + let mut config_contents = String::new(); + File::open(ZAPRETT_DIR_PATH.join("config.json")) + .await + .expect("cannot open config.json") + .read_to_string(&mut config_contents).await?; + + let config: Config = serde_json::from_str(&config_contents).expect("invalid json"); + + let def_strat = String::from(" + --filter-tcp=80 --dpi-desync=fake,split2 --dpi-desync-autottl=2 --dpi-desync-fooling=md5sig,badsum $hostlist --new + --filter-tcp=443 $hostlist --dpi-desync=fake,split2 --dpi-desync-repeats=6 --dpi-desync-fooling=md5sig,badsum --dpi-desync-fake-tls=${zaprettdir}/bin/tls_clienthello_www_google_com.bin --new + --filter-tcp=80,443 --dpi-desync=fake,disorder2 --dpi-desync-repeats=6 --dpi-desync-autottl=2 --dpi-desync-fooling=md5sig,badsum $hostlist --new + --filter-udp=50000-50100 --dpi-desync=fake --dpi-desync-any-protocol --dpi-desync-fake-quic=0xC30000000108 --new + --filter-udp=443 $hostlist --dpi-desync=fake --dpi-desync-repeats=6 --dpi-desync-fake-quic=${zaprettdir}/bin/quic_initial_www_google_com.bin --new + --filter-udp=443 --dpi-desync=fake --dpi-desync-repeats=6 $hostlist + "); + + let start = fs::read_to_string(config.strategy()) + .await + .unwrap_or(def_strat); + + let regex_hostlist = Regex::new(r"\$hostlist")?; + let regex_ipsets = Regex::new(r"\$ipset")?; + let regex_zaprettdir = Regex::new(r"\$\{?zaprettdir}?")?; + + let mut strat_modified; + let (hosts, ipsets) = config.list_type().merge(&config).await; + + strat_modified = regex_hostlist.replace_all(&start, &hosts).into_owned(); + strat_modified = regex_ipsets + .replace_all(&strat_modified, &ipsets) + .into_owned(); + strat_modified = regex_zaprettdir + .replace_all(&strat_modified, ZAPRETT_DIR_PATH.to_str().unwrap()) + .into_owned(); + + let ctl = sysctl::Ctl::new("net.netfilter.nf_conntrack_tcp_be_liberal")?; + ctl.set_value(sysctl::CtlValue::String("1".into()))?; + + setup_iptables_rules().expect("setup iptables rules"); + + daemonize_nfqws(&strat_modified).await; + info!("zaprett service started!"); + Ok(()) +} + +async fn stop_service() -> anyhow::Result<()> { + if !Uid::effective().is_root() { + bail!("Running not from root, exiting"); + }; + + clear_iptables_rules().expect("clear iptables rules"); + + let pid_str = fs::read_to_string(MODULE_PATH.join("tmp/pid.lock")).await?; + let pid = pid_str.trim().parse::()?; + + kill(Pid::from_raw(pid), Signal::SIGKILL)?; + + Ok(()) +} + +async fn restart_service() -> anyhow::Result<()> { + stop_service().await?; + start_service().await?; + info!("zaprett service restarted!"); + Ok(()) +} + +async fn set_autostart(autostart: &bool) -> Result<(), anyhow::Error> { + if *autostart { + File::create(MODULE_PATH.join("autostart")).await?; + } else { + fs::remove_file(MODULE_PATH.join("autostart")).await?; + } + + Ok(()) +} + +fn get_autostart() { + let file = MODULE_PATH.join("autostart"); + println!("{}", file.exists()); +} + +async fn service_status() -> bool { + fs::read_to_string(MODULE_PATH.join("tmp/pid.lock")) + .await + .ok() + .and_then(|pid_str| pid_str.trim().parse::().ok()) + .is_some() +} + +fn module_version() { + if let Ok(prop) = Ini::load_from_file(MODULE_PATH.join("module.prop")) + && let Some(props) = prop.section::(None) + && let Some(version) = props.get("version") + { + println!("{version}"); + } +} + +fn bin_version() { + println!("{}", env!("ZAPRET_VERSION")); +} + +pub async fn merge_files( + input_paths: &[impl AsRef], + output_path: impl AsRef, +) -> Result<(), Box> { + let output_path = output_path.as_ref(); + let mut output_file = File::create(output_path).await?; + + for input in input_paths { + let input_path = input.as_ref(); + let mut input_file = File::open(input_path) + .await + .map_err(|e| format!("Failed to open {}: {}", input_path.display(), e))?; + + copy(&mut input_file, &mut output_file) + .await + .map_err(|e| { + format!( + "Failed to write contents of {}: {}", + input_path.display(), + e + ) + })?; + } + + output_file.flush().await?; + Ok(()) +} + +fn setup_iptables_rules() -> Result<(), Box> { + let ipt = iptables::new(false)?; + + ipt.insert( + "mangle", + "POSTROUTING", + "-j NFQUEUE --queue-num 200 --queue-bypass", + 1, + )?; + + ipt.insert( + "mangle", + "PREROUTING", + "-j NFQUEUE --queue-num 200 --queue-bypass", + 1, + )?; + + ipt.append( + "filter", + "FORWARD", + "-j NFQUEUE --queue-num 200 --queue-bypass", + )?; + + Ok(()) +} + +fn clear_iptables_rules() -> Result<(), Box> { + let ipt = iptables::new(false)?; + + ipt.delete( + "mangle", + "POSTROUTING", + "-j NFQUEUE --queue-num 200 --queue-bypass", + )?; + + ipt.delete( + "mangle", + "PREROUTING", + "-j NFQUEUE --queue-num 200 --queue-bypass", + )?; + + ipt.delete( + "filter", + "FORWARD", + "-j NFQUEUE --queue-num 200 --queue-bypass", + )?; + + Ok(()) +} + +async fn run_nfqws(args_str: &str) -> anyhow::Result<()> { + if service_status().await { + bail!("nfqws already started!"); + } + + let mut args = vec!["nfqws".to_string()]; + + if args_str.trim().is_empty() { + args.push("-v".to_string()); + } else { + args.extend(args_str.split_whitespace().map(String::from)); + } + + task::spawn_blocking(move || { + let c_args: Vec = args + .into_iter() + .map(|arg| CString::new(arg).unwrap()) + .collect(); + + let mut ptrs: Vec<*const c_char> = c_args.iter().map(|arg| arg.as_ptr()).collect(); + + unsafe { + nfqws_main(c_args.len() as libc::c_int, ptrs.as_mut_ptr() as *mut _); + } + }) + .await?; + + Ok(()) +} diff --git a/rust/crates/zaprett/src/main.rs b/rust/crates/zaprett/src/main.rs index 698bcab..526996a 100644 --- a/rust/crates/zaprett/src/main.rs +++ b/rust/crates/zaprett/src/main.rs @@ -1,403 +1,16 @@ -use anyhow::bail; -use clap::{ArgAction, Parser, Subcommand, builder::BoolishValueParser}; -use daemonize::Daemonize; -use ini::Ini; -use libnfqws::nfqws_main; -use log::{error, info}; -use nix::sys::signal::{Signal, kill}; -use nix::unistd::{Pid, Uid}; -use regex::Regex; -use serde::{Deserialize, Serialize}; -use std::ffi::CString; -use tokio::fs; -use std::os::raw::c_char; -use std::sync::LazyLock; -use std::{error}; -use std::{path::Path}; -use sysctl::Sysctl; -use tokio::io::{copy, AsyncReadExt, AsyncWriteExt}; -use tokio::fs::File; -use tokio::task; - -#[derive(Parser)] -#[command(version)] -struct Cli { - #[command(subcommand)] - cmd: Option, -} - -#[derive(Subcommand)] -enum Commands { - #[clap(about = "Start service")] - Start, - - #[clap(about = "Stop service")] - Stop, - - #[clap(about = "Restart service")] - Restart, - - #[clap(about = "Show service status")] - Status, - - #[clap(about = "Enable/disable autorestart")] - SetAutostart { - #[arg( - value_name = "boolean", - action = ArgAction::Set, - value_parser = BoolishValueParser::new() - )] - autostart: bool, - }, - - #[clap(about = "Get autorestart state")] - GetAutostart, - - #[clap(about = "Get module version")] - ModuleVer, - - #[clap(about = "Get nfqws binary version")] - BinVer, -} - -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -enum ListType { - Whitelist, - Blacklist, -} - -impl ListType { - /// # Returns - /// - /// (hostlist arg, ipset arg) - async fn merge(&self, config: &Config) -> (String, String) { - let module_path_str = MODULE_PATH.to_str().unwrap(); - - let (host_files, ipset_files, host_suffix, ipset_suffix) = match self { - ListType::Whitelist => ( - &config.active_lists, - &config.active_ipsets, - "hostlist", - "ipset", - ), - ListType::Blacklist => ( - &config.active_exclude_lists, - &config.active_exclude_ipsets, - "hostlist-exclude", - "ipset-exclude", - ), - }; - - let host_path = MODULE_PATH.join(format!("tmp/{host_suffix}")); - let ipset_path = MODULE_PATH.join(format!("tmp/{ipset_suffix}")); - - merge_files(host_files, host_path).await.unwrap(); - merge_files(ipset_files, ipset_path).await.unwrap(); - - let exclude = if matches!(self, ListType::Blacklist) { - "-exclude" - } else { - "" - }; - - ( - format!("--hostlist{exclude}={module_path_str}/tmp/{host_suffix}"), - format!("--ipset{exclude}={module_path_str}/tmp/{ipset_suffix}"), - ) - } -} - -#[derive(Serialize, Deserialize)] -struct Config { - active_lists: Vec, - active_ipsets: Vec, - active_exclude_lists: Vec, - active_exclude_ipsets: Vec, - list_type: ListType, - strategy: String, - app_list: String, - whitelist: Vec, - blacklist: Vec, -} - -pub static MODULE_PATH: LazyLock<&Path> = LazyLock::new(|| Path::new("/data/adb/modules/zaprett")); -pub static ZAPRETT_DIR_PATH: LazyLock<&Path> = - LazyLock::new(|| Path::new("/storage/emulated/0/zaprett")); +use clap::Parser; +use log::info; +use zaprett::cli::CliApp; #[tokio::main] async fn main() -> anyhow::Result<()> { pretty_env_logger::init(); - let cli = Cli::parse(); - match &cli.cmd { - Some(Commands::Start) => return start_service().await, - Some(Commands::Stop) => { - let _ = stop_service().await; - }, - Some(Commands::Restart) => return restart_service().await, - Some(Commands::Status) => { - println!( - "zaprett is {}", - if service_status().await { - "working" - } else { - "stopped" - } - ); - }, - Some(Commands::SetAutostart { autostart }) => { - if let Err(err) = set_autostart(autostart).await { - error!("Failed to set auto start: {err}") - } - }, - Some(Commands::GetAutostart) => get_autostart(), - Some(Commands::ModuleVer) => module_version(), - Some(Commands::BinVer) => bin_version(), - None => info!("zaprett installed. Join us: t.me/zaprett_module"), + let cli = CliApp::parse(); + match &cli.cmd() { + Some(cmd) => cmd.exec().await?, + None => info!("zaprett installed. Join us: t.me/zaprett_module") } Ok(()) } - -async fn daemonize_nfqws(args: &str) { - info!("Starting nfqws as a daemon"); - let daemonize = Daemonize::new() - .pid_file(MODULE_PATH.join("tmp/pid.lock").as_path()) - .working_directory("/tmp") - .group("daemon") - .privileged_action(|| "Executed before drop privileges"); - - match daemonize.start() { - Ok(_) => { - info!("Success, daemonized"); - run_nfqws(args).await.unwrap() - } - Err(e) => error!("Error while starting nfqws daemon: {e}"), - } -} - -async fn start_service() -> anyhow::Result<()> { - if !Uid::effective().is_root() { - bail!("Running not from root, exiting"); - }; - - info!("Starting zaprett service..."); - - let tmp_dir = MODULE_PATH.join("/tmp"); - if tmp_dir.exists() { - fs::remove_dir_all(&tmp_dir).await?; - fs::create_dir_all(&tmp_dir).await?; - } - - let mut config_contents = String::new(); - File::open(ZAPRETT_DIR_PATH.join("config.json")) - .await - .expect("cannot open config.json") - .read_to_string(&mut config_contents).await?; - - let config: Config = serde_json::from_str(&config_contents).expect("invalid json"); - - let def_strat = String::from(" - --filter-tcp=80 --dpi-desync=fake,split2 --dpi-desync-autottl=2 --dpi-desync-fooling=md5sig,badsum $hostlist --new - --filter-tcp=443 $hostlist --dpi-desync=fake,split2 --dpi-desync-repeats=6 --dpi-desync-fooling=md5sig,badsum --dpi-desync-fake-tls=${zaprettdir}/bin/tls_clienthello_www_google_com.bin --new - --filter-tcp=80,443 --dpi-desync=fake,disorder2 --dpi-desync-repeats=6 --dpi-desync-autottl=2 --dpi-desync-fooling=md5sig,badsum $hostlist --new - --filter-udp=50000-50100 --dpi-desync=fake --dpi-desync-any-protocol --dpi-desync-fake-quic=0xC30000000108 --new - --filter-udp=443 $hostlist --dpi-desync=fake --dpi-desync-repeats=6 --dpi-desync-fake-quic=${zaprettdir}/bin/quic_initial_www_google_com.bin --new - --filter-udp=443 --dpi-desync=fake --dpi-desync-repeats=6 $hostlist - "); - - let start = fs::read_to_string(&config.strategy) - .await - .unwrap_or(def_strat); - - let regex_hostlist = Regex::new(r"\$hostlist")?; - let regex_ipsets = Regex::new(r"\$ipset")?; - let regex_zaprettdir = Regex::new(r"\$\{?zaprettdir}?")?; - - let mut strat_modified; - let (hosts, ipsets) = config.list_type.merge(&config).await; - - strat_modified = regex_hostlist.replace_all(&start, &hosts).into_owned(); - strat_modified = regex_ipsets - .replace_all(&strat_modified, &ipsets) - .into_owned(); - strat_modified = regex_zaprettdir - .replace_all(&strat_modified, ZAPRETT_DIR_PATH.to_str().unwrap()) - .into_owned(); - - let ctl = sysctl::Ctl::new("net.netfilter.nf_conntrack_tcp_be_liberal")?; - ctl.set_value(sysctl::CtlValue::String("1".into()))?; - - setup_iptables_rules().expect("setup iptables rules"); - - daemonize_nfqws(&strat_modified).await; - info!("zaprett service started!"); - Ok(()) -} - -async fn stop_service() -> anyhow::Result<()> { - if !Uid::effective().is_root() { - bail!("Running not from root, exiting"); - }; - - clear_iptables_rules().expect("clear iptables rules"); - - let pid_str = fs::read_to_string(MODULE_PATH.join("tmp/pid.lock")).await?; - let pid = pid_str.trim().parse::()?; - - kill(Pid::from_raw(pid), Signal::SIGKILL)?; - - Ok(()) -} - -async fn restart_service() -> anyhow::Result<()> { - stop_service().await?; - start_service().await?; - info!("zaprett service restarted!"); - Ok(()) -} - -async fn set_autostart(autostart: &bool) -> Result<(), anyhow::Error> { - if *autostart { - File::create(MODULE_PATH.join("autostart")).await?; - } else { - fs::remove_file(MODULE_PATH.join("autostart")).await?; - } - - Ok(()) -} - -fn get_autostart() { - let file = MODULE_PATH.join("autostart"); - println!("{}", file.exists()); -} - -async fn service_status() -> bool { - fs::read_to_string(MODULE_PATH.join("tmp/pid.lock")) - .await - .ok() - .and_then(|pid_str| pid_str.trim().parse::().ok()) - .is_some() -} - -fn module_version() { - if let Ok(prop) = Ini::load_from_file(MODULE_PATH.join("module.prop")) - && let Some(props) = prop.section::(None) - && let Some(version) = props.get("version") - { - println!("{version}"); - } -} - -fn bin_version() { - println!("{}", env!("ZAPRET_VERSION")); -} - -pub async fn merge_files( - input_paths: &[impl AsRef], - output_path: impl AsRef, -) -> Result<(), Box> { - let output_path = output_path.as_ref(); - let mut output_file = File::create(output_path).await?; - - for input in input_paths { - let input_path = input.as_ref(); - let mut input_file = File::open(input_path) - .await - .map_err(|e| format!("Failed to open {}: {}", input_path.display(), e))?; - - copy(&mut input_file, &mut output_file) - .await - .map_err(|e| { - format!( - "Failed to write contents of {}: {}", - input_path.display(), - e - ) - })?; - } - - output_file.flush().await?; - Ok(()) -} - -fn setup_iptables_rules() -> Result<(), Box> { - let ipt = iptables::new(false)?; - - ipt.insert( - "mangle", - "POSTROUTING", - "-j NFQUEUE --queue-num 200 --queue-bypass", - 1, - )?; - - ipt.insert( - "mangle", - "PREROUTING", - "-j NFQUEUE --queue-num 200 --queue-bypass", - 1, - )?; - - ipt.append( - "filter", - "FORWARD", - "-j NFQUEUE --queue-num 200 --queue-bypass", - )?; - - Ok(()) -} - -fn clear_iptables_rules() -> Result<(), Box> { - let ipt = iptables::new(false)?; - - ipt.delete( - "mangle", - "POSTROUTING", - "-j NFQUEUE --queue-num 200 --queue-bypass", - )?; - - ipt.delete( - "mangle", - "PREROUTING", - "-j NFQUEUE --queue-num 200 --queue-bypass", - )?; - - ipt.delete( - "filter", - "FORWARD", - "-j NFQUEUE --queue-num 200 --queue-bypass", - )?; - - Ok(()) -} - -async fn run_nfqws(args_str: &str) -> anyhow::Result<()> { - if service_status().await { - bail!("nfqws already started!"); - } - - let mut args = vec!["nfqws".to_string()]; - - if args_str.trim().is_empty() { - args.push("-v".to_string()); - } else { - args.extend(args_str.split_whitespace().map(String::from)); - } - - task::spawn_blocking(move || { - let c_args: Vec = args - .into_iter() - .map(|arg| CString::new(arg).unwrap()) - .collect(); - - let mut ptrs: Vec<*const c_char> = c_args.iter().map(|arg| arg.as_ptr()).collect(); - - unsafe { - nfqws_main(c_args.len() as libc::c_int, ptrs.as_mut_ptr() as *mut _); - } - }) - .await?; - - Ok(()) -}