summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock433
-rw-r--r--Cargo.toml11
-rw-r--r--benches/hoptab_bench.rs99
-rw-r--r--src/opt.rs25
-rw-r--r--src/pkt.rs124
-rw-r--r--src/pkt/fake.rs143
-rw-r--r--src/pkt/hoptab.rs551
-rw-r--r--src/platform/linux/iptables.rs19
-rw-r--r--src/platform/linux/nftables.rs59
-rw-r--r--src/platform/windows.rs24
10 files changed, 1388 insertions, 100 deletions
diff --git a/Cargo.lock b/Cargo.lock
index a24e8d9..e748169 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,6 +3,36 @@
version = 4
[[package]]
+name = "aho-corasick"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "alloca"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "anes"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
+
+[[package]]
+name = "anstyle"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
+
+[[package]]
name = "anyhow"
version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -15,6 +45,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -30,12 +66,24 @@ dependencies = [
]
[[package]]
+name = "bumpalo"
+version = "3.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
+
+[[package]]
name = "bytes"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
[[package]]
+name = "cast"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
+
+[[package]]
name = "cc"
version = "1.2.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -58,6 +106,124 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
+name = "ciborium"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
+dependencies = [
+ "ciborium-io",
+ "ciborium-ll",
+ "serde",
+]
+
+[[package]]
+name = "ciborium-io"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
+
+[[package]]
+name = "ciborium-ll"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
+dependencies = [
+ "ciborium-io",
+ "half",
+]
+
+[[package]]
+name = "clap"
+version = "4.5.54"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394"
+dependencies = [
+ "clap_builder",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.54"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00"
+dependencies = [
+ "anstyle",
+ "clap_lex",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
+
+[[package]]
+name = "criterion"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d883447757bb0ee46f233e9dc22eb84d93a9508c9b868687b274fc431d886bf"
+dependencies = [
+ "alloca",
+ "anes",
+ "cast",
+ "ciborium",
+ "clap",
+ "criterion-plot",
+ "itertools",
+ "num-traits",
+ "oorandom",
+ "page_size",
+ "plotters",
+ "rayon",
+ "regex",
+ "serde",
+ "serde_json",
+ "tinytemplate",
+ "walkdir",
+]
+
+[[package]]
+name = "criterion-plot"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed943f81ea2faa8dcecbbfa50164acf95d555afec96a27871663b300e387b2e4"
+dependencies = [
+ "cast",
+ "itertools",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crunchy"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
+
+[[package]]
name = "ctrlc"
version = "3.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -85,6 +251,7 @@ name = "dpibreak"
version = "0.2.2"
dependencies = [
"anyhow",
+ "criterion",
"ctrlc",
"etherparse 0.18.2",
"nfq-updated",
@@ -96,6 +263,12 @@ dependencies = [
]
[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
name = "etherparse"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -120,12 +293,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
[[package]]
+name = "half"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
+dependencies = [
+ "cfg-if",
+ "crunchy",
+ "zerocopy",
+]
+
+[[package]]
+name = "itertools"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+dependencies = [
+ "either",
+]
+
+[[package]]
name = "itoa"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]]
+name = "js-sys"
+version = "0.3.85"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
name = "libc"
version = "0.2.180"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -172,6 +375,15 @@ dependencies = [
]
[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
name = "objc2"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -187,6 +399,56 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "oorandom"
+version = "11.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
+
+[[package]]
+name = "page_size"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "plotters"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
+dependencies = [
+ "num-traits",
+ "plotters-backend",
+ "plotters-svg",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "plotters-backend"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
+
+[[package]]
+name = "plotters-svg"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
+dependencies = [
+ "plotters-backend",
+]
+
+[[package]]
name = "proc-macro2"
version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -205,12 +467,77 @@ dependencies = [
]
[[package]]
+name = "rayon"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "regex"
+version = "1.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
+ "serde_derive",
]
[[package]]
@@ -294,6 +621,16 @@ dependencies = [
]
[[package]]
+name = "tinytemplate"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
name = "toml"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -309,6 +646,102 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.85"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
name = "windivert"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index f580a85..53b2ea4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -48,3 +48,14 @@ winres = "0.1"
[profile.release]
lto = true
panic = "abort"
+
+[dev-dependencies]
+criterion = { version = "0.8", features = ["html_reports"] }
+
+[features]
+bench = []
+
+[[bench]]
+name = "hoptab_bench"
+harness = false
+required-features = ["bench"] \ No newline at end of file
diff --git a/benches/hoptab_bench.rs b/benches/hoptab_bench.rs
new file mode 100644
index 0000000..fd9a863
--- /dev/null
+++ b/benches/hoptab_bench.rs
@@ -0,0 +1,99 @@
+// SPDX-FileCopyrightText: 2026 Dilluti0n <[email protected]>
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#![cfg(feature = "bench")]
+
+use std::net::{IpAddr, Ipv4Addr};
+use criterion::{criterion_group, criterion_main, Criterion, BenchmarkId, BatchSize, Throughput};
+use std::hint::black_box;
+
+#[macro_use]
+#[path = "../src/log.rs"]
+mod log;
+
+#[path = "../src/opt.rs"]
+mod opt;
+
+#[path = "../src/pkt/hoptab.rs"]
+pub mod hoptab;
+
+use hoptab::{put_0, find_0, reset_0};
+
+fn prepare_data(count: usize) -> Vec<(IpAddr, u8)> {
+ (0..count as u32)
+ .map(|i| (IpAddr::V4(Ipv4Addr::from(i)), (i % 255) as u8))
+ .collect()
+}
+
+fn bench_hoptab_operations(c: &mut Criterion) {
+ let dataset = prepare_data(1024);
+ let mut group = c.benchmark_group("HopTab_Core");
+
+ let (test_ip, test_hop) = dataset[0];
+ reset_0();
+ put_0(test_ip, test_hop);
+
+ group.bench_function("find_hop_hit", |b| {
+ b.iter(|| {
+ _ = find_0(black_box(test_ip));
+ })
+ });
+ reset_0();
+
+ let mut i = 0;
+ group.bench_function("put_and_evict", |b| {
+ b.iter(|| {
+ let (ip, hop) = dataset[i % dataset.len()];
+ put_0(black_box(ip), black_box(hop));
+ i += 1;
+ })
+ });
+
+ group.finish();
+}
+
+#[inline]
+fn xorshift64(s: &mut u64) -> u64 {
+ let mut x = *s;
+ x ^= x << 13;
+ x ^= x >> 7;
+ x ^= x << 17;
+ *s = x;
+ x
+}
+
+pub fn bench_hoptab_usecase(c: &mut Criterion) {
+ let dataset = prepare_data(1024);
+
+ let mut group = c.benchmark_group("HopTab_Usecase");
+ let mut seed = 0xC0FFEE_u64;
+
+ for &noise in &[0usize, 1, 4, 16, 64, 128] {
+ reset_0();
+
+ // put origin
+ let i = (xorshift64(&mut seed) as usize) % dataset.len();
+ let (ip, hop) = dataset[i];
+ put_0(black_box(ip), black_box(hop));
+
+ // noise: other SYN/ACK
+ for _ in 0..noise {
+ let j = (xorshift64(&mut seed) as usize) % dataset.len();
+ let (ip2, hop2) = dataset[j];
+ put_0(black_box(ip2), black_box(hop2));
+ }
+
+ group.bench_function(
+ BenchmarkId::new("find_between_noise", noise),
+ |b| {
+ b.iter(|| {
+ _ = black_box(find_0(black_box(ip)));
+ })
+ });
+ }
+
+ group.finish();
+}
+
+criterion_group!(benches, bench_hoptab_operations, bench_hoptab_usecase);
+criterion_main!(benches);
diff --git a/src/opt.rs b/src/opt.rs
index 78d6b0a..255ae8f 100644
--- a/src/opt.rs
+++ b/src/opt.rs
@@ -15,6 +15,7 @@ static OPT_NO_SPLASH: OnceLock<bool> = OnceLock::new();
static OPT_FAKE: OnceLock<bool> = OnceLock::new();
static OPT_FAKE_TTL: OnceLock<u8> = OnceLock::new();
+static OPT_FAKE_AUTOTTL: OnceLock<bool> = OnceLock::new();
static OPT_FAKE_BADSUM: OnceLock<bool> = OnceLock::new();
static OPT_DELAY_MS: OnceLock<u64> = OnceLock::new();
@@ -28,6 +29,7 @@ const DEFAULT_NO_SPLASH: bool = false;
const DEFAULT_FAKE: bool = false;
const DEFAULT_FAKE_TTL: u8 = 8;
+const DEFAULT_FAKE_AUTOTTL: bool = false;
const DEFAULT_FAKE_BADSUM: bool = false;
const DEFAULT_DELAY_MS: u64 = 0;
@@ -51,6 +53,10 @@ pub fn fake_ttl() -> u8 {
*OPT_FAKE_TTL.get().unwrap_or(&DEFAULT_FAKE_TTL)
}
+pub fn fake_autottl() -> bool {
+ *OPT_FAKE_AUTOTTL.get().unwrap_or(&DEFAULT_FAKE_AUTOTTL)
+}
+
pub fn fake_badsum() -> bool {
*OPT_FAKE_BADSUM.get().unwrap_or(&DEFAULT_FAKE_BADSUM)
}
@@ -96,7 +102,9 @@ fn usage() {
println!(" --fake Enable fake clienthello injection");
println!(" --fake-ttl <u8> Override ttl of fake clienthello (default: {DEFAULT_FAKE_TTL})");
- println!(" --fake-badsum Modifies the TCP checksum of the fake packet to an invalid value.\n");
+ println!(" --fake-autottl Override ttl of fake clienthello automatically");
+ println!(" --fake-badsum Modifies the TCP checksum of the fake packet to an invalid value.");
+ println!("");
println!(" -h, --help Show this help");
}
@@ -115,12 +123,13 @@ fn set_opt<T: std::fmt::Display>(
}
fn parse_args_1() -> Result<()> {
- let mut log_level = DEFAULT_LOG_LEVEL;
- let mut delay_ms = DEFAULT_DELAY_MS;
- let mut no_splash = DEFAULT_NO_SPLASH;
- let mut fake = DEFAULT_FAKE;
- let mut fake_ttl = DEFAULT_FAKE_TTL;
- let mut fake_badsum = DEFAULT_FAKE_BADSUM;
+ let mut log_level = DEFAULT_LOG_LEVEL;
+ let mut delay_ms = DEFAULT_DELAY_MS;
+ let mut no_splash = DEFAULT_NO_SPLASH;
+ let mut fake = DEFAULT_FAKE;
+ let mut fake_ttl = DEFAULT_FAKE_TTL;
+ let mut fake_autottl = DEFAULT_FAKE_AUTOTTL;
+ let mut fake_badsum = DEFAULT_FAKE_BADSUM;
#[cfg(target_os = "linux")]
let mut queue_num: u16 = DEFAULT_QUEUE_NUM;
@@ -150,6 +159,7 @@ Use `--log-level' instead.");
"--fake" => { fake = true; }
"--fake-ttl" => { fake_ttl = take_value(&mut args, argv)?; }
+ "--fake-autottl" => { fake_autottl = true }
"--fake-badsum" => { fake_badsum = true }
#[cfg(target_os = "linux")]
@@ -168,6 +178,7 @@ Use `--log-level' instead.");
set_opt("OPT_DELAY_MS", &OPT_DELAY_MS, delay_ms)?;
set_opt("OPT_FAKE", &OPT_FAKE, fake)?;
set_opt("OPT_FAKE_TTL", &OPT_FAKE_TTL, fake_ttl)?;
+ set_opt("OPT_FAKE_AUTOTTL", &OPT_FAKE_AUTOTTL, fake_autottl)?;
set_opt("OPT_FAKE_BADSUM", &OPT_FAKE_BADSUM, fake_badsum)?;
#[cfg(target_os = "linux")] set_opt("OPT_QUEUE_NUM", &OPT_QUEUE_NUM, queue_num)?;
diff --git a/src/pkt.rs b/src/pkt.rs
index 158ad4b..34190a8 100644
--- a/src/pkt.rs
+++ b/src/pkt.rs
@@ -18,6 +18,7 @@
use anyhow::Result;
use etherparse::{IpSlice, TcpSlice};
use anyhow::anyhow;
+#[cfg(target_os = "linux")]
use std::sync::atomic::Ordering;
#[cfg(debug_assertions)]
@@ -28,91 +29,49 @@ use crate::opt;
use crate::platform;
use crate::tls;
+mod fake;
+mod hoptab;
+
#[cfg(debug_assertions)]
use log::LogLevel;
-/// www.microsoft.com
-/// Stolen from github.com/bol-van/zapret/blob/master/nfq/desync.c
-const DEFAULT_FAKE_TLS_CLIENTHELLO: &'static [u8] = &[
- 0x16, 0x03, 0x01, 0x02, 0xa3, 0x01, 0x00, 0x02, 0x9f, 0x03, 0x03, 0x41,
- 0x88, 0x82, 0x2d, 0x4f, 0xfd, 0x81, 0x48, 0x9e, 0xe7, 0x90, 0x65, 0x1f,
- 0xba, 0x05, 0x7b, 0xff, 0xa7, 0x5a, 0xf9, 0x5b, 0x8a, 0x8f, 0x45, 0x8b,
- 0x41, 0xf0, 0x3d, 0x1b, 0xdd, 0xe3, 0xf8, 0x20, 0x9b, 0x23, 0xa5, 0xd2,
- 0x21, 0x1e, 0x9f, 0xe7, 0x85, 0x6c, 0xfc, 0x61, 0x80, 0x3a, 0x3f, 0xba,
- 0xb9, 0x60, 0xba, 0xb3, 0x0e, 0x98, 0x27, 0x6c, 0xf7, 0x38, 0x28, 0x65,
- 0x80, 0x5d, 0x40, 0x38, 0x00, 0x22, 0x13, 0x01, 0x13, 0x03, 0x13, 0x02,
- 0xc0, 0x2b, 0xc0, 0x2f, 0xcc, 0xa9, 0xcc, 0xa8, 0xc0, 0x2c, 0xc0, 0x30,
- 0xc0, 0x0a, 0xc0, 0x09, 0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d,
- 0x00, 0x2f, 0x00, 0x35, 0x01, 0x00, 0x02, 0x34, 0x00, 0x00, 0x00, 0x16,
- 0x00, 0x14, 0x00, 0x00, 0x11, 0x77, 0x77, 0x77, 0x2e, 0x6d, 0x69, 0x63,
- 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x17,
- 0x00, 0x00, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x0e, 0x00,
- 0x0c, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19, 0x01, 0x00, 0x01,
- 0x01, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
- 0x10, 0x00, 0x0e, 0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74,
- 0x70, 0x2f, 0x31, 0x2e, 0x31, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x22, 0x00, 0x0a, 0x00, 0x08, 0x04, 0x03, 0x05, 0x03,
- 0x06, 0x03, 0x02, 0x03, 0x00, 0x12, 0x00, 0x00, 0x00, 0x33, 0x00, 0x6b,
- 0x00, 0x69, 0x00, 0x1d, 0x00, 0x20, 0x69, 0x15, 0x16, 0x29, 0x6d, 0xad,
- 0xd5, 0x68, 0x88, 0x27, 0x2f, 0xde, 0xaf, 0xac, 0x3c, 0x4c, 0xa4, 0xe4,
- 0xd8, 0xc8, 0xfb, 0x41, 0x87, 0xf4, 0x76, 0x4e, 0x0e, 0xfa, 0x64, 0xc4,
- 0xe9, 0x29, 0x00, 0x17, 0x00, 0x41, 0x04, 0xfe, 0x62, 0xb9, 0x08, 0xc8,
- 0xc3, 0x2a, 0xb9, 0x87, 0x37, 0x84, 0x42, 0x6b, 0x5c, 0xcd, 0xc9, 0xca,
- 0x62, 0x38, 0xd3, 0xd9, 0x99, 0x8a, 0xc4, 0x2d, 0xc6, 0xd0, 0xa3, 0x60,
- 0xb2, 0x12, 0x54, 0x41, 0x8e, 0x52, 0x5e, 0xe3, 0xab, 0xf9, 0xc2, 0x07,
- 0x81, 0xdc, 0xf8, 0xf2, 0x6a, 0x91, 0x40, 0x2f, 0xcb, 0xa4, 0xff, 0x6f,
- 0x24, 0xc7, 0x4d, 0x77, 0x77, 0x2d, 0x6f, 0xe0, 0x77, 0xaa, 0x92, 0x00,
- 0x2b, 0x00, 0x05, 0x04, 0x03, 0x04, 0x03, 0x03, 0x00, 0x0d, 0x00, 0x18,
- 0x00, 0x16, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x08, 0x04, 0x08, 0x05,
- 0x08, 0x06, 0x04, 0x01, 0x05, 0x01, 0x06, 0x01, 0x02, 0x03, 0x02, 0x01,
- 0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, 0x00, 0x1c, 0x00, 0x02, 0x40, 0x01,
- 0x00, 0x1b, 0x00, 0x07, 0x06, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0xfe,
- 0x0d, 0x01, 0x19, 0x00, 0x00, 0x01, 0x00, 0x03, 0x21, 0x00, 0x20, 0x62,
- 0xe8, 0x83, 0xd8, 0x97, 0x05, 0x8a, 0xbe, 0xa1, 0xf2, 0x63, 0x4e, 0xce,
- 0x93, 0x84, 0x8e, 0xcf, 0xe7, 0xdd, 0xb2, 0xe4, 0x87, 0x06, 0xac, 0x11,
- 0x19, 0xbe, 0x0e, 0x71, 0x87, 0xf1, 0xa6, 0x00, 0xef, 0xd8, 0x6b, 0x27,
- 0x5e, 0xc0, 0xa7, 0x5d, 0x42, 0x4e, 0x8c, 0xdc, 0xf3, 0x9f, 0x1c, 0x51,
- 0x62, 0xef, 0xff, 0x5b, 0xed, 0xc8, 0xfd, 0xee, 0x6f, 0xbb, 0x88, 0x9b,
- 0xb1, 0x30, 0x9c, 0x66, 0x42, 0xab, 0x0f, 0x66, 0x89, 0x18, 0x8b, 0x11,
- 0xc1, 0x6d, 0xe7, 0x2a, 0xeb, 0x96, 0x3b, 0x7f, 0x52, 0x78, 0xdb, 0xf8,
- 0x6d, 0x04, 0xf7, 0x95, 0x1a, 0xa8, 0xf0, 0x64, 0x52, 0x07, 0x39, 0xf0,
- 0xa8, 0x1d, 0x0d, 0x16, 0x36, 0xb7, 0x18, 0x0e, 0xc8, 0x44, 0x27, 0xfe,
- 0xf3, 0x31, 0xf0, 0xde, 0x8c, 0x74, 0xf5, 0xa1, 0xd8, 0x8f, 0x6f, 0x45,
- 0x97, 0x69, 0x79, 0x5e, 0x2e, 0xd4, 0xb0, 0x2c, 0x0c, 0x1a, 0x6f, 0xcc,
- 0xce, 0x90, 0xc7, 0xdd, 0xc6, 0x60, 0x95, 0xf3, 0xc2, 0x19, 0xde, 0x50,
- 0x80, 0xbf, 0xde, 0xf2, 0x25, 0x63, 0x15, 0x26, 0x63, 0x09, 0x1f, 0xc5,
- 0xdf, 0x32, 0xf5, 0xea, 0x9c, 0xd2, 0xff, 0x99, 0x4e, 0x67, 0xa2, 0xe5,
- 0x1a, 0x94, 0x85, 0xe3, 0xdf, 0x36, 0xa5, 0x83, 0x4b, 0x0a, 0x1c, 0xaf,
- 0xd7, 0x48, 0xc9, 0x4b, 0x8a, 0x27, 0xdd, 0x58, 0x7f, 0x95, 0xf2, 0x6b,
- 0xde, 0x2b, 0x12, 0xd3, 0xec, 0x4d, 0x69, 0x37, 0x9c, 0x13, 0x9b, 0x16,
- 0xb0, 0x45, 0x52, 0x38, 0x77, 0x69, 0xef, 0xaa, 0x65, 0x19, 0xbc, 0xc2,
- 0x93, 0x4d, 0xb0, 0x1b, 0x7f, 0x5b, 0x41, 0xff, 0xaf, 0xba, 0x50, 0x51,
- 0xc3, 0xf1, 0x27, 0x09, 0x25, 0xf5, 0x60, 0x90, 0x09, 0xb1, 0xe5, 0xc0,
- 0xc7, 0x42, 0x78, 0x54, 0x3b, 0x23, 0x19, 0x7d, 0x8e, 0x72, 0x13, 0xb4,
- 0xd3, 0xcd, 0x63, 0xb6, 0xc4, 0x4a, 0x28, 0x3d, 0x45, 0x3e, 0x8b, 0xdb,
- 0x84, 0x4f, 0x78, 0x64, 0x30, 0x69, 0xe2, 0x1b
-];
-
-pub struct PktView<'a> {
- pub ip: IpSlice<'a>,
- pub tcp: TcpSlice<'a>
+struct PktView<'a> {
+ ip: IpSlice<'a>,
+ tcp: TcpSlice<'a>
}
impl<'a> PktView<'a> {
#[inline]
- pub fn from_raw(raw: &'a [u8]) -> Result<Self> {
+ fn from_raw(raw: &'a [u8]) -> Result<Self> {
let ip = IpSlice::from_slice(raw)?;
let tcp = TcpSlice::from_slice(ip.payload().payload)?;
Ok(Self { ip, tcp })
}
+
+ fn ttl(&self) -> u8 {
+ use etherparse::IpSlice;
+
+ match &self.ip {
+ IpSlice::Ipv4(v4) => v4.header().ttl(),
+ IpSlice::Ipv6(v6) => v6.header().hop_limit()
+ }
+ }
+
+ fn saddr(&self) -> std::net::IpAddr {
+ self.ip.source_addr()
+ }
+
+ fn daddr(&self) -> std::net::IpAddr {
+ self.ip.destination_addr()
+ }
}
/// Write TCP/IP packet (payload = view.tcp.payload[start..Some(end)])
/// to out_buf, explicitly clearing before.
///
/// If payload, ttl or tcp_checksum is given, override view's one.
-pub fn split_packet_0(
+fn split_packet_0(
view: &PktView,
start: u32,
end: Option<u32>,
@@ -183,25 +142,6 @@ pub fn split_packet_0(
Ok(())
}
-fn fake_clienthello(
- view: &PktView,
- start: u32,
- end: Option<u32>,
- out_buf: &mut Vec<u8>
-) -> Result<()> {
-
- let tcp_checksum = if opt::fake_badsum() {
- Some(0)
- } else {
- None
- };
-
- split_packet_0(view, start, end, out_buf,
- Some(DEFAULT_FAKE_TLS_CLIENTHELLO),
- Some(opt::fake_ttl()),
- tcp_checksum)
-}
-
fn split_packet(
view: &PktView,
start: u32,
@@ -220,7 +160,7 @@ fn send_segment(
use platform::send_to_raw;
if opt::fake() {
- fake_clienthello(view, start, end, buf)?;
+ fake::fake_clienthello(view, start, end, buf)?;
send_to_raw(buf)?;
}
split_packet(view, start, end, buf)?;
@@ -247,6 +187,11 @@ fn split_packet_1(view: &PktView, order: &[u32], buf: &mut Vec<u8>) -> Result<()
Ok(())
}
+fn is_synack_from_443(view: &PktView) -> bool {
+ // sport == 443 and flags SYN+ACK
+ view.tcp.source_port() == 443 && view.tcp.syn() && view.tcp.ack()
+}
+
/// Return Ok(true) if packet is handled
pub fn handle_packet(pkt: &[u8], buf: &mut Vec::<u8>) -> Result<bool> {
#[cfg(target_os = "linux")]
@@ -257,6 +202,11 @@ pub fn handle_packet(pkt: &[u8], buf: &mut Vec::<u8>) -> Result<bool> {
let view = PktView::from_raw(pkt)?;
+ if opt::fake_autottl() && is_synack_from_443(&view) {
+ fake::saddr_hop_put(&view);
+ return Ok(false);
+ }
+
if !is_filtered && !tls::is_client_hello(view.tcp.payload()) {
return Ok(false);
}
diff --git a/src/pkt/fake.rs b/src/pkt/fake.rs
new file mode 100644
index 0000000..774642f
--- /dev/null
+++ b/src/pkt/fake.rs
@@ -0,0 +1,143 @@
+// SPDX-FileCopyrightText: 2026 Dilluti0n <[email protected]>
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+use anyhow::Result;
+use log::LogLevel;
+
+use crate::log;
+use crate::opt;
+use crate::log_println;
+use crate::pkt::hoptab;
+
+use super::PktView;
+
+/// www.microsoft.com
+/// Stolen from github.com/bol-van/zapret/blob/master/nfq/desync.c
+const DEFAULT_FAKE_TLS_CLIENTHELLO: &'static [u8] = &[
+ 0x16, 0x03, 0x01, 0x02, 0xa3, 0x01, 0x00, 0x02, 0x9f, 0x03, 0x03, 0x41,
+ 0x88, 0x82, 0x2d, 0x4f, 0xfd, 0x81, 0x48, 0x9e, 0xe7, 0x90, 0x65, 0x1f,
+ 0xba, 0x05, 0x7b, 0xff, 0xa7, 0x5a, 0xf9, 0x5b, 0x8a, 0x8f, 0x45, 0x8b,
+ 0x41, 0xf0, 0x3d, 0x1b, 0xdd, 0xe3, 0xf8, 0x20, 0x9b, 0x23, 0xa5, 0xd2,
+ 0x21, 0x1e, 0x9f, 0xe7, 0x85, 0x6c, 0xfc, 0x61, 0x80, 0x3a, 0x3f, 0xba,
+ 0xb9, 0x60, 0xba, 0xb3, 0x0e, 0x98, 0x27, 0x6c, 0xf7, 0x38, 0x28, 0x65,
+ 0x80, 0x5d, 0x40, 0x38, 0x00, 0x22, 0x13, 0x01, 0x13, 0x03, 0x13, 0x02,
+ 0xc0, 0x2b, 0xc0, 0x2f, 0xcc, 0xa9, 0xcc, 0xa8, 0xc0, 0x2c, 0xc0, 0x30,
+ 0xc0, 0x0a, 0xc0, 0x09, 0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d,
+ 0x00, 0x2f, 0x00, 0x35, 0x01, 0x00, 0x02, 0x34, 0x00, 0x00, 0x00, 0x16,
+ 0x00, 0x14, 0x00, 0x00, 0x11, 0x77, 0x77, 0x77, 0x2e, 0x6d, 0x69, 0x63,
+ 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x17,
+ 0x00, 0x00, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x0e, 0x00,
+ 0x0c, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19, 0x01, 0x00, 0x01,
+ 0x01, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x0e, 0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74,
+ 0x70, 0x2f, 0x31, 0x2e, 0x31, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x22, 0x00, 0x0a, 0x00, 0x08, 0x04, 0x03, 0x05, 0x03,
+ 0x06, 0x03, 0x02, 0x03, 0x00, 0x12, 0x00, 0x00, 0x00, 0x33, 0x00, 0x6b,
+ 0x00, 0x69, 0x00, 0x1d, 0x00, 0x20, 0x69, 0x15, 0x16, 0x29, 0x6d, 0xad,
+ 0xd5, 0x68, 0x88, 0x27, 0x2f, 0xde, 0xaf, 0xac, 0x3c, 0x4c, 0xa4, 0xe4,
+ 0xd8, 0xc8, 0xfb, 0x41, 0x87, 0xf4, 0x76, 0x4e, 0x0e, 0xfa, 0x64, 0xc4,
+ 0xe9, 0x29, 0x00, 0x17, 0x00, 0x41, 0x04, 0xfe, 0x62, 0xb9, 0x08, 0xc8,
+ 0xc3, 0x2a, 0xb9, 0x87, 0x37, 0x84, 0x42, 0x6b, 0x5c, 0xcd, 0xc9, 0xca,
+ 0x62, 0x38, 0xd3, 0xd9, 0x99, 0x8a, 0xc4, 0x2d, 0xc6, 0xd0, 0xa3, 0x60,
+ 0xb2, 0x12, 0x54, 0x41, 0x8e, 0x52, 0x5e, 0xe3, 0xab, 0xf9, 0xc2, 0x07,
+ 0x81, 0xdc, 0xf8, 0xf2, 0x6a, 0x91, 0x40, 0x2f, 0xcb, 0xa4, 0xff, 0x6f,
+ 0x24, 0xc7, 0x4d, 0x77, 0x77, 0x2d, 0x6f, 0xe0, 0x77, 0xaa, 0x92, 0x00,
+ 0x2b, 0x00, 0x05, 0x04, 0x03, 0x04, 0x03, 0x03, 0x00, 0x0d, 0x00, 0x18,
+ 0x00, 0x16, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x08, 0x04, 0x08, 0x05,
+ 0x08, 0x06, 0x04, 0x01, 0x05, 0x01, 0x06, 0x01, 0x02, 0x03, 0x02, 0x01,
+ 0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, 0x00, 0x1c, 0x00, 0x02, 0x40, 0x01,
+ 0x00, 0x1b, 0x00, 0x07, 0x06, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0xfe,
+ 0x0d, 0x01, 0x19, 0x00, 0x00, 0x01, 0x00, 0x03, 0x21, 0x00, 0x20, 0x62,
+ 0xe8, 0x83, 0xd8, 0x97, 0x05, 0x8a, 0xbe, 0xa1, 0xf2, 0x63, 0x4e, 0xce,
+ 0x93, 0x84, 0x8e, 0xcf, 0xe7, 0xdd, 0xb2, 0xe4, 0x87, 0x06, 0xac, 0x11,
+ 0x19, 0xbe, 0x0e, 0x71, 0x87, 0xf1, 0xa6, 0x00, 0xef, 0xd8, 0x6b, 0x27,
+ 0x5e, 0xc0, 0xa7, 0x5d, 0x42, 0x4e, 0x8c, 0xdc, 0xf3, 0x9f, 0x1c, 0x51,
+ 0x62, 0xef, 0xff, 0x5b, 0xed, 0xc8, 0xfd, 0xee, 0x6f, 0xbb, 0x88, 0x9b,
+ 0xb1, 0x30, 0x9c, 0x66, 0x42, 0xab, 0x0f, 0x66, 0x89, 0x18, 0x8b, 0x11,
+ 0xc1, 0x6d, 0xe7, 0x2a, 0xeb, 0x96, 0x3b, 0x7f, 0x52, 0x78, 0xdb, 0xf8,
+ 0x6d, 0x04, 0xf7, 0x95, 0x1a, 0xa8, 0xf0, 0x64, 0x52, 0x07, 0x39, 0xf0,
+ 0xa8, 0x1d, 0x0d, 0x16, 0x36, 0xb7, 0x18, 0x0e, 0xc8, 0x44, 0x27, 0xfe,
+ 0xf3, 0x31, 0xf0, 0xde, 0x8c, 0x74, 0xf5, 0xa1, 0xd8, 0x8f, 0x6f, 0x45,
+ 0x97, 0x69, 0x79, 0x5e, 0x2e, 0xd4, 0xb0, 0x2c, 0x0c, 0x1a, 0x6f, 0xcc,
+ 0xce, 0x90, 0xc7, 0xdd, 0xc6, 0x60, 0x95, 0xf3, 0xc2, 0x19, 0xde, 0x50,
+ 0x80, 0xbf, 0xde, 0xf2, 0x25, 0x63, 0x15, 0x26, 0x63, 0x09, 0x1f, 0xc5,
+ 0xdf, 0x32, 0xf5, 0xea, 0x9c, 0xd2, 0xff, 0x99, 0x4e, 0x67, 0xa2, 0xe5,
+ 0x1a, 0x94, 0x85, 0xe3, 0xdf, 0x36, 0xa5, 0x83, 0x4b, 0x0a, 0x1c, 0xaf,
+ 0xd7, 0x48, 0xc9, 0x4b, 0x8a, 0x27, 0xdd, 0x58, 0x7f, 0x95, 0xf2, 0x6b,
+ 0xde, 0x2b, 0x12, 0xd3, 0xec, 0x4d, 0x69, 0x37, 0x9c, 0x13, 0x9b, 0x16,
+ 0xb0, 0x45, 0x52, 0x38, 0x77, 0x69, 0xef, 0xaa, 0x65, 0x19, 0xbc, 0xc2,
+ 0x93, 0x4d, 0xb0, 0x1b, 0x7f, 0x5b, 0x41, 0xff, 0xaf, 0xba, 0x50, 0x51,
+ 0xc3, 0xf1, 0x27, 0x09, 0x25, 0xf5, 0x60, 0x90, 0x09, 0xb1, 0xe5, 0xc0,
+ 0xc7, 0x42, 0x78, 0x54, 0x3b, 0x23, 0x19, 0x7d, 0x8e, 0x72, 0x13, 0xb4,
+ 0xd3, 0xcd, 0x63, 0xb6, 0xc4, 0x4a, 0x28, 0x3d, 0x45, 0x3e, 0x8b, 0xdb,
+ 0x84, 0x4f, 0x78, 0x64, 0x30, 0x69, 0xe2, 0x1b
+];
+
+const AUTOTTL_DELTA: u8 = 1;
+
+/// Crudely infer hop from ttl
+///
+/// Assume server initial TTL is one of: 64, 126, 255.
+/// Pick the smallest origin that can produce the observed TTL (origin >= ttl),
+/// then hops = origin - ttl.
+fn infer_hops(ttl: u8) -> u8 {
+ let origin = if ttl <= 64 {
+ 64u8
+ } else if ttl <= 126 {
+ 126u8
+ } else {
+ 255u8
+ };
+
+ origin - ttl
+}
+
+
+pub fn saddr_hop_put(view: &PktView) {
+ hoptab::put_0(view.saddr(), infer_hops(view.ttl()))
+}
+
+fn daddr_hop(view: &PktView) -> hoptab::HopResult<u8> {
+ hoptab::find_0(view.daddr())
+}
+
+pub fn fake_clienthello(
+ view: &PktView,
+ start: u32,
+ end: Option<u32>,
+ out_buf: &mut Vec<u8>
+) -> Result<()> {
+
+ let tcp_checksum = if opt::fake_badsum() {
+ Some(0)
+ } else {
+ None
+ };
+
+ let ttl: u8 = if opt::fake_autottl() {
+ match daddr_hop(&view) {
+ Ok(hop) => {
+ let fake_ttl = hop.saturating_sub(AUTOTTL_DELTA);
+ log_println!(LogLevel::Debug,
+ "autottl: set ttl to {fake_ttl}"
+ );
+ fake_ttl
+ },
+ Err(e) => {
+ let fake_ttl = opt::fake_ttl();
+ log_println!(LogLevel::Warning,
+ "autottl: sv_hop_find: {e}; fallback to {fake_ttl}");
+ fake_ttl
+ }
+ }
+ } else {
+ opt::fake_ttl()
+ };
+
+ super::split_packet_0(
+ view, start, end, out_buf,
+ Some(DEFAULT_FAKE_TLS_CLIENTHELLO),
+ Some(ttl),
+ tcp_checksum
+ )
+}
diff --git a/src/pkt/hoptab.rs b/src/pkt/hoptab.rs
new file mode 100644
index 0000000..2ad1beb
--- /dev/null
+++ b/src/pkt/hoptab.rs
@@ -0,0 +1,551 @@
+// SPDX-FileCopyrightText: 2026 Dilluti0n <[email protected]>
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+//! Linear probing hash table for ip-hop cache
+//!
+//! On inbound SYN/ACK (src port 443) from a server, infer the hop
+//! count from the observed TTL via `crate::pkt::fake::infer_hops` and
+//! store it with [`HopTab::put`]. Later, when sending a fake
+//! ClientHello, look up the hop count by destination IP
+//! [`HopTab::find_hop`] to automatically choose an appropriate TTL.
+//!
+//! One could consider keeping a global variable (like `current_hop`),
+//! loading it when a SYN/ACK arrives, and reading it when needed. The
+//! problem is that, before the ClientHello is sent after that SYN/ACK
+//! (i.e., in between), a SYN/ACK from a different server may
+//! arrive. To handle this, the (ip, hop) pair must be stored in an
+//! appropriate data structure and looked up later.
+//!
+//! [`HopTab`] has capacity [`CAP`], which means that if at least
+//! [`CAP`] SYN/ACKs arrive from distinct servers before the first
+//! ClientHello is sent, [`HopLookupError::NotFound`] will inevitably
+//! occur. (Given the size of [`CAP`], this is extremely unlikely.)
+//! Also, after an entry is inserted, if [`HopTab::update`] runs at
+//! least [`HopTab::STALE_AGE`] times, the entry is marked stale and
+//! becomes evictable, so once the number of updates reaches
+//! [`HopTab::STALE_AGE`] or more, there is a chance that
+//! [`HopLookupError::NotFound`] occurs. Other than these cases, it
+//! will not occur.
+
+
+use std::fmt;
+use std::net::IpAddr;
+use std::sync::{Mutex, OnceLock};
+use std::net::{Ipv4Addr, Ipv6Addr};
+
+use crate::log_println;
+use crate::log::LogLevel;
+
+/// Size of [`HopTab`]
+const CAP: usize = 1 << 7; // 128
+
+/// 128-bit (IPv6-shaped) unified IP key for [`HopTab`] lookups (IPv4
+/// stored as ::ffff:a.b.c.d).
+#[repr(C)]
+#[derive(Clone, Copy, PartialEq, Eq)]
+struct HopKey {
+ hi: u64,
+ lo: u64
+}
+
+impl HopKey {
+ const ZERO: Self = Self { hi: 0, lo: 0 };
+
+ #[inline]
+ fn from_ipaddr(ip: IpAddr) -> Self {
+ match ip {
+ IpAddr::V4(v4) => {
+ // ::ffff:a.b.c.d (IPv4-mapped IPv6)
+ let v4u = u32::from(v4) as u64;
+ Self { hi: 0, lo: (0xFFFFu64 << 32) | v4u }
+ }
+ IpAddr::V6(v6) => {
+ let b = v6.octets();
+ let hi = u64::from_be_bytes(b[0..8].try_into().unwrap());
+ let lo = u64::from_be_bytes(b[8..16].try_into().unwrap());
+ Self { hi, lo }
+ }
+ }
+ }
+
+ #[inline]
+ fn to_ipaddr(self) -> IpAddr {
+ // ::ffff:a.b.c.d (IPv4-mapped IPv6)
+ if self.hi == 0 && (self.lo >> 32) == 0x0000_FFFF {
+ let v4 = (self.lo & 0xFFFF_FFFF) as u32;
+ return IpAddr::V4(Ipv4Addr::from(v4));
+ }
+
+ let mut b = [0u8; 16];
+ b[0..8].copy_from_slice(&self.hi.to_be_bytes());
+ b[8..16].copy_from_slice(&self.lo.to_be_bytes());
+ IpAddr::V6(Ipv6Addr::from(b))
+ }
+}
+
+#[derive(Clone, Copy)]
+struct HopTabEntry {
+ key: HopKey,
+
+ /// [RESERVED(32) | TS(16) | HOP(8) | STATE(8)]
+ /// * TS: timestamp snapshot (see [`HopTab::now`])
+ /// * HOP: stored hop count
+ /// * STATE: [`Self::ST_OCCUPIED`], [`Self::ST_TOUCHED`]
+ meta: u64,
+}
+
+impl HopTabEntry {
+ /// For internal use; Do not use it globally
+ const ST_EMPTY: u8 = 0;
+ const ST_OCCUPIED: u8 = 1 << 0;
+
+ /// Entry has been touched (i.e., consumed by [`HopTab::find_hop`]
+ /// at least once).
+ ///
+ /// Note: "touched" does not mean "recently used, keep it". It
+ /// means the entry already served its purpose (consumed for Fake
+ /// ClientHello TTL), so it is more eligible for eviction under
+ /// pressure than a fresh, untouched entry.
+ const ST_TOUCHED: u8 = 1 << 1;
+
+ const EMPTY: Self = Self { key: HopKey::ZERO, meta: Self::ST_EMPTY as u64};
+
+ const S_STATE: usize = 0;
+ const S_HOP: usize = 8;
+ const S_TS: usize = 16;
+
+ #[inline]
+ fn key(&self) -> HopKey {
+ self.key
+ }
+
+ #[inline]
+ fn new(key: HopKey, ts: u16, hop: u8) -> Self {
+ Self {
+ key: key,
+ meta: ((ts as u64) << Self::S_TS)
+ | ((hop as u64) << Self::S_HOP)
+ | ((Self::ST_OCCUPIED as u64) << Self::S_STATE)
+ }
+ }
+
+ #[inline]
+ fn hop(&self) -> u8 {
+ (self.meta >> Self::S_HOP) as u8
+ }
+
+ #[inline]
+ fn state(&self) -> u8 {
+ (self.meta >> Self::S_STATE) as u8
+ }
+
+ #[inline]
+ fn has(&self, mask: u8) -> bool {
+ (self.state() & mask) == mask
+ }
+
+ #[inline]
+ fn touch(&mut self) {
+ self.meta |= Self::ST_TOUCHED as u64;
+ }
+
+ #[inline]
+ fn ts(&self) -> u16 {
+ (self.meta >> Self::S_TS) as u16
+ }
+}
+
+impl fmt::Debug for HopTabEntry {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let ip = self.key.to_ipaddr();
+ let state = self.state();
+ let hop = self.hop();
+ let ts = self.ts();
+
+ write!(
+ f,
+ "HopTabEntry{{ ip={}, state=0x{:02x}, hop={}, ts={}, meta=0x{:016x} }}",
+ ip, state, hop, ts, self.meta
+ )
+ }
+}
+
+#[derive(Debug, Clone)]
+pub enum HopLookupError {
+ NotFound { ip: IpAddr },
+}
+
+impl fmt::Display for HopLookupError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ HopLookupError::NotFound { ip } => write!(f, "hop not found for {ip}"),
+ }
+ }
+}
+
+impl std::error::Error for HopLookupError {}
+
+pub type HopResult<T> = std::result::Result<T, HopLookupError>;
+
+struct HopTab<const CAP: usize> {
+ entries: Box<[HopTabEntry; CAP]>,
+
+ /// Logical tick counter.
+ ///
+ /// Increments on every successful [`Self::update`]
+ /// (put/overwrite) and wraps via [`u16::wrapping_add`]. It is
+ /// ensured that at least [`CAP`] - [`Self::STALE_AGE`] entries
+ /// evictable. (i.e. [`HopTab`] not become corrupted.)
+ now: u16,
+}
+
+/// Non-cryptographic hash using SplitMix64-style finalizer
+#[inline]
+fn hash(key: HopKey) -> usize {
+ let mut x = key.hi ^ key.lo.rotate_left(13);
+ x ^= x >> 30;
+ x = x.wrapping_mul(0xbf58476d1ce4e5b9);
+ x ^= x >> 27;
+ x = x.wrapping_mul(0x94d049bb133111eb);
+ x ^= x >> 31;
+ x as usize
+}
+
+trait HashIdx {
+ /// `CAP` must be a power of two (we index via `hash & (CAP-1)`).
+ fn to_idx<const CAP: usize>(self) -> usize;
+}
+
+impl HashIdx for usize {
+ #[inline]
+ fn to_idx<const CAP: usize>(self) -> usize {
+ self & (CAP - 1)
+ }
+}
+
+#[derive(PartialEq, PartialOrd, Clone, Copy)]
+enum EvictPriority {
+ /// Occupied && fresh && untouched
+ None = 0,
+
+ /// Already consumed
+ Touched = 1,
+ Stale = 2,
+ Empty = 3,
+
+ /// Same key occers
+ MustUpdate = 4,
+}
+
+impl<const CAP: usize> HopTab<CAP> {
+ const ASSERT_CAP_POW2: () = {
+ assert!(CAP.is_power_of_two());
+ };
+
+ /// To avoid the edge case where all entries become non-stale,
+ /// [`Self::STALE_AGE`] must be smaller than [`CAP`].
+ const STALE_AGE: usize = CAP >> 1; // 64
+
+ fn new() -> Self {
+ _ = Self::ASSERT_CAP_POW2;
+
+ Self {
+ entries: Box::new([HopTabEntry::EMPTY; CAP]),
+ now: 0,
+ }
+ }
+
+ #[inline]
+ fn age(&self, entry: &HopTabEntry) -> u16 {
+ // Since we use u16 with wrapping_sub, the age calculation remains
+ // correct even when `self.now` overflows and wraps around to zero.
+ // This holds true as long as the temporal distance between
+ // `entry.ts()` and `self.now` does not exceed 2^15 (32,768).
+ // Given that STALE_AGE (64) << 2^15, the "stale" judgment is
+ // always mathematically sound.
+ self.now.wrapping_sub(entry.ts())
+ }
+
+ #[inline]
+ fn is_stale(&self, entry: &HopTabEntry) -> bool {
+ self.age(entry) >= Self::STALE_AGE as u16
+ }
+
+ #[inline]
+ fn update(&mut self, idx: usize, new: HopTabEntry) {
+ self.entries[idx] = new;
+ self.now = self.now.wrapping_add(1);
+ }
+
+ #[inline]
+ fn evict_priority(&self, entry: &HopTabEntry) -> EvictPriority {
+ if !entry.has(HopTabEntry::ST_OCCUPIED) {
+ EvictPriority::Empty
+ } else if self.is_stale(entry) {
+ EvictPriority::Stale
+ } else if entry.has(HopTabEntry::ST_TOUCHED) {
+ EvictPriority::Touched
+ } else {
+ EvictPriority::None
+ }
+ }
+
+ fn put(&mut self, ip: IpAddr, hop: u8) {
+ let key = HopKey::from_ipaddr(ip);
+ let entry = HopTabEntry::new(key, self.now, hop);
+ let start = hash(key).to_idx::<CAP>();
+
+ let mut victim = (0, EvictPriority::None); // (idx, priority)
+
+ // Key must be unique in the table
+ for step in 0..CAP {
+ let idx = (start + step).to_idx::<CAP>();
+ let e = self.entries[idx];
+
+ // Hit; must update same key (hop could be changed)
+ if e.key() == key && e.has(HopTabEntry::ST_OCCUPIED) {
+ victim = (idx, EvictPriority::MustUpdate);
+ #[cfg(debug_assertions)]
+ log_println!(LogLevel::Debug, "HopTab::put: hit {}; {:#?}", victim.0, entry);
+ break;
+ }
+
+ let prio = self.evict_priority(&e);
+
+ if prio > victim.1 {
+ victim = (idx, prio);
+
+ if prio == EvictPriority::Empty {
+ #[cfg(debug_assertions)]
+ log_println!(LogLevel::Debug,
+ "HopTab::put: hit empty {}; {:#?}", victim.0, entry);
+ break; // linear probing; there is no key here
+ }
+ }
+ }
+
+ if victim.1 > EvictPriority::None {
+ self.update(victim.0, entry);
+ #[cfg(debug_assertions)]
+ log_println!(LogLevel::Debug, "HopTab::put: update {} to {:#?}", victim.0, entry);
+ } else {
+ log_println!(LogLevel::Error, "HopTab::put: update fail: corrupted; {:#?}", entry);
+ }
+ }
+
+ fn find_hop(&mut self, ip: IpAddr) -> HopResult<u8> {
+ let key = HopKey::from_ipaddr(ip);
+ let start = hash(key).to_idx::<CAP>();
+
+ for step in 0..CAP {
+ let idx = (start + step).to_idx::<CAP>();
+ let e = self.entries[idx];
+
+ if !e.has(HopTabEntry::ST_OCCUPIED) {
+ break; // linear probing; there is no key here
+ }
+
+ if e.key() == key {
+ self.entries[idx].touch();
+
+ #[cfg(debug_assertions)]
+ log_println!(LogLevel::Debug, "HopTab::find_hop: found {idx}; {:#?}", e);
+ return Ok(e.hop());
+ }
+ }
+
+ Err(HopLookupError::NotFound { ip })
+ }
+}
+
+static H_TAB: OnceLock<Mutex<HopTab<CAP>>> = OnceLock::new();
+
+#[inline]
+fn htab() -> std::sync::MutexGuard<'static, HopTab<CAP>> {
+ H_TAB.get_or_init(|| Mutex::new(HopTab::new()))
+ .lock()
+ .unwrap()
+}
+
+pub fn put_0(ip: IpAddr, hop: u8) {
+ htab().put(ip, hop)
+}
+
+pub fn find_0(ip: IpAddr) -> HopResult<u8> {
+ htab().find_hop(ip)
+}
+
+//
+// below are test/bench codes
+//
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::fs::File;
+ use std::io::Read;
+ use std::net::{IpAddr, Ipv4Addr};
+
+ #[test]
+ fn test_hop_key_conversion() {
+ let ip: IpAddr = "1.2.3.4".parse().unwrap();
+ let key = HopKey::from_ipaddr(ip);
+ assert_eq!(key.to_ipaddr(), ip);
+ }
+
+ #[test]
+ fn test_basic_flow() {
+ let ip: IpAddr = "1.1.1.1".parse().unwrap();
+ put_0(ip, 12);
+
+ let result = find_0(ip).expect("cannot find {ip}");
+ assert_eq!(result, 12);
+ }
+
+ #[test]
+ fn test_not_found() {
+ let ip: IpAddr = "8.8.8.8".parse().unwrap();
+ let result = find_0(ip);
+ assert!(result.is_err());
+ }
+
+ fn u32_to_ipaddr(i: u32) -> IpAddr {
+ IpAddr::V4(Ipv4Addr::from(i))
+ }
+
+ #[test]
+ fn test_full_table() {
+ let mut tab = HopTab::<CAP>::new();
+ for i in 0..CAP {
+ tab.put(u32_to_ipaddr(i as u32), i as u8);
+ }
+ for i in 0..CAP {
+ assert_eq!(tab.find_hop(u32_to_ipaddr(i as u32)).unwrap(), i as u8);
+ }
+ }
+
+ fn get_random(size: usize) -> Vec<u8> {
+ const RAND: &'static str = "/dev/urandom";
+ let mut f = File::open(RAND).unwrap();
+ let mut buf = vec![0u8; size];
+
+ f.read_exact(&mut buf).unwrap();
+
+ buf
+ }
+
+ const ITERATIONS: usize = 1 << 19;
+
+ fn get_random_bulk() -> Vec<u8> {
+ get_random(ITERATIONS * 5)
+ }
+
+ fn get_iphop(raw: &Vec<u8>, idx: usize) -> (IpAddr, u8) {
+ let off = idx * 5;
+ let ip_num = u32::from_ne_bytes(raw[off..off+4].try_into().unwrap());
+
+ (u32_to_ipaddr(ip_num), raw[off+4])
+ }
+
+ #[test]
+ fn test_hoptab_stress() {
+ let mut tab = HopTab::<CAP>::new();
+ let rand = get_random_bulk();
+
+ for i in 0..ITERATIONS {
+ let (ip, hop) = get_iphop(&rand, i);
+
+ tab.put(ip, hop);
+
+ assert_eq!(tab.find_hop(ip).unwrap(), hop);
+ }
+ }
+
+ #[test]
+ fn test_random_cache_integrity() {
+ let mut tab = HopTab::<CAP>::new();
+
+ const SAFE_RANGE: usize = CAP - (CAP >> 1);
+ let rand = get_random_bulk();
+
+ let mut recent_data = std::collections::VecDeque::with_capacity(SAFE_RANGE);
+
+ for i in 0..ITERATIONS {
+ let (ip, hop) = get_iphop(&rand, i);
+
+ tab.put(ip, hop);
+
+ if recent_data.len() == SAFE_RANGE {
+ recent_data.pop_front();
+ }
+ recent_data.push_back((ip, hop));
+ }
+
+ for (ip, expected_hop) in recent_data {
+ let actual_hop = tab.find_hop(ip).expect("In-flight data should not be evicted");
+ assert_eq!(actual_hop, expected_hop);
+ }
+ }
+
+ #[test]
+ fn test_age_overflow_handling() {
+ let mut tab = HopTab::<CAP>::new();
+ let ip1 = u32_to_ipaddr(1);
+ let ip2 = u32_to_ipaddr(2);
+
+ tab.now = u16::MAX;
+
+ tab.put(ip1, 10);
+ assert_eq!(tab.now, 0);
+
+ tab.put(ip2, 20);
+ assert_eq!(tab.now, 1);
+
+ let entry1 = tab.entries[hash(HopKey::from_ipaddr(ip1)).to_idx::<CAP>()];
+ assert_eq!(tab.age(&entry1), 2);
+ assert!(!tab.is_stale(&entry1));
+
+ const DISTINCT: u32 = 3;
+ let ip3 = u32_to_ipaddr(DISTINCT);
+
+ // Since there is no lookup for ip1, it is not become
+ // evictable by putting ip3. Use STALE_AGE - 1 because ip2 has
+ // been putted already.
+ for _ in 0..HopTab::<CAP>::STALE_AGE-1 {
+ tab.put(ip3, 3);
+ }
+
+ assert!(tab.is_stale(&entry1));
+ }
+}
+
+#[cfg(feature = "bench")]
+#[allow(unused_imports)]
+pub use bench_support::reset_0;
+
+#[cfg(feature = "bench")]
+#[allow(dead_code)]
+mod bench_support {
+ use super::*;
+
+ impl HopTabEntry {
+ #[inline]
+ fn clear(&mut self) {
+ self.meta &= (!Self::ST_OCCUPIED << Self::S_STATE) as u64;
+ }
+ }
+
+ impl<const CAP: usize> HopTab<CAP> {
+ fn reset(&mut self) {
+ for i in 0..CAP {
+ self.entries[i].clear();
+ }
+ self.now = 0;
+ }
+ }
+
+ #[inline]
+ pub fn reset_0() {
+ htab().reset();
+ }
+}
diff --git a/src/platform/linux/iptables.rs b/src/platform/linux/iptables.rs
index 91fa1da..4264967 100644
--- a/src/platform/linux/iptables.rs
+++ b/src/platform/linux/iptables.rs
@@ -138,6 +138,21 @@ pub fn install_iptables_rules(ipt: &IPTables) -> Result<()> {
1
).map_err(iptables_err)?;
+ if opt::fake_autottl() {
+ let synack_rule = vec![
+ "-p", "tcp",
+ "--sport", "443",
+ "-m", "tcp", "--tcp-flags", "SYN,ACK", "SYN,ACK",
+ "-j", "NFQUEUE", "--queue-num", &q_num, "--queue-bypass",
+ ];
+
+ ipt.append("mangle", DPIBREAK_CHAIN, &synack_rule).map_err(iptables_err)?;
+ log_println!(LogLevel::Info, "{}: add SYN/ACK learning rule on mangle/{}", ipt.cmd, DPIBREAK_CHAIN);
+
+ ipt.insert("mangle", "INPUT", &["-j", DPIBREAK_CHAIN], 1).map_err(iptables_err)?;
+ log_println!(LogLevel::Info, "{}: add jump to {} chain on INPUT", ipt.cmd, DPIBREAK_CHAIN);
+ }
+
ipt.append("mangle", DPIBREAK_CHAIN, &rule).map_err(iptables_err)?;
log_println!(LogLevel::Info, "{}: new chain {} on table mangle", ipt.cmd, DPIBREAK_CHAIN);
@@ -152,6 +167,10 @@ pub fn cleanup_iptables_rules(ipt: &IPTables) -> Result<()> {
log_println!(LogLevel::Info, "{}: delete jump to {} from mangle/POSTROUTING", ipt.cmd, DPIBREAK_CHAIN);
}
+ if opt::fake_autottl() && ipt.delete("mangle", "INPUT", &["-j", DPIBREAK_CHAIN]).is_ok() {
+ log_println!(LogLevel::Info, "{}: delete jump to {} from mangle/INPUT", ipt.cmd, DPIBREAK_CHAIN);
+ }
+
if ipt.flush_chain("mangle", DPIBREAK_CHAIN).is_ok() {
log_println!(LogLevel::Info, "{}: flush chain {}", ipt.cmd, DPIBREAK_CHAIN);
}
diff --git a/src/platform/linux/nftables.rs b/src/platform/linux/nftables.rs
index ccb918e..27e1830 100644
--- a/src/platform/linux/nftables.rs
+++ b/src/platform/linux/nftables.rs
@@ -103,6 +103,65 @@ pub fn install_nft_rules() -> Result<()> {
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}");
diff --git a/src/platform/windows.rs b/src/platform/windows.rs
index 4303a65..37a6654 100644
--- a/src/platform/windows.rs
+++ b/src/platform/windows.rs
@@ -23,17 +23,29 @@ use windivert::{
use std::sync::{atomic::Ordering, LazyLock, Mutex, MutexGuard};
use crate::{log::LogLevel, log_println, splash};
+fn windivert_filter() -> String {
+ let base = "(outbound and tcp and tcp.DstPort == 443 \
+ and tcp.Payload[0] == 22 \
+ and tcp.Payload[5] == 1)";
+
+ if crate::opt::fake_autottl() {
+ let synack = "(!outbound and tcp and tcp.SrcPort == 443 \
+ and tcp.Syn and tcp.Ack)";
+ format!("({base} or {synack}) and !impostor")
+ } else {
+ format!("{base} and !impostor")
+ }
+}
+
+
pub static WINDIVERT_HANDLE: LazyLock<Mutex<WinDivert<NetworkLayer>>> = LazyLock::new(|| {
use windivert::*;
- const FILTER: &str = "outbound and tcp and tcp.DstPort == 443 \
- and tcp.Payload[0] == 22 \
- and tcp.Payload[5] == 1 \
- and !impostor"; // to prevent inf loop
+ let filter = windivert_filter();
- let h = match WinDivert::network(FILTER, 0, prelude::WinDivertFlags::new()) {
+ let h = match WinDivert::network(&filter, 0, prelude::WinDivertFlags::new()) {
Ok(h) => {
- log_println!(LogLevel::Info, "windivert: HANDLE constructed for {}", FILTER);
+ log_println!(LogLevel::Info, "windivert: HANDLE constructed for {}", filter);
h
},
Err(e) => {