summaryrefslogtreecommitdiffhomepage
path: root/src/platform/linux/nftables.rs
blob: ccb918eb97aa7b0debda4f8e69c8bf7441eb1ff0 (plain)
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(())
}