1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
// SPDX-FileCopyrightText: 2026 Dilluti0n <[email protected]>
// 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);
// 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(())
}
|