diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.rs | 33 | ||||
| -rw-r--r-- | src/platform/linux.rs | 165 |
2 files changed, 173 insertions, 25 deletions
diff --git a/src/main.rs b/src/main.rs index 12bd303..e41888f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -83,10 +83,31 @@ fn split_packet(pkt: &pkt::PktView, start: u32, end: Option<u32>, Ok(()) } -/// Return Ok(true) if packet is handled -fn handle_packet(pkt: &[u8], buf: &mut Vec::<u8>) -> Result<bool> { +fn split_packet_1(view: &pkt::PktView, order: &[u32], buf: &mut Vec<u8>) -> Result<()> { use platform::send_to_raw; + let mut it = order.iter().copied(); + + let Some(mut first) = it.next() else { + return Err(anyhow!("split_packet_1: invalid order array")); + }; + + for next in it { + split_packet(view, first, Some(next), buf)?; + send_to_raw(buf)?; + std::thread::sleep(std::time::Duration::from_millis(delay_ms())); + first = next; + } + + split_packet(view, first, None, buf)?; + send_to_raw(buf)?; + + Ok(()) +} + + +/// Return Ok(true) if packet is handled +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); @@ -102,13 +123,7 @@ fn handle_packet(pkt: &[u8], buf: &mut Vec::<u8>) -> Result<bool> { // TODO: if clienthello packet has been (unlikely) fragmented, // we should find the second part and drop, reassemble it here. - split_packet(&view, 0, Some(1), buf)?; - send_to_raw(&buf)?; - - std::thread::sleep(std::time::Duration::from_millis(delay_ms())); - - split_packet(&view, 1, None, buf)?; - send_to_raw(&buf)?; + split_packet_1(&view, &[0, 1], buf)?; #[cfg(debug_assertions)] log_println!(LogLevel::Debug, "packet is handled, len={}", pkt.len()); diff --git a/src/platform/linux.rs b/src/platform/linux.rs index 0faef31..e04a6e4 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -28,6 +28,7 @@ use crate::{log::LogLevel, log_println, splash, MESSAGE_AT_RUN}; pub static IS_U32_SUPPORTED: AtomicBool = AtomicBool::new(false); pub static IS_XT_U32_LOADED_BY_US: AtomicBool = AtomicBool::new(false); +static IS_NFT_NOT_SUPPORTED: AtomicBool = AtomicBool::new(false); pub static QUEUE_NUM: OnceLock<u16> = OnceLock::new(); @@ -83,7 +84,7 @@ fn iptables_err(e: impl ToString) -> Error { Error::msg(format!("iptables: {}", e.to_string())) } -fn install_rules(ipt: &IPTables) -> Result<()> { +fn install_iptables_rules(ipt: &IPTables) -> Result<()> { let base = format!("-p tcp --dport 443 -j NFQUEUE --queue-num {} --queue-bypass", queue_num()); let rule = if is_u32_supported(ipt) { @@ -107,7 +108,7 @@ fn install_rules(ipt: &IPTables) -> Result<()> { Ok(()) } -fn cleanup_rules(ipt: &IPTables) -> Result<()> { +fn cleanup_iptables_rules(ipt: &IPTables) -> Result<()> { if ipt.delete("mangle", "POSTROUTING", &format!("-j {}", DPIBREAK_CHAIN)).is_ok() { log_println!(LogLevel::Info, "{}: deleted jump from POSTROUTING", ipt.cmd); } @@ -123,12 +124,151 @@ fn cleanup_rules(ipt: &IPTables) -> Result<()> { Ok(()) } -pub fn cleanup() -> Result<()> { - let ipt = iptables::new(false).map_err(iptables_err)?; - let ip6 = iptables::new(true).map_err(iptables_err)?; +const DPIBREAK_TABLE: &str = "dpibreak"; + +fn install_nft_rules() -> Result<()> { + use nftables::helper; + + let json = serde_json::json!( + { + "nftables": [ + {"add": {"table": {"family": "inet", "name": DPIBREAK_TABLE}}}, + { + "add": { + "chain": { + "family": "inet", + "table": DPIBREAK_TABLE, + "name": "OUTPUT", + "type": "filter", + "hook": "output", + "prio": 0, + "policy": "accept", + } + } + }, + { + "add": { + "chain": { + "family": "inet", + "table": DPIBREAK_TABLE, + "name": DPIBREAK_CHAIN + } + } + }, + { + "add": { + "rule": { + "family": "inet", + "table": DPIBREAK_TABLE, + "chain": "OUTPUT", + "expr": [{ "jump": { "target": DPIBREAK_CHAIN }}] + } + } + }, + { + "add": { + "rule": { + "family": "inet", + "table": DPIBREAK_TABLE, + "chain": DPIBREAK_CHAIN, + "expr": [ + { + "match": { + "left": {"payload": { "protocol": "tcp", "field": "dport" }}, + "op": "==", + "right": 443 + } + }, + // TLS ContentType == 0x16 (Handshake) + { + "match": { + "left": { "payload": { "base": "ih", "offset": 0, "len": 8 } }, + "op": "==", + "right": 0x16 + } + }, + // HandshakeType == 0x01 (ClientHello) + { + "match": { + // Note: offset and len are both "bit" unit not byte + "left": { "payload": { "base": "ih", "offset": 40, "len": 8 } }, + "op": "==", + "right": 0x01 + } + }, + { + "queue": { + "num": queue_num(), + "flags": [ "bypass" ] + } + } + ] + } + } + } + ] + } + ); + + let json_str = serde_json::to_string(&json)?; + + helper::apply_ruleset_raw(&json_str, helper::DEFAULT_NFT, + helper::DEFAULT_ARGS)?; - cleanup_rules(&ipt)?; - cleanup_rules(&ip6)?; + // clienthello filtered by nft + IS_U32_SUPPORTED.store(true, Ordering::Relaxed); + log_println!(LogLevel::Info, "nftables: create table inet {DPIBREAK_TABLE}"); + + Ok(()) +} + +fn install_rules() -> Result<()> { + match install_nft_rules() { + Ok(_) => {}, + Err(e) => { + IS_NFT_NOT_SUPPORTED.store(true, Ordering::Relaxed); + log_println!(LogLevel::Warning, "nftables: {}", e.to_string()); + log_println!(LogLevel::Warning, "fallback to iptables"); + + let ipt = iptables::new(false).map_err(iptables_err)?; + let ip6 = iptables::new(true).map_err(iptables_err)?; + + install_iptables_rules(&ipt)?; + // FIXME: using xt_u32 on ipv6 is not supported; (even if it does, + // the rule should be different) + install_iptables_rules(&ip6)?; + } + } + + Ok(()) +} + +fn cleanup_rules() -> Result<()> { + if IS_NFT_NOT_SUPPORTED.load(Ordering::Relaxed) { + let ipt = iptables::new(false).map_err(iptables_err)?; + let ip6 = iptables::new(true).map_err(iptables_err)?; + + cleanup_iptables_rules(&ipt)?; + cleanup_iptables_rules(&ip6)?; + } else { + use nftables::*; + + let mut nft = batch::Batch::new(); + + // nft delete table inet dpibreak + nft.delete(schema::NfListObject::Table(schema::Table { + family: types::NfFamily::INet, + name: DPIBREAK_TABLE.into(), + ..Default::default() + })); + _ = helper::apply_ruleset(&nft.to_nftables()); + } + + Ok(()) +} + +pub fn cleanup() -> Result<()> { + cleanup_rules()?; if IS_XT_U32_LOADED_BY_US.load(Ordering::Relaxed) { _ = Command::new("modprobe").args(&["-q", "-r", "xt_u32"]).status(); @@ -139,15 +279,8 @@ pub fn cleanup() -> Result<()> { } pub fn bootstrap() -> Result<()> { - let ipt = iptables::new(false).map_err(iptables_err)?; - let ip6 = iptables::new(true).map_err(iptables_err)?; - - cleanup().ok(); - install_rules(&ipt)?; - // FIXME: using xt_u32 on ipv6 is not supported; (even if it does, - // the rule should be different) - install_rules(&ip6)?; - Ok(()) + _ = cleanup(); // In case the previous execution was not cleaned properly + install_rules() } use socket2::{Domain, Protocol, Socket, Type}; |
