summaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.rs33
-rw-r--r--src/platform/linux.rs165
2 files changed, 173 insertions, 25 deletions
diff --git a/src/main.rs b/src/main.rs
index 12bd303..e41888f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -83,10 +83,31 @@ fn split_packet(pkt: &pkt::PktView, start: u32, end: Option<u32>,
Ok(())
}
-/// Return Ok(true) if packet is handled
-fn handle_packet(pkt: &[u8], buf: &mut Vec::<u8>) -> Result<bool> {
+fn split_packet_1(view: &pkt::PktView, order: &[u32], buf: &mut Vec<u8>) -> Result<()> {
use platform::send_to_raw;
+ let mut it = order.iter().copied();
+
+ let Some(mut first) = it.next() else {
+ return Err(anyhow!("split_packet_1: invalid order array"));
+ };
+
+ for next in it {
+ split_packet(view, first, Some(next), buf)?;
+ send_to_raw(buf)?;
+ std::thread::sleep(std::time::Duration::from_millis(delay_ms()));
+ first = next;
+ }
+
+ split_packet(view, first, None, buf)?;
+ send_to_raw(buf)?;
+
+ Ok(())
+}
+
+
+/// Return Ok(true) if packet is handled
+fn handle_packet(pkt: &[u8], buf: &mut Vec::<u8>) -> Result<bool> {
#[cfg(target_os = "linux")]
let is_filtered = platform::IS_U32_SUPPORTED.load(Ordering::Relaxed);
@@ -102,13 +123,7 @@ fn handle_packet(pkt: &[u8], buf: &mut Vec::<u8>) -> Result<bool> {
// TODO: if clienthello packet has been (unlikely) fragmented,
// we should find the second part and drop, reassemble it here.
- split_packet(&view, 0, Some(1), buf)?;
- send_to_raw(&buf)?;
-
- std::thread::sleep(std::time::Duration::from_millis(delay_ms()));
-
- split_packet(&view, 1, None, buf)?;
- send_to_raw(&buf)?;
+ split_packet_1(&view, &[0, 1], buf)?;
#[cfg(debug_assertions)]
log_println!(LogLevel::Debug, "packet is handled, len={}", pkt.len());
diff --git a/src/platform/linux.rs b/src/platform/linux.rs
index 0faef31..e04a6e4 100644
--- a/src/platform/linux.rs
+++ b/src/platform/linux.rs
@@ -28,6 +28,7 @@ use crate::{log::LogLevel, log_println, splash, MESSAGE_AT_RUN};
pub static IS_U32_SUPPORTED: AtomicBool = AtomicBool::new(false);
pub static IS_XT_U32_LOADED_BY_US: AtomicBool = AtomicBool::new(false);
+static IS_NFT_NOT_SUPPORTED: AtomicBool = AtomicBool::new(false);
pub static QUEUE_NUM: OnceLock<u16> = OnceLock::new();
@@ -83,7 +84,7 @@ fn iptables_err(e: impl ToString) -> Error {
Error::msg(format!("iptables: {}", e.to_string()))
}
-fn install_rules(ipt: &IPTables) -> Result<()> {
+fn install_iptables_rules(ipt: &IPTables) -> Result<()> {
let base = format!("-p tcp --dport 443 -j NFQUEUE --queue-num {} --queue-bypass", queue_num());
let rule = if is_u32_supported(ipt) {
@@ -107,7 +108,7 @@ fn install_rules(ipt: &IPTables) -> Result<()> {
Ok(())
}
-fn cleanup_rules(ipt: &IPTables) -> Result<()> {
+fn cleanup_iptables_rules(ipt: &IPTables) -> Result<()> {
if ipt.delete("mangle", "POSTROUTING", &format!("-j {}", DPIBREAK_CHAIN)).is_ok() {
log_println!(LogLevel::Info, "{}: deleted jump from POSTROUTING", ipt.cmd);
}
@@ -123,12 +124,151 @@ fn cleanup_rules(ipt: &IPTables) -> Result<()> {
Ok(())
}
-pub fn cleanup() -> Result<()> {
- let ipt = iptables::new(false).map_err(iptables_err)?;
- let ip6 = iptables::new(true).map_err(iptables_err)?;
+const DPIBREAK_TABLE: &str = "dpibreak";
+
+fn install_nft_rules() -> Result<()> {
+ use nftables::helper;
+
+ let json = serde_json::json!(
+ {
+ "nftables": [
+ {"add": {"table": {"family": "inet", "name": DPIBREAK_TABLE}}},
+ {
+ "add": {
+ "chain": {
+ "family": "inet",
+ "table": DPIBREAK_TABLE,
+ "name": "OUTPUT",
+ "type": "filter",
+ "hook": "output",
+ "prio": 0,
+ "policy": "accept",
+ }
+ }
+ },
+ {
+ "add": {
+ "chain": {
+ "family": "inet",
+ "table": DPIBREAK_TABLE,
+ "name": DPIBREAK_CHAIN
+ }
+ }
+ },
+ {
+ "add": {
+ "rule": {
+ "family": "inet",
+ "table": DPIBREAK_TABLE,
+ "chain": "OUTPUT",
+ "expr": [{ "jump": { "target": DPIBREAK_CHAIN }}]
+ }
+ }
+ },
+ {
+ "add": {
+ "rule": {
+ "family": "inet",
+ "table": DPIBREAK_TABLE,
+ "chain": DPIBREAK_CHAIN,
+ "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": queue_num(),
+ "flags": [ "bypass" ]
+ }
+ }
+ ]
+ }
+ }
+ }
+ ]
+ }
+ );
+
+ let json_str = serde_json::to_string(&json)?;
+
+ helper::apply_ruleset_raw(&json_str, helper::DEFAULT_NFT,
+ helper::DEFAULT_ARGS)?;
- cleanup_rules(&ipt)?;
- cleanup_rules(&ip6)?;
+ // clienthello filtered by nft
+ IS_U32_SUPPORTED.store(true, Ordering::Relaxed);
+ log_println!(LogLevel::Info, "nftables: create table inet {DPIBREAK_TABLE}");
+
+ Ok(())
+}
+
+fn install_rules() -> Result<()> {
+ match install_nft_rules() {
+ Ok(_) => {},
+ Err(e) => {
+ IS_NFT_NOT_SUPPORTED.store(true, Ordering::Relaxed);
+ log_println!(LogLevel::Warning, "nftables: {}", e.to_string());
+ log_println!(LogLevel::Warning, "fallback to iptables");
+
+ let ipt = iptables::new(false).map_err(iptables_err)?;
+ let ip6 = iptables::new(true).map_err(iptables_err)?;
+
+ install_iptables_rules(&ipt)?;
+ // FIXME: using xt_u32 on ipv6 is not supported; (even if it does,
+ // the rule should be different)
+ install_iptables_rules(&ip6)?;
+ }
+ }
+
+ Ok(())
+}
+
+fn cleanup_rules() -> Result<()> {
+ if IS_NFT_NOT_SUPPORTED.load(Ordering::Relaxed) {
+ let ipt = iptables::new(false).map_err(iptables_err)?;
+ let ip6 = iptables::new(true).map_err(iptables_err)?;
+
+ cleanup_iptables_rules(&ipt)?;
+ cleanup_iptables_rules(&ip6)?;
+ } else {
+ use nftables::*;
+
+ let mut nft = batch::Batch::new();
+
+ // nft delete table inet dpibreak
+ nft.delete(schema::NfListObject::Table(schema::Table {
+ family: types::NfFamily::INet,
+ name: DPIBREAK_TABLE.into(),
+ ..Default::default()
+ }));
+ _ = helper::apply_ruleset(&nft.to_nftables());
+ }
+
+ Ok(())
+}
+
+pub fn cleanup() -> Result<()> {
+ cleanup_rules()?;
if IS_XT_U32_LOADED_BY_US.load(Ordering::Relaxed) {
_ = Command::new("modprobe").args(&["-q", "-r", "xt_u32"]).status();
@@ -139,15 +279,8 @@ pub fn cleanup() -> Result<()> {
}
pub fn bootstrap() -> Result<()> {
- let ipt = iptables::new(false).map_err(iptables_err)?;
- let ip6 = iptables::new(true).map_err(iptables_err)?;
-
- cleanup().ok();
- install_rules(&ipt)?;
- // FIXME: using xt_u32 on ipv6 is not supported; (even if it does,
- // the rule should be different)
- install_rules(&ip6)?;
- Ok(())
+ _ = cleanup(); // In case the previous execution was not cleaned properly
+ install_rules()
}
use socket2::{Domain, Protocol, Socket, Type};