// SPDX-FileCopyrightText: 2026 Dilluti0n // SPDX-License-Identifier: GPL-3.0-or-later use std::sync::atomic::Ordering; use anyhow::Result; use crate::{log::LogLevel, log_println, opt}; use super::{exec_process, INJECT_MARK, IS_U32_SUPPORTED}; const DPIBREAK_TABLE: &str = "dpibreak"; /// Apply json format nft rules with `nft_command() -j -f -`. fn apply_nft_rules(rule: &str) -> Result<()> { exec_process(&[crate::opt::nft_command(), "-j", "-f", "-"], Some(rule)) } pub fn install_nft_rules() -> Result<()> { let rule = serde_json::json!( { "nftables": [ {"add": {"table": {"family": "inet", "name": DPIBREAK_TABLE}}}, // Clienthello { "add": { "chain": { "family": "inet", "table": DPIBREAK_TABLE, "name": "OUTPUT", "type": "filter", "hook": "output", "prio": 0, "policy": "accept", } } }, { "add": { "rule": { "family": "inet", "table": DPIBREAK_TABLE, "chain": "OUTPUT", "expr": [ { "match": { "left": { "meta": { "key": "mark" }}, "op": "==", "right": INJECT_MARK } }, { "return": null } ] } } }, { "add": { "rule": { "family": "inet", "table": DPIBREAK_TABLE, "chain": "OUTPUT", "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": crate::opt::queue_num(), "flags": [ "bypass" ] } } ] } } } ] } ); apply_nft_rules(&serde_json::to_string(&rule)?)?; log_println!(LogLevel::Info, "nftables: add chain OUTPUT, match ClientHello -> queue {})", opt::queue_num()); log_println!(LogLevel::Debug, "nftables: rule json={}", rule); if opt::fake_autottl() { let synack_rule = serde_json::json!( { "nftables": [ // SYN,ACK (for --fake-autottl) { "add": { "chain": { "family": "inet", "table": DPIBREAK_TABLE, "name": "INPUT", "type": "filter", "hook": "input", "prio": 0, "policy": "accept", } } }, { "add" : { "rule": { "family": "inet", "table": DPIBREAK_TABLE, "chain": "INPUT", "expr": [ { "match": { "left": { "payload": { "protocol": "tcp", "field": "sport" }}, "op": "==", "right": 443 } }, { "match": { "left": { "payload": { "protocol": "tcp", "field": "flags" }}, "op": "==", "right": 18 // 18 = SYN(2) | ACK(16) } }, { "queue": { "num": crate::opt::queue_num(), "flags": [ "bypass" ] } } ] }, } } ] } ); apply_nft_rules(&serde_json::to_string(&synack_rule)?)?; log_println!(LogLevel::Info, "nftables: add chain INPUT, match tcp sport 443 & SYN|ACK -> queue {})", opt::queue_num()); log_println!(LogLevel::Debug, "nftables: synack rule json={}", synack_rule); } // clienthello filtered by nft IS_U32_SUPPORTED.store(true, Ordering::Relaxed); log_println!(LogLevel::Info, "nftables: create table inet {DPIBREAK_TABLE}"); Ok(()) } pub fn cleanup_nftables_rules() -> Result<()> { // nft delete table inet dpibreak let rule = serde_json::json!({ "nftables": [ {"delete": {"table": {"family": "inet", "name": DPIBREAK_TABLE}}} ] }); apply_nft_rules(&serde_json::to_string(&rule)?)?; log_println!(LogLevel::Info, "cleanup: nftables: delete table inet {}", DPIBREAK_TABLE); Ok(()) }