diff --git a/rust/crates/zaprett/src/cli.rs b/rust/crates/zaprett/src/cli.rs index fe4768d..8824e9a 100644 --- a/rust/crates/zaprett/src/cli.rs +++ b/rust/crates/zaprett/src/cli.rs @@ -1,7 +1,8 @@ +pub mod commands; + use clap::Parser; use getset::Getters; -use serde::{Deserialize, Serialize}; -use crate::commands::Command; +use commands::Command; #[derive(Parser, Getters)] #[command(version)] diff --git a/rust/crates/zaprett/src/commands.rs b/rust/crates/zaprett/src/cli/commands.rs similarity index 91% rename from rust/crates/zaprett/src/commands.rs rename to rust/crates/zaprett/src/cli/commands.rs index 289d381..f42a8e4 100644 --- a/rust/crates/zaprett/src/commands.rs +++ b/rust/crates/zaprett/src/cli/commands.rs @@ -1,6 +1,7 @@ -use crate::{bin_version, get_autostart, module_version, restart_service, service_status, set_autostart, start_service, stop_service}; +use crate::{bin_version, get_autostart, module_version, set_autostart}; use clap::Subcommand; use log::error; +use crate::service::{restart_service, service_status, start_service, stop_service}; #[derive(Subcommand)] pub enum Command { diff --git a/rust/crates/zaprett/src/config.rs b/rust/crates/zaprett/src/config.rs index 4667c15..a09d3e6 100644 --- a/rust/crates/zaprett/src/config.rs +++ b/rust/crates/zaprett/src/config.rs @@ -30,18 +30,20 @@ impl ListType { 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 { + let (host_files, ipset_files, host_suffix, ipset_suffix, exclude_flag) = 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", + "-exclude" ), }; @@ -50,16 +52,10 @@ impl ListType { 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}"), + format!("--hostlist{exclude_flag}={module_path_str}/tmp/{host_suffix}"), + format!("--ipset{exclude_flag}={module_path_str}/tmp/{ipset_suffix}"), ) } } diff --git a/rust/crates/zaprett/src/daemon.rs b/rust/crates/zaprett/src/daemon.rs new file mode 100644 index 0000000..db958a3 --- /dev/null +++ b/rust/crates/zaprett/src/daemon.rs @@ -0,0 +1,20 @@ +use log::{error, info}; +use daemonize::Daemonize; +use crate::{run_nfqws, MODULE_PATH}; + +pub 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}"), + } +} diff --git a/rust/crates/zaprett/src/iptables_rust.rs b/rust/crates/zaprett/src/iptables_rust.rs new file mode 100644 index 0000000..7eb9b09 --- /dev/null +++ b/rust/crates/zaprett/src/iptables_rust.rs @@ -0,0 +1,51 @@ +use std::error; + +pub 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(()) +} + +pub 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(()) +} diff --git a/rust/crates/zaprett/src/lib.rs b/rust/crates/zaprett/src/lib.rs index 8d6dc6b..bb11f3a 100644 --- a/rust/crates/zaprett/src/lib.rs +++ b/rust/crates/zaprett/src/lib.rs @@ -1,6 +1,8 @@ -pub mod commands; pub mod cli; pub mod config; +pub mod iptables_rust; +mod service; +mod daemon; use std::error; use std::ffi::CString; @@ -8,126 +10,32 @@ 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 tokio::io::{copy, 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(" +pub static DEFAULT_START: &str = " --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> { + let autostart_path = MODULE_PATH.join("autostart"); + if *autostart { - File::create(MODULE_PATH.join("autostart")).await?; + File::create(autostart_path).await?; } else { - fs::remove_file(MODULE_PATH.join("autostart")).await?; + fs::remove_file(autostart_path).await?; } Ok(()) @@ -138,14 +46,6 @@ fn get_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) @@ -187,58 +87,8 @@ pub async fn merge_files( 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 { + if service::service_status().await { bail!("nfqws already started!"); } diff --git a/rust/crates/zaprett/src/service.rs b/rust/crates/zaprett/src/service.rs new file mode 100644 index 0000000..278479e --- /dev/null +++ b/rust/crates/zaprett/src/service.rs @@ -0,0 +1,96 @@ +use std::borrow::Cow; +use nix::unistd::{Pid, Uid}; +use anyhow::bail; +use tokio::fs; +use nix::sys::signal::{kill, Signal}; +use log::info; +use tokio::fs::File; +use regex::Regex; +use tokio::io::AsyncReadExt; +use sysctl::Sysctl; +use crate::iptables_rust::{clear_iptables_rules, setup_iptables_rules}; +use crate::{DEFAULT_START, MODULE_PATH, ZAPRETT_DIR_PATH}; +use crate::config::Config; +use crate::daemon::daemonize_nfqws; + +pub 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 start = fs::read_to_string(config.strategy()) + .await + .map(Cow::Owned) + .unwrap_or(Cow::Borrowed(DEFAULT_START)); + + 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(()) +} + +pub 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(()) +} + +pub async fn restart_service() -> anyhow::Result<()> { + stop_service().await?; + start_service().await?; + info!("zaprett service restarted!"); + Ok(()) +} + +pub 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() +}