diff options
Diffstat (limited to 'src/linux')
| -rw-r--r-- | src/linux/firewall.rs | 18 | ||||
| -rw-r--r-- | src/linux/mod.rs | 53 | ||||
| -rw-r--r-- | src/linux/routes.rs | 3 |
3 files changed, 60 insertions, 14 deletions
diff --git a/src/linux/firewall.rs b/src/linux/firewall.rs index 79731b7..8a0dd43 100644 --- a/src/linux/firewall.rs +++ b/src/linux/firewall.rs @@ -84,6 +84,12 @@ pub fn preflight() -> Result<()> { /// Replacing the table is one kernel transaction. Connection marks survive table replacement. pub fn apply(enabled: bool, xray_uid: u32) -> Result<()> { + apply_policy(enabled, xray_uid, false) +} +pub fn forward(xray_uid: u32) -> Result<()> { + apply_policy(true, xray_uid, true) +} +fn apply_policy(enabled: bool, xray_uid: u32, allow_tun: bool) -> Result<()> { let existing = tables()?; let table = Table::new(TABLE, ProtoFamily::Inet); let mut batch = Batch::new(); @@ -124,11 +130,13 @@ pub fn apply(enabled: bool, xray_uid: u32) -> Result<()> { guard.set_policy(Policy::Drop); batch.add(&guard, MsgType::Add); exceptions(&mut batch, &guard, xray_uid); - let mut tun = Rule::new(&guard); - tun.add_expr(&nft_expr!(meta oifname)); - tun.add_expr(&nft_expr!(cmp == &b"roentgen0\0"[..])); - tun.add_expr(&nft_expr!(verdict accept)); - batch.add(&tun, MsgType::Add); + if allow_tun { + let mut tun = Rule::new(&guard); + tun.add_expr(&nft_expr!(meta oifname)); + tun.add_expr(&nft_expr!(cmp == &b"roentgen0\0"[..])); + tun.add_expr(&nft_expr!(verdict accept)); + batch.add(&tun, MsgType::Add); + } } send(batch.finalize()).context("Could not apply Roentgen firewall transaction") } diff --git a/src/linux/mod.rs b/src/linux/mod.rs index 68bb3a8..fd9c471 100644 --- a/src/linux/mod.rs +++ b/src/linux/mod.rs @@ -9,12 +9,14 @@ use std::{path::Path, time::Duration}; pub struct LinuxRuntime { routing: String, applied: Option<Trojan>, + armed: bool, } impl Default for LinuxRuntime { fn default() -> Self { Self { routing: "unknown".into(), applied: None, + armed: false, } } } @@ -25,17 +27,39 @@ impl Runtime for LinuxRuntime { xray::preflight().await?; firewall::preflight()?; routes::preflight().await?; + anyhow::ensure!( + self.applied.is_some() || routes::tun_index().await?.is_none(), + "roentgen0 already exists outside the active owned configuration; resolve the interface conflict before enabling" + ); let (uid, _) = xray::identity()?; firewall::apply(true, uid)?; // Persist the boot guard input before recording enabled intent in SQLite. write_guard(true)?; self.routing = "blocked".into(); + self.armed = true; + Ok(()) + } + async fn block(&mut self) -> Result<()> { + firewall::apply(true, xray::identity()?.0)?; + write_guard(true)?; + self.routing = "blocked".into(); + self.applied = None; + xray::systemctl(&["stop", "roentgen-xray.service"]).await?; + routes::remove().await?; Ok(()) } async fn apply(&mut self, connection: Option<&Trojan>, _: u64) -> Result<()> { - if self.applied.as_ref() == connection { + if connection.is_none() { + return self.block().await; + } + anyhow::ensure!( + self.armed, + "Activation requires successful compatibility checks and an armed guard" + ); + if connection.is_some() && self.applied.as_ref() == connection { if let Some(index) = routes::tun_index().await? { routes::capture(index).await?; + firewall::forward(xray::identity()?.0)?; self.routing = "tun".into(); return Ok(()); } @@ -45,14 +69,22 @@ impl Runtime for LinuxRuntime { xray::systemctl(&["stop", "roentgen-xray.service"]).await?; routes::remove().await?; if let Some(connection) = connection { + anyhow::ensure!( + routes::tun_index().await?.is_none(), + "roentgen0 remained after stopping the owned Xray unit; refusing to take over another interface" + ); + xray::preflight().await?; + firewall::preflight()?; + routes::preflight().await?; xray::write_config( Path::new("/run/roentgen/xray.json"), - &xray::configuration(connection, true, None), + &xray::configuration(connection, true), )?; xray::systemctl(&["start", "roentgen-xray.service"]).await?; for _ in 0..100 { if let Some(index) = routes::tun_index().await? { routes::capture(index).await?; + firewall::forward(xray::identity()?.0)?; self.routing = "tun".into(); self.applied = Some(connection.clone()); return Ok(()); @@ -64,14 +96,16 @@ impl Runtime for LinuxRuntime { Ok(()) } async fn maintain(&mut self, connection: Option<&Trojan>, generation: u64) -> Result<()> { - match routes::tun_index().await? { - Some(index) if connection.is_some() => { - routes::capture(index).await?; - self.routing = "tun".into(); - Ok(()) - } - _ => self.apply(connection, generation).await, + if connection.is_none() { + return self.block().await; + } + if !self.armed { + self.arm().await?; } + self.apply(connection, generation).await + } + async fn probe(&mut self, connection: &Trojan, duration: u64) -> Result<xray::Probe> { + xray::Probe::start(connection, duration).await } async fn disable(&mut self) -> Result<()> { xray::systemctl(&["stop", "roentgen-xray.service"]).await?; @@ -80,6 +114,7 @@ impl Runtime for LinuxRuntime { write_guard(false)?; firewall::apply(false, xray::identity()?.0)?; self.routing = "direct".into(); + self.armed = false; Ok(()) } fn routing(&self) -> &str { diff --git a/src/linux/routes.rs b/src/linux/routes.rs index 07083bb..4212183 100644 --- a/src/linux/routes.rs +++ b/src/linux/routes.rs @@ -84,6 +84,9 @@ fn owned_rule(rule: &rtnetlink::packet_route::rule::RuleMessage) -> bool { } pub async fn capture(interface: u32) -> Result<()> { + // Only the product-owned TUN changes this setting. Loose reverse-path + // validation accepts proxied replies whose ordinary reverse route is uplink. + std::fs::write("/proc/sys/net/ipv4/conf/roentgen0/rp_filter", "2")?; let handle = connection()?; for route in [ RouteMessageBuilder::<Ipv4Addr>::new() |