diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/opt.rs | 25 | ||||
| -rw-r--r-- | src/pkt.rs | 124 | ||||
| -rw-r--r-- | src/pkt/fake.rs | 143 | ||||
| -rw-r--r-- | src/pkt/hoptab.rs | 551 | ||||
| -rw-r--r-- | src/platform/linux/iptables.rs | 19 | ||||
| -rw-r--r-- | src/platform/linux/nftables.rs | 59 | ||||
| -rw-r--r-- | src/platform/windows.rs | 24 |
7 files changed, 845 insertions, 100 deletions
@@ -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)?; @@ -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) => { |
