summaryrefslogtreecommitdiffhomepage
path: root/src/platform/linux
diff options
context:
space:
mode:
Diffstat (limited to 'src/platform/linux')
-rw-r--r--src/platform/linux/nftables.rs39
-rw-r--r--src/platform/linux/rules.rs191
-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)?;