summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/pkt.rs4
-rw-r--r--src/platform.rs2
-rw-r--r--src/platform/linux.rs116
-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
6 files changed, 211 insertions, 218 deletions
diff --git a/src/pkt.rs b/src/pkt.rs
index f51151d..5955f42 100644
--- a/src/pkt.rs
+++ b/src/pkt.rs
@@ -18,8 +18,6 @@
use anyhow::Result;
use etherparse::{IpSlice, TcpSlice};
use anyhow::anyhow;
-#[cfg(target_os = "linux")]
-use std::sync::atomic::Ordering;
use crate::opt;
use crate::platform;
@@ -236,7 +234,7 @@ pub fn put_hop(pkt: &[u8]) {
/// Return Ok(true) if packet is handled
pub fn handle_packet(pkt: &[u8], buf: &mut Vec::<u8>) -> Result<bool> {
#[cfg(target_os = "linux")]
- let is_filtered = platform::IS_U32_SUPPORTED.load(Ordering::Relaxed);
+ let is_filtered = platform::is_kernel_filtered_clienthello();
#[cfg(windows)]
let is_filtered = true;
diff --git a/src/platform.rs b/src/platform.rs
index 5b74eb9..1ec9b39 100644
--- a/src/platform.rs
+++ b/src/platform.rs
@@ -30,7 +30,7 @@ pub use windows::{bootstrap, run, local_time, send_to_raw};
pub mod linux;
#[cfg(target_os = "linux")]
-pub use linux::{bootstrap, run, local_time, send_to_raw, IS_U32_SUPPORTED};
+pub use linux::{bootstrap, run, local_time, send_to_raw, is_kernel_filtered_clienthello};
/// pause before exit on windows to print information in console before it is closed.
pub fn paexit(code: i32) {
diff --git a/src/platform/linux.rs b/src/platform/linux.rs
index 03dd94e..1db2e09 100644
--- a/src/platform/linux.rs
+++ b/src/platform/linux.rs
@@ -3,118 +3,25 @@
use std::{
os::fd::{AsRawFd, OwnedFd},
- sync::{LazyLock, atomic::AtomicBool}
+ sync::{LazyLock, atomic}
};
use std::fs::OpenOptions;
-use std::process::{Command, Stdio};
use std::io::Write;
-use anyhow::{Result, Context, anyhow};
+use anyhow::{Result, Context};
use socket2::{Domain, Protocol, Socket, Type};
-mod iptables;
-mod nftables;
+mod rules;
mod rxring;
#[macro_use] mod libc_s;
-use iptables::*;
-use nftables::*;
-
use crate::pkt;
use crate::opt;
-pub static IS_U32_SUPPORTED: AtomicBool = AtomicBool::new(false);
-
const INJECT_MARK: u32 = 0xD001;
const PID_FILE: &str = "/run/dpibreak.pid"; // TODO: unmagic this
const PKG_NAME: &str = env!("CARGO_PKG_NAME");
-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))
- }
-}
-
-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
-}
-
-fn install_rules() -> 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 {
- cleanup_nftables_rules().map_err(|e| crate::warn!("fail to cleanup nftables rules: {e}")).ok();
- }
- }
-}
-
fn lock_pid_file() -> Result<()> {
use libc_s::flock;
@@ -279,21 +186,16 @@ fn open_signalfd() -> Result<OwnedFd> {
}
}
-fn ipt6_cleanup(is_ipv6: bool) -> Result<()> {
- let ipt6 = IPTables::new(is_ipv6)?;
- ipt6.cleanup()
-}
-
pub fn run() -> Result<()> {
use crate::handle_packet;
use super::PACKET_SIZE_CAP;
// In case the previous execution was not cleaned properly
- _ = cleanup_nftables_rules();
- _ = ipt6_cleanup(false);
- _ = ipt6_cleanup(true);
+ _ = rules::nft_cleanup();
+ _ = rules::ipt6_cleanup(false);
+ _ = rules::ipt6_cleanup(true);
- let _rule = install_rules()?;
+ let _rule = rules::install()?;
let sfd = open_signalfd()?;
let mut q = open_nfqueue()?;
@@ -400,3 +302,7 @@ pub fn local_time() -> (i32, u8, u8, u8, u8, u8) {
tm.tm_hour as u8, tm.tm_min as u8, tm.tm_sec as u8)
}
}
+
+pub fn is_kernel_filtered_clienthello() -> bool {
+ rules::IS_U32_SUPPORTED.load(atomic::Ordering::Relaxed)
+}
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)?;