diff options
Diffstat (limited to 'src/platform/linux')
| -rw-r--r-- | src/platform/linux/nftables.rs | 39 | ||||
| -rw-r--r-- | src/platform/linux/rules.rs | 191 | ||||
| -rw-r--r-- | src/platform/linux/rules/iptables.rs (renamed from src/platform/linux/iptables.rs) | 77 |
3 files changed, 198 insertions, 109 deletions
diff --git a/src/platform/linux/nftables.rs b/src/platform/linux/nftables.rs deleted file mode 100644 index 4809000..0000000 --- a/src/platform/linux/nftables.rs +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Dilluti0n <[email protected]> -// SPDX-License-Identifier: GPL-3.0-or-later - -use std::sync::atomic::Ordering; -use anyhow::Result; - -use crate::opt; -use super::{exec_process, INJECT_MARK, IS_U32_SUPPORTED}; - -const DPIBREAK_TABLE: &str = "dpibreak"; - -/// Apply nft rules with `nft_command() -f -`. -fn nft(rule: &str) -> Result<()> { - crate::info!("nft: {rule}"); - exec_process(&[opt::nft_command(), "-f", "-"], Some(rule)) -} - -pub fn install_nft_rules() -> Result<()> { - let queue_num = opt::queue_num(); - let rule = format!( - r#"add table inet {DPIBREAK_TABLE} -add chain inet {DPIBREAK_TABLE} OUTPUT {{ type filter hook output priority 0; policy accept; }} -add rule inet {DPIBREAK_TABLE} OUTPUT meta mark {INJECT_MARK} return -add rule inet {DPIBREAK_TABLE} OUTPUT tcp dport 443 @ih,0,8 0x16 @ih,40,8 0x01 queue num {queue_num} bypass"# - ); - nft(&rule)?; - - // clienthello filtered by nft - IS_U32_SUPPORTED.store(true, Ordering::Relaxed); - - Ok(()) -} - -pub fn cleanup_nftables_rules() -> Result<()> { - let rule = format!("delete table inet {DPIBREAK_TABLE}"); - nft(&rule)?; - - Ok(()) -} diff --git a/src/platform/linux/rules.rs b/src/platform/linux/rules.rs new file mode 100644 index 0000000..7dcdc36 --- /dev/null +++ b/src/platform/linux/rules.rs @@ -0,0 +1,191 @@ +// SPDX-FileCopyrightText: 2026 Dilluti0n <[email protected]> +// SPDX-License-Identifier: GPL-3.0-or-later + +use std::sync::atomic; +use std::process::{Command, Stdio}; +use std::io::Write; +use anyhow::{Result, Context, anyhow}; + +mod iptables; + +use iptables::{IPTables, cleanup_xt_u32}; + +use crate::opt; +use super::INJECT_MARK; + +const DPIBREAK_CHAIN: &str = "DPIBREAK"; +const DPIBREAK_TABLE: &str = "dpibreak"; +pub static IS_U32_SUPPORTED: atomic::AtomicBool = atomic::AtomicBool::new(false); + +fn exec_process(args: &[&str], input: Option<&str>) -> Result<()> { + if args.is_empty() { + return Err(anyhow!("command args cannot be empty")); + } + + let program = args[0]; + let stdin_mode = if input.is_some() { Stdio::piped() } else { Stdio::null() }; + + let mut child = Command::new(program) + .args(&args[1..]) + .stdin(stdin_mode) + .stdout(Stdio::null()) + .stderr(Stdio::piped()) + .spawn() + .with_context(|| format!("failed to spawn {}", program))?; + + if let Some(data) = input { + if let Some(mut stdin) = child.stdin.take() { + stdin.write_all(data.as_bytes()) + .with_context(|| format!("failed to write input to {}", program))?; + } + } + + let output = child.wait_with_output() + .with_context(|| format!("failed to wait for {}", program))?; + + match output.status.code() { + Some(0) => Ok(()), + Some(code) => Err(anyhow!("{} exited with status {}: {}", program, code, + String::from_utf8_lossy(&output.stderr))), + None => Err(anyhow!("{} terminated by signal", program)) + } +} + +/// Apply nft rules with `nft_command() -f -`. +fn nft(rule: &str) -> Result<()> { + crate::info!("nft: {rule}"); + exec_process(&[opt::nft_command(), "-f", "-"], Some(rule)) +} + +pub struct InstalledRules { + is_nft_not_supported: bool, + ipt: Option<IPTables>, + ip6: Option<IPTables> +} + +fn install_ipt6(is_ipv6: bool) -> Option<IPTables> { + let ipt = IPTables::new(is_ipv6).map_err(|e| crate::warn!("iptables: {e}")).ok(); + if let Some(ref ipt) = ipt { + ipt.install().map_err(|e| crate::warn!("iptables: {e}")).ok(); + } + + ipt +} + +pub fn install() -> Result<InstalledRules> { + let mut is_nft_not_supported = false; + let mut ipt = None; + let mut ip6 = None; + + if let Err(e) = install_nft_rules() { + is_nft_not_supported = true; + crate::warn!("nftables: {}", e.to_string()); + crate::warn!("fallback to iptables"); + + ipt = install_ipt6(false); + ip6 = install_ipt6(true); + } + + Ok(InstalledRules{ + is_nft_not_supported, + ipt, + ip6 + }) +} + +impl Drop for InstalledRules { + fn drop(&mut self) { + if self.is_nft_not_supported { + if let Some(ipt) = &self.ipt { + ipt.cleanup().map_err(|e| crate::warn!("fail to cleanup iptables rules: {e}")).ok(); + } + if let Some(ipt) = &self.ip6 { + ipt.cleanup().map_err(|e| crate::warn!("fail to cleanup ip6tables rules: {e}")).ok(); + } + cleanup_xt_u32().map_err(|e| crate::warn!("fail to cleanup xt_u32: {e}")).ok(); + } else { + nft_cleanup().map_err(|e| crate::warn!("fail to cleanup nftables rules: {e}")).ok(); + } + } +} + +pub fn ipt6_cleanup(is_ipv6: bool) -> Result<()> { + let ipt6 = IPTables::new(is_ipv6)?; + ipt6.cleanup() +} + +pub fn nft_cleanup() -> Result<()> { + let rule = format!("delete table inet {DPIBREAK_TABLE}"); + nft(&rule)?; + + Ok(()) +} + +fn install_nft_rules() -> Result<()> { + let queue_num = opt::queue_num(); + let rule = format!( + r#"add table inet {DPIBREAK_TABLE} +add chain inet {DPIBREAK_TABLE} OUTPUT {{ type filter hook output priority 0; policy accept; }} +add rule inet {DPIBREAK_TABLE} OUTPUT meta mark {INJECT_MARK} return +add rule inet {DPIBREAK_TABLE} OUTPUT tcp dport 443 @ih,0,8 0x16 @ih,40,8 0x01 queue num {queue_num} bypass"# + ); + nft(&rule)?; + + // clienthello filtered by nft + IS_U32_SUPPORTED.store(true, atomic::Ordering::Relaxed); + + Ok(()) +} + +impl IPTables { + fn install(&self) -> Result<()> { + let q_num = crate::opt::queue_num().to_string(); + // prevent inf loop + let mark = format!("{:#x}", INJECT_MARK); + + let mut rule = vec![ + "-p", "tcp", "--dport", "443", + "-j", "NFQUEUE", "--queue-num", &q_num, "--queue-bypass" + ]; + + if iptables::is_u32_supported(self) { + const U32: &str = "0>>22&0x3C @ 12>>26&0x3C @ 0>>24&0xFF=0x16 && \ + 0>>22&0x3C @ 12>>26&0x3C @ 2>>24&0xFF=0x01"; + + rule.extend_from_slice(&["-m", "u32", "--u32", U32]); + } + + self.new_chain("mangle", DPIBREAK_CHAIN)?; + + self.insert( + "mangle", + DPIBREAK_CHAIN, + &["-m", "mark", "--mark", &mark, "-j", "RETURN"], + 1 + )?; + + self.append("mangle", DPIBREAK_CHAIN, &rule)?; + crate::info!("{}: new chain {} on table mangle", self.cmd(), DPIBREAK_CHAIN); + + self.insert("mangle", "POSTROUTING", &["-j", DPIBREAK_CHAIN], 1)?; + crate::info!("{}: add jump to {} chain on POSTROUTING", self.cmd(), DPIBREAK_CHAIN); + + Ok(()) + } + + fn cleanup(&self) -> Result<()> { + if self.delete("mangle", "POSTROUTING", &["-j", DPIBREAK_CHAIN]).is_ok() { + crate::info!("{}: delete jump to {} from mangle/POSTROUTING", self.cmd(), DPIBREAK_CHAIN); + } + + if self.flush_chain("mangle", DPIBREAK_CHAIN).is_ok() { + crate::info!("{}: flush chain {}", self.cmd(), DPIBREAK_CHAIN); + } + + if self.delete_chain("mangle", DPIBREAK_CHAIN).is_ok() { + crate::info!("{}: delete chain {}", self.cmd(), DPIBREAK_CHAIN); + } + + Ok(()) + } +} diff --git a/src/platform/linux/iptables.rs b/src/platform/linux/rules/iptables.rs index af8957f..c8eed2d 100644 --- a/src/platform/linux/iptables.rs +++ b/src/platform/linux/rules/iptables.rs @@ -1,18 +1,16 @@ // SPDX-FileCopyrightText: 2026 Dilluti0n <[email protected]> // SPDX-License-Identifier: GPL-3.0-or-later -use anyhow::{Result, Error}; +use anyhow::{Result}; use std::sync::{ atomic::{AtomicBool, Ordering} }; -use super::{exec_process, INJECT_MARK, IS_U32_SUPPORTED}; +use super::{exec_process, IS_U32_SUPPORTED}; static IS_XT_U32_LOADED_BY_US: AtomicBool = AtomicBool::new(false); -const DPIBREAK_CHAIN: &str = "DPIBREAK"; - pub struct IPTables { cmd: &'static str, } @@ -63,6 +61,10 @@ impl IPTables { args.extend_from_slice(rule); self.run(&args) } + + pub fn cmd(&self) -> &'static str { + self.cmd + } } fn is_xt_u32_loaded() -> bool { @@ -82,7 +84,7 @@ fn ensure_xt_u32() -> Result<()> { Ok(()) } -fn is_u32_supported(ipt: &IPTables) -> bool { +pub fn is_u32_supported(ipt: &IPTables) -> bool { if IS_U32_SUPPORTED.load(Ordering::Relaxed) { return true; } @@ -107,71 +109,6 @@ fn is_u32_supported(ipt: &IPTables) -> bool { } } -pub fn iptables_err(e: impl ToString) -> Error { - Error::msg(format!("iptables: {}", e.to_string())) -} - -pub fn install_iptables_rules(ipt: &IPTables) -> Result<()> { - let q_num = crate::opt::queue_num().to_string(); - // prevent inf loop - let mark = format!("{:#x}", INJECT_MARK); - - let mut rule = vec![ - "-p", "tcp", "--dport", "443", - "-j", "NFQUEUE", "--queue-num", &q_num, "--queue-bypass" - ]; - - if is_u32_supported(ipt) { - const U32: &str = "0>>22&0x3C @ 12>>26&0x3C @ 0>>24&0xFF=0x16 && \ - 0>>22&0x3C @ 12>>26&0x3C @ 2>>24&0xFF=0x01"; - - rule.extend_from_slice(&["-m", "u32", "--u32", U32]); - } - - ipt.new_chain("mangle", DPIBREAK_CHAIN).map_err(iptables_err)?; - - ipt.insert( - "mangle", - DPIBREAK_CHAIN, - &["-m", "mark", "--mark", &mark, "-j", "RETURN"], - 1 - ).map_err(iptables_err)?; - - ipt.append("mangle", DPIBREAK_CHAIN, &rule).map_err(iptables_err)?; - crate::info!("{}: new chain {} on table mangle", ipt.cmd, DPIBREAK_CHAIN); - - ipt.insert("mangle", "POSTROUTING", &["-j", DPIBREAK_CHAIN], 1).map_err(iptables_err)?; - crate::info!("{}: add jump to {} chain on POSTROUTING", ipt.cmd, DPIBREAK_CHAIN); - - Ok(()) -} - -pub fn cleanup_iptables_rules(ipt: &IPTables) -> Result<()> { - if ipt.delete("mangle", "POSTROUTING", &["-j", DPIBREAK_CHAIN]).is_ok() { - crate::info!("{}: delete jump to {} from mangle/POSTROUTING", ipt.cmd, DPIBREAK_CHAIN); - } - - if ipt.flush_chain("mangle", DPIBREAK_CHAIN).is_ok() { - crate::info!("{}: flush chain {}", ipt.cmd, DPIBREAK_CHAIN); - } - - if ipt.delete_chain("mangle", DPIBREAK_CHAIN).is_ok() { - crate::info!("{}: delete chain {}", ipt.cmd, DPIBREAK_CHAIN); - } - - Ok(()) -} - -impl IPTables { - pub fn install(&self) -> Result<()> { - install_iptables_rules(&self) - } - - pub fn cleanup(&self) -> Result<()> { - cleanup_iptables_rules(&self) - } -} - pub fn cleanup_xt_u32() -> Result<()> { if IS_XT_U32_LOADED_BY_US.load(Ordering::Relaxed) { exec_process(&["modprobe", "-q", "-r", "xt_u32"], None)?; |
