diff --git a/rust/crates/zaprett/src/cli.rs b/rust/crates/zaprett/src/cli.rs index ec85ea7..b928272 100644 --- a/rust/crates/zaprett/src/cli.rs +++ b/rust/crates/zaprett/src/cli.rs @@ -5,7 +5,7 @@ use commands::Command; use getset::Getters; #[derive(Parser, Getters)] -#[command(version = option_env!("MODULE_VERSION").unwrap_or("unknown"))] +#[command(version = option_env!("MODULE_VERSION").unwrap_or(env!("CARGO_PKG_VERSION")))] #[getset(get = "pub")] pub struct CliApp { #[command(subcommand)] diff --git a/rust/crates/zaprett/src/config.rs b/rust/crates/zaprett/src/config.rs index 22bde3b..8794f6c 100644 --- a/rust/crates/zaprett/src/config.rs +++ b/rust/crates/zaprett/src/config.rs @@ -1,5 +1,5 @@ use std::path::{Path, PathBuf}; -use crate::{check_manifest, merge_files}; +use crate::{get_manifest, merge_files}; use getset::Getters; use serde::{Deserialize, Serialize}; use crate::path::path::MODULE_PATH; @@ -82,13 +82,13 @@ impl ListType { }; let host_paths: Vec = host_files.iter() .map(|path| -> anyhow::Result { - let manifest = check_manifest(Path::new(path))?; + let manifest = get_manifest(Path::new(path))?; Ok(PathBuf::from(manifest.file())) }).collect::>()?; let ipset_paths: Vec = ipset_files .iter() .map(|path| -> anyhow::Result { - let manifest = check_manifest(Path::new(path))?; + let manifest = get_manifest(Path::new(path))?; Ok(PathBuf::from(manifest.file())) }) .collect::>()?; diff --git a/rust/crates/zaprett/src/lib.rs b/rust/crates/zaprett/src/lib.rs index ed6d6cc..dd8be99 100644 --- a/rust/crates/zaprett/src/lib.rs +++ b/rust/crates/zaprett/src/lib.rs @@ -5,6 +5,7 @@ pub mod iptables_rust; mod service; mod autostart; mod path; +mod strategy; use crate::config::Manifest; use anyhow::{anyhow, Context}; @@ -19,12 +20,12 @@ use tokio::io::{copy, AsyncWriteExt}; pub static DEFAULT_STRATEGY_NFQWS: &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-tcp=80 --dpi-desync=fake,split2 --dpi-desync-autottl=2 --dpi-desync-fooling=md5sig,badsum ${hostlists} --new + --filter-tcp=443 ${hostlists} --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 ${hostlists} --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} + --filter-udp=443 ${hostlists} --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 ${hostlists} "; // тестовая стратегия, заменить на нормальную потом pub static DEFAULT_STRATEGY_NFQWS2: &str = " @@ -73,7 +74,7 @@ pub async fn merge_files( Ok(()) } -pub fn get_manifest(path: &Path) -> anyhow::Result { +pub fn read_manifest(path: &Path) -> anyhow::Result { let content = fs::read_to_string(path)?; Ok(serde_json::from_str(&content)?) } @@ -81,7 +82,7 @@ pub fn get_manifest(path: &Path) -> anyhow::Result { pub fn check_dependencies(manifest: &Manifest) -> anyhow::Result<()> { manifest.dependencies().iter().try_for_each(|dependency| { let path = Path::new(dependency); - let manifest = get_manifest(&path).with_context( + let manifest = read_manifest(&path).with_context( || format!("Failed to check dependency: {}", dependency) )?; check_file(&manifest) @@ -96,13 +97,20 @@ pub fn check_file(manifest: &Manifest) -> anyhow::Result<()> { } } -pub fn check_manifest(path: &Path) -> anyhow::Result { - let manifest = get_manifest(path)?; +pub fn get_manifest(path: &Path) -> anyhow::Result { + let manifest = read_manifest(path)?; check_file(&manifest)?; check_dependencies(&manifest)?; Ok(manifest) } +pub fn get_all_manifests(path: &Path) -> anyhow::Result> { + path.read_dir()?.map( + |manifest_path| { + get_manifest(&manifest_path?.path()) + } + ).collect() +} fn run_nfqws(args_str: &str) -> anyhow::Result<()> { let mut args = vec![ "nfqws".to_string(), diff --git a/rust/crates/zaprett/src/service.rs b/rust/crates/zaprett/src/service.rs index 8f098aa..0586c4b 100644 --- a/rust/crates/zaprett/src/service.rs +++ b/rust/crates/zaprett/src/service.rs @@ -1,14 +1,15 @@ -use crate::config::{Config, ServiceType}; +use crate::config::{Config, Manifest, ServiceType}; use crate::daemon::daemonize_nfqws; use crate::daemon::daemonize_nfqws2; use crate::iptables_rust::{clear_iptables_rules, setup_iptables_rules}; -use crate::{check_manifest, DEFAULT_STRATEGY_NFQWS, DEFAULT_STRATEGY_NFQWS2}; +use crate::{get_manifest, get_all_manifests, DEFAULT_STRATEGY_NFQWS, DEFAULT_STRATEGY_NFQWS2}; use anyhow::bail; use log::info; use nix::sys::signal::{Signal, kill}; use nix::unistd::{Pid, Uid}; use regex::Regex; use std::borrow::Cow; +use std::collections::{HashMap}; use std::io::ErrorKind; use std::path::Path; use sysctl::{Ctl, CtlValue, Sysctl}; @@ -16,6 +17,7 @@ use sysinfo::{Pid as SysPid, System}; use tokio::fs; use tokio::io::AsyncReadExt; use crate::path::path::{MODULE_PATH, ZAPRETT_DIR_PATH, ZAPRETT_LIBS_PATH}; +use crate::strategy::prepare_manifests; pub async fn start_service() -> anyhow::Result<()> { if !Uid::effective().is_root() { @@ -54,7 +56,7 @@ pub async fn start_service() -> anyhow::Result<()> { } let config: Config = serde_json::from_str(&config_contents)?; - let strategy = check_manifest(Path::new(config.strategy())).ok(); + let strategy = get_manifest(Path::new(config.strategy())).ok(); let default_strategy = match config.service_type() { ServiceType::Nfqws => DEFAULT_STRATEGY_NFQWS, ServiceType::Nfqws2 => DEFAULT_STRATEGY_NFQWS2 @@ -67,26 +69,50 @@ pub async fn start_service() -> anyhow::Result<()> { } else { Cow::Borrowed(default_strategy) }; - let regex_hostlist = Regex::new(r"\$(?:hostlist|\{hostlist})")?; - let regex_ipsets = Regex::new(r"\$(?:ipset|\{ipset})")?; + let regex_hostlists = Regex::new(r"\$(?:hostlists|\{hostlists})")?; + let regex_hostlist = Regex::new(r"\$\{hostlist:([^}]+)\}")?; + let regex_hostlist_exclude = Regex::new(r"\$\{hostlist_exclude:([^}]+)\}")?; + let regex_ipset = Regex::new(r"\$\{ipset:([^}]+)\}")?; + let regex_ipset_exclude = Regex::new(r"\$\{ipset_exclude:([^}]+)\}")?; + let regex_ipsets = Regex::new(r"\$(?:ipsets|\{ipsets})")?; let regex_zaprettdir = Regex::new(r"\$(?:zaprettdir|\{zaprettdir})")?; let regex_libsdir = Regex::new(r"\$(?:libsdir|\{libsdir})")?; - - 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(); - - strat_modified = regex_libsdir - .replace_all(&strat_modified, ZAPRETT_LIBS_PATH.to_str().unwrap()) - .into_owned(); + let hostlists: HashMap = + get_all_manifests(&ZAPRETT_DIR_PATH.join("manifests/lists/include")) + .unwrap_or_default() + .into_iter() + .map(|m| (m.name().clone(), m)) + .collect(); + let hostlists_exclude: HashMap = + get_all_manifests(&ZAPRETT_DIR_PATH.join("manifests/lists/exclude")) + .unwrap_or_default() + .into_iter() + .map(|m| (m.name().clone(), m)) + .collect(); + let ipset: HashMap = + get_all_manifests(&ZAPRETT_DIR_PATH.join("manifests/ipset/include")) + .unwrap_or_default() + .into_iter() + .map(|m| (m.name().clone(), m)) + .collect(); + let ipset_exclude: HashMap = + get_all_manifests(&ZAPRETT_DIR_PATH.join("manifests/ipset/exclude")) + .unwrap_or_default() + .into_iter() + .map(|m| (m.name().clone(), m)) + .collect(); + let strat_modified = prepare_manifests(&start, ®ex_hostlist, &hostlists, &tmp_dir)?; + let strat_modified = prepare_manifests(&strat_modified, ®ex_hostlist_exclude, &hostlists_exclude, &tmp_dir)?; + let strat_modified = prepare_manifests(&strat_modified, ®ex_ipset, &ipset, &tmp_dir)?; + let strat_modified = prepare_manifests(&strat_modified, ®ex_ipset_exclude, &ipset_exclude, &tmp_dir)?; + let strat_modified = regex_hostlists.replace_all(&strat_modified, &hosts); + let strat_modified = regex_ipsets.replace_all(&strat_modified, &ipsets); + let strat_modified = + regex_zaprettdir.replace_all(&strat_modified, ZAPRETT_DIR_PATH.to_str().unwrap()); + let strat_modified = + regex_libsdir.replace_all(&strat_modified, ZAPRETT_LIBS_PATH.to_str().unwrap()); + let strat_modified = strat_modified.into_owned(); let ctl = Ctl::new("net.netfilter.nf_conntrack_tcp_be_liberal")?; ctl.set_value(CtlValue::String("1".into()))?; diff --git a/rust/crates/zaprett/src/strategy.rs b/rust/crates/zaprett/src/strategy.rs new file mode 100644 index 0000000..08896f5 --- /dev/null +++ b/rust/crates/zaprett/src/strategy.rs @@ -0,0 +1,26 @@ +use std::collections::{HashMap, HashSet}; +use std::path::{Path, PathBuf}; +use anyhow::bail; +use regex::Regex; +use crate::config::Manifest; + +pub fn prepare_manifests(input: &str, regex: &Regex, manifests: &HashMap, tmp_dir: &Path) -> anyhow::Result { + let required: HashSet = regex.captures_iter(input).map(|c| c[1].to_string()).collect(); + for name in &required { + if !manifests.contains_key(name) { + bail!("Manifest not found: {}", name) + } + } + let mut paths: HashMap = HashMap::new(); + for name in &required { + let manifest = &manifests[name]; + let path = Path::new(manifest.file()); + let dst = tmp_dir.join(format!("{}.txt", name)); + std::fs::copy(path, &dst)?; + paths.insert(name.clone(), dst); + } + let result = regex.replace_all(input, |caps: ®ex::Captures| { + paths[&caps[1]].to_string_lossy().into_owned() + }); + Ok(result.into_owned()) +} \ No newline at end of file